├── android
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-anydpi-v33
│ │ │ │ └── ic_launcher.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── devexperto
│ │ │ │ └── kotlinexpert
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ └── debug
│ │ ├── res
│ │ └── xml
│ │ │ └── network_security_config.xml
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── ios
├── NotesAppIos
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── NotesAppIosApp.swift
│ └── ContentView.swift
└── NotesAppIos.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── antonio.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ ├── xcuserdata
│ └── antonio.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── project.pbxproj
├── common
├── src
│ ├── androidMain
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── devexperto
│ │ │ └── kotlinexpert
│ │ │ ├── getPlatformName.kt
│ │ │ ├── data
│ │ │ └── remote
│ │ │ │ └── NotesUrl.kt
│ │ │ └── ui
│ │ │ └── screens
│ │ │ └── DropdownMenu.android.kt
│ ├── iosMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── devexperto
│ │ │ └── kotlinexpert
│ │ │ ├── getPlatformName.kt
│ │ │ ├── data
│ │ │ └── remote
│ │ │ │ └── NotesUrl.kt
│ │ │ ├── main.kt
│ │ │ └── ui
│ │ │ └── screens
│ │ │ └── DropdownMenu.ios.kt
│ ├── jsMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── devexperto
│ │ │ └── kotlinexpert
│ │ │ ├── getPlatformName.kt
│ │ │ ├── data
│ │ │ └── remote
│ │ │ │ └── NotesUrl.kt
│ │ │ └── ui
│ │ │ ├── common
│ │ │ └── Icon.kt
│ │ │ ├── screens
│ │ │ ├── home
│ │ │ │ ├── Home.kt
│ │ │ │ ├── NotesList.kt
│ │ │ │ └── TopBar.kt
│ │ │ └── detail
│ │ │ │ └── Detail.kt
│ │ │ └── theme
│ │ │ └── AppStyleSheet.kt
│ ├── desktopMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── devexperto
│ │ │ └── kotlinexpert
│ │ │ ├── getPlatformName.kt
│ │ │ ├── data
│ │ │ └── remote
│ │ │ │ └── NotesUrl.kt
│ │ │ └── ui
│ │ │ └── screens
│ │ │ └── DropdownMenu.desktop.kt
│ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── devexperto
│ │ │ └── kotlinexpert
│ │ │ ├── platform.kt
│ │ │ ├── ui
│ │ │ ├── Route.kt
│ │ │ ├── App.kt
│ │ │ ├── screens
│ │ │ │ ├── detail
│ │ │ │ │ └── Detail.kt
│ │ │ │ └── home
│ │ │ │ │ └── Home.kt
│ │ │ └── viewmodels
│ │ │ │ ├── HomeViewModel.kt
│ │ │ │ └── DetailViewModel.kt
│ │ │ └── data
│ │ │ ├── Filter.kt
│ │ │ ├── remote
│ │ │ ├── NotesClient.kt
│ │ │ └── NotesRepository.kt
│ │ │ └── Note.kt
│ └── commonComposeKmpMain
│ │ └── kotlin
│ │ └── com
│ │ └── devexperto
│ │ └── kotlinexpert
│ │ └── ui
│ │ └── screens
│ │ ├── DropdownMenu.kt
│ │ ├── home
│ │ ├── TopBar.kt
│ │ ├── Home.kt
│ │ └── NotesList.kt
│ │ └── detail
│ │ └── Detail.kt
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
└── build.gradle.kts
├── web
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── src
│ └── jsMain
│ │ ├── resources
│ │ ├── kotlin-js.html
│ │ └── index.html
│ │ └── kotlin
│ │ └── com
│ │ └── devexperto
│ │ └── kotlinexpert
│ │ └── Main.kt
└── build.gradle.kts
├── desktop
├── src
│ └── jvmMain
│ │ └── kotlin
│ │ └── com
│ │ └── devexperto
│ │ └── kotlinexpert
│ │ └── Main.kt
└── build.gradle.kts
├── gradle.properties
├── .gitignore
├── settings.gradle.kts
├── gradlew.bat
├── yarn.lock
└── gradlew
/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MyNotes
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ios/NotesAppIos/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/common/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/web/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/web/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/common/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/common/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ios/NotesAppIos/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/common/src/iosMain/kotlin/com/devexperto/kotlinexpert/getPlatformName.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | actual fun getPlatformName(): String = "iOS"
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/getPlatformName.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | actual fun getPlatformName(): String = "Web"
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/common/src/androidMain/kotlin/com/devexperto/kotlinexpert/getPlatformName.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | actual fun getPlatformName(): String = "Android"
--------------------------------------------------------------------------------
/common/src/desktopMain/kotlin/com/devexperto/kotlinexpert/getPlatformName.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | actual fun getPlatformName(): String = "Desktop"
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/common/src/androidMain/kotlin/com/devexperto/kotlinexpert/data/remote/NotesUrl.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data.remote
2 |
3 | actual const val NOTES_URL: String = "http://10.0.2.2:8080/notes"
--------------------------------------------------------------------------------
/common/src/iosMain/kotlin/com/devexperto/kotlinexpert/data/remote/NotesUrl.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data.remote
2 |
3 | actual const val NOTES_URL: String = "http://localhost:8080/notes"
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/data/remote/NotesUrl.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data.remote
2 |
3 | actual const val NOTES_URL: String = "http://localhost:8080/notes"
--------------------------------------------------------------------------------
/common/src/desktopMain/kotlin/com/devexperto/kotlinexpert/data/remote/NotesUrl.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data.remote
2 |
3 | actual const val NOTES_URL: String = "http://localhost:8080/notes"
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/platform.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | expect fun getPlatformName(): String
4 |
5 | fun getAppTitle(): String = "My Notes - ${getPlatformName()}"
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/ui/Route.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui
2 |
3 | sealed interface Route {
4 | object Home : Route
5 | data class Detail(val id: Long) : Route
6 | }
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/data/Filter.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data
2 |
3 | sealed interface Filter {
4 | object All : Filter
5 | class ByType(val type: Note.Type) : Filter
6 | }
--------------------------------------------------------------------------------
/ios/NotesAppIos.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/NotesAppIos/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/web/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/common/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/ios/NotesAppIos.xcodeproj/project.xcworkspace/xcuserdata/antonio.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devexpert-io/kotlin-expert/HEAD/ios/NotesAppIos.xcodeproj/project.xcworkspace/xcuserdata/antonio.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/ios/NotesAppIos/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/web/src/jsMain/resources/kotlin-js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Kotlin/JS Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/android/src/debug/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 10.0.2.2
5 |
6 |
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ios/NotesAppIos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/ui/App.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.navigator.Navigator
5 | import com.devexperto.kotlinexpert.ui.screens.home.HomeScreen
6 |
7 | @Composable
8 | fun App() {
9 | Navigator(HomeScreen)
10 | }
--------------------------------------------------------------------------------
/ios/NotesAppIos/NotesAppIosApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotesAppIosApp.swift
3 | // NotesAppIos
4 | //
5 | // Created by Antonio Leiva Gordillo on 11/7/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct NotesAppIosApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/web/src/jsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sample
6 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/data/remote/NotesClient.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data.remote
2 |
3 | import io.ktor.client.*
4 | import io.ktor.client.plugins.contentnegotiation.*
5 | import io.ktor.serialization.kotlinx.json.*
6 |
7 | val notesClient = HttpClient() {
8 | install(ContentNegotiation) {
9 | json()
10 | }
11 | }
--------------------------------------------------------------------------------
/common/src/iosMain/kotlin/com/devexperto/kotlinexpert/main.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | import androidx.compose.ui.window.ComposeUIViewController
4 | import com.devexperto.kotlinexpert.ui.App
5 | import platform.UIKit.UIViewController
6 |
7 | @Suppress("unused", "FunctionName")
8 | fun MainViewController(): UIViewController {
9 | return ComposeUIViewController { App() }
10 | }
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-anydpi-v33/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/data/Note.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Note(val title: String, val description: String, val type: Type, val id: Long = NEW_NOTE) {
7 | companion object {
8 | const val NEW_NOTE = -1L
9 | }
10 | enum class Type { TEXT, AUDIO }
11 | }
--------------------------------------------------------------------------------
/desktop/src/jvmMain/kotlin/com/devexperto/kotlinexpert/Main.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | import androidx.compose.ui.window.Window
4 | import androidx.compose.ui.window.application
5 | import com.devexperto.kotlinexpert.ui.App
6 |
7 | fun main() {
8 | application {
9 | Window(onCloseRequest = ::exitApplication, title = getAppTitle()) {
10 | App()
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | kotlin.version=1.8.20
3 | agp.version=7.3.0
4 | compose.version=1.4.0
5 | ktor_version=2.1.2
6 | org.gradle.jvmargs=-Xmx4096m
7 | android.useAndroidX=true
8 | kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true
9 | voyager_version=1.0.0-rc05
10 |
11 | #Compose
12 | org.jetbrains.compose.experimental.jscanvas.enabled=true
13 | org.jetbrains.compose.experimental.uikit.enabled=true
14 | kotlin.native.cacheKind=none
--------------------------------------------------------------------------------
/android/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/android/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/android/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/common/src/commonComposeKmpMain/kotlin/com/devexperto/kotlinexpert/ui/screens/DropdownMenu.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | expect fun DropdownMenu(expanded: Boolean, onDismissRequest: () -> Unit, content: @Composable ColumnScope.() -> Unit)
8 |
9 | @Composable
10 | expect fun DropdownMenuItem(onClick: () -> Unit, text: @Composable () -> Unit)
--------------------------------------------------------------------------------
/ios/NotesAppIos.xcodeproj/xcuserdata/antonio.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | NotesAppIos.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/android/src/main/java/com/devexperto/kotlinexpert/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.material.MaterialTheme
7 | import com.devexperto.kotlinexpert.ui.App
8 |
9 | class MainActivity : ComponentActivity() {
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | setContent {
14 | MaterialTheme {
15 | App()
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/ui/common/Icon.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.common
2 |
3 | import androidx.compose.runtime.Composable
4 | import org.jetbrains.compose.web.attributes.AttrsScope
5 | import org.jetbrains.compose.web.dom.Span
6 | import org.jetbrains.compose.web.dom.Text
7 | import org.w3c.dom.HTMLSpanElement
8 |
9 | @Composable
10 | fun Icon(iconName: String, attrs: (AttrsScope.() -> Unit)? = null) {
11 | Span(
12 | attrs = {
13 | classes("material-icons")
14 | attrs?.invoke(this)
15 | }
16 | ) {
17 | Text(iconName)
18 | }
19 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | local.properties
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### IntelliJ IDEA ###
9 | .idea/*
10 | *.iws
11 | *.iml
12 | *.ipr
13 | out/
14 | !**/src/main/**/out/
15 | !**/src/test/**/out/
16 |
17 | ### Eclipse ###
18 | .apt_generated
19 | .classpath
20 | .factorypath
21 | .project
22 | .settings
23 | .springBeans
24 | .sts4-cache
25 | bin/
26 | !**/src/main/**/bin/
27 | !**/src/test/**/bin/
28 |
29 | ### NetBeans ###
30 | /nbproject/private/
31 | /nbbuild/
32 | /dist/
33 | /nbdist/
34 | /.nb-gradle/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
39 | ### Mac OS ###
40 | .DS_Store
--------------------------------------------------------------------------------
/ios/NotesAppIos/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // NotesAppIos
4 | //
5 | // Created by Antonio Leiva Gordillo on 11/7/23.
6 | //
7 |
8 | import SwiftUI
9 | import common
10 |
11 | struct ContentView: View {
12 | var body: some View {
13 | ComposeView()
14 | }
15 | }
16 |
17 | struct ComposeView: UIViewControllerRepresentable {
18 | func makeUIViewController(context: Context) -> UIViewController {
19 | MainKt.MainViewController()
20 | }
21 |
22 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
23 | }
24 |
25 | struct ContentView_Previews: PreviewProvider {
26 | static var previews: some View {
27 | ContentView()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/common/src/iosMain/kotlin/com/devexperto/kotlinexpert/ui/screens/DropdownMenu.ios.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | actual fun DropdownMenu(
8 | expanded: Boolean,
9 | onDismissRequest: () -> Unit,
10 | content: @Composable ColumnScope.() -> Unit
11 | ) {
12 | androidx.compose.material3.DropdownMenu(
13 | expanded = expanded,
14 | onDismissRequest = onDismissRequest,
15 | content = content
16 | )
17 | }
18 |
19 | @Composable
20 | actual fun DropdownMenuItem(onClick: () -> Unit, text: @Composable () -> Unit) {
21 | androidx.compose.material3.DropdownMenuItem(
22 | onClick = onClick,
23 | text = text
24 | )
25 | }
--------------------------------------------------------------------------------
/common/src/androidMain/kotlin/com/devexperto/kotlinexpert/ui/screens/DropdownMenu.android.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | actual fun DropdownMenu(
8 | expanded: Boolean,
9 | onDismissRequest: () -> Unit,
10 | content: @Composable ColumnScope.() -> Unit
11 | ) {
12 | androidx.compose.material3.DropdownMenu(
13 | expanded = expanded,
14 | onDismissRequest = onDismissRequest,
15 | content = content
16 | )
17 | }
18 |
19 | @Composable
20 | actual fun DropdownMenuItem(onClick: () -> Unit, text: @Composable () -> Unit) {
21 | androidx.compose.material3.DropdownMenuItem(
22 | onClick = onClick,
23 | text = text
24 | )
25 | }
--------------------------------------------------------------------------------
/common/src/desktopMain/kotlin/com/devexperto/kotlinexpert/ui/screens/DropdownMenu.desktop.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | actual fun DropdownMenu(
8 | expanded: Boolean,
9 | onDismissRequest: () -> Unit,
10 | content: @Composable ColumnScope.() -> Unit
11 | ) {
12 | androidx.compose.material3.DropdownMenu(
13 | expanded = expanded,
14 | onDismissRequest = onDismissRequest,
15 | content = content
16 | )
17 | }
18 |
19 | @Composable
20 | actual fun DropdownMenuItem(onClick: () -> Unit, text: @Composable () -> Unit) {
21 | androidx.compose.material3.DropdownMenuItem(
22 | onClick = onClick,
23 | text = text
24 | )
25 | }
--------------------------------------------------------------------------------
/android/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
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/ui/screens/detail/Detail.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.detail
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import cafe.adriel.voyager.core.model.rememberScreenModel
6 | import cafe.adriel.voyager.core.screen.Screen
7 | import cafe.adriel.voyager.navigator.LocalNavigator
8 | import cafe.adriel.voyager.navigator.currentOrThrow
9 | import com.devexperto.kotlinexpert.ui.viewmodels.DetailViewModel
10 |
11 | data class DetailScreen(val noteId: Long) : Screen {
12 |
13 | @Composable
14 | override fun Content() {
15 | val navigator = LocalNavigator.currentOrThrow
16 | Detail(
17 | vm = rememberScreenModel { DetailViewModel(noteId) },
18 | onClose = { navigator.pop() })
19 | }
20 |
21 | }
22 |
23 | @Composable
24 | expect fun Detail(vm: DetailViewModel, onClose: () -> Unit)
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2 | pluginManagement {
3 | repositories {
4 | google()
5 | gradlePluginPortal()
6 | mavenCentral()
7 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
8 | }
9 |
10 | plugins {
11 | kotlin("multiplatform").version(extra["kotlin.version"] as String)
12 | kotlin("android").version(extra["kotlin.version"] as String)
13 | kotlin("plugin.serialization").version(extra["kotlin.version"] as String)
14 | id("org.jetbrains.compose").version(extra["compose.version"] as String)
15 | id("com.android.application").version(extra["agp.version"] as String)
16 | id("com.android.library").version(extra["agp.version"] as String)
17 | }
18 | }
19 |
20 | rootProject.name = "MyNotes"
21 |
22 | include(":common", ":desktop", ":web", ":android")
23 |
24 |
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/ui/screens/home/Home.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.home
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import cafe.adriel.voyager.core.model.rememberScreenModel
6 | import cafe.adriel.voyager.core.screen.Screen
7 | import cafe.adriel.voyager.navigator.LocalNavigator
8 | import cafe.adriel.voyager.navigator.currentOrThrow
9 | import com.devexperto.kotlinexpert.ui.screens.detail.DetailScreen
10 | import com.devexperto.kotlinexpert.ui.viewmodels.HomeViewModel
11 |
12 | object HomeScreen : Screen {
13 |
14 | @Composable
15 | override fun Content() {
16 | val navigator = LocalNavigator.currentOrThrow
17 | Home(
18 | vm = rememberScreenModel { HomeViewModel() },
19 | onNoteClick = { navigator.push(DetailScreen(it)) }
20 | )
21 | }
22 |
23 | }
24 |
25 | @Composable
26 | expect fun Home(vm: HomeViewModel, onNoteClick: (Long) -> Unit)
--------------------------------------------------------------------------------
/desktop/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UNUSED_VARIABLE")
2 |
3 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
4 |
5 | plugins {
6 | kotlin("multiplatform")
7 | id("org.jetbrains.compose")
8 | }
9 |
10 | group = "com.devexperto.kotlinexpert"
11 | version = "1.0-SNAPSHOT"
12 |
13 | kotlin {
14 | jvm {
15 | jvmToolchain(11)
16 | withJava()
17 | }
18 | sourceSets {
19 | val jvmMain by getting {
20 | dependencies {
21 | implementation(project(":common"))
22 | implementation(compose.desktop.currentOs)
23 | }
24 | }
25 | val jvmTest by getting
26 | }
27 | }
28 |
29 | compose.desktop {
30 | application {
31 | mainClass = "com.devexperto.kotlinexpert.MainKt"
32 | nativeDistributions {
33 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
34 | packageName = "MyNotes"
35 | packageVersion = "1.0.0"
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/web/src/jsMain/kotlin/com/devexperto/kotlinexpert/Main.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert
2 |
3 | import com.devexperto.kotlinexpert.ui.App
4 | import com.devexperto.kotlinexpert.ui.theme.AppStyleSheet
5 | import kotlinx.browser.document
6 | import kotlinx.browser.window
7 | import org.jetbrains.compose.web.css.Style
8 | import org.jetbrains.compose.web.renderComposable
9 |
10 | fun main() {
11 | composeSample()
12 | kotlinJsSample()
13 | }
14 |
15 | private fun composeSample() {
16 | document.getElementById("root") ?: return
17 | renderComposable(rootElementId = "root") {
18 | Style(AppStyleSheet)
19 | App()
20 | }
21 | }
22 |
23 | private fun kotlinJsSample() {
24 | window.onload = {
25 | val message = document.getElementById("message")
26 | if (message != null) {
27 | message.textContent = "Hello, Kotlin/JS!"
28 |
29 | val button = document.getElementById("button")!!
30 | button.addEventListener("click", {
31 | window.alert("You clicked the button!")
32 | })
33 | }
34 | }
35 | }
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | id("org.jetbrains.compose")
5 | id("com.android.application")
6 | id("org.jetbrains.kotlin.android")
7 | }
8 |
9 | android {
10 | namespace = "com.devexperto.kotlinexpert"
11 | compileSdk = 33
12 |
13 | defaultConfig {
14 | applicationId = "com.devexperto.kotlinexpert"
15 | minSdk = 24
16 | targetSdk = 33
17 | versionCode = 1
18 | versionName = "1.0"
19 |
20 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = false
26 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility = JavaVersion.VERSION_1_8
31 | targetCompatibility = JavaVersion.VERSION_1_8
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation(project(":common"))
37 | implementation("androidx.activity:activity-compose:1.7.0")
38 | implementation("androidx.appcompat:appcompat:1.6.1")
39 | }
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/data/remote/NotesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.data.remote
2 |
3 | import com.devexperto.kotlinexpert.data.Note
4 | import io.ktor.client.call.*
5 | import io.ktor.client.request.*
6 | import io.ktor.http.*
7 |
8 | expect val NOTES_URL: String
9 |
10 | object NotesRepository {
11 |
12 | suspend fun save(note: Note) {
13 | notesClient.post(NOTES_URL) {
14 | setBody(note)
15 | contentType(ContentType.Application.Json)
16 | }
17 | }
18 |
19 | suspend fun getAll(): List {
20 | val response = notesClient.request(NOTES_URL)
21 | return response.body()
22 | }
23 |
24 | suspend fun getById(id: Long): Note {
25 | val response = notesClient.request("$NOTES_URL/$id")
26 | return response.body()
27 | }
28 |
29 | suspend fun update(note: Note) {
30 | notesClient.put(NOTES_URL) {
31 | setBody(note)
32 | contentType(ContentType.Application.Json)
33 | }
34 | }
35 |
36 | suspend fun delete(note: Note) {
37 | notesClient.delete("$NOTES_URL/${note.id}")
38 | }
39 | }
--------------------------------------------------------------------------------
/web/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | id("org.jetbrains.compose")
4 | }
5 |
6 | group "com.devexperto.kotlinexpert"
7 | version "1.0-SNAPSHOT"
8 |
9 | repositories {
10 | google()
11 | mavenCentral()
12 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
13 | }
14 |
15 | kotlin {
16 | js(IR) {
17 | browser {
18 | testTask {
19 | testLogging.showStandardStreams = true
20 | useKarma {
21 | useChromeHeadless()
22 | useFirefox()
23 | }
24 | }
25 | }
26 | binaries.executable()
27 | }
28 | sourceSets {
29 | val jsMain by getting {
30 | dependencies {
31 | implementation(project(":common"))
32 | implementation(compose.web.core)
33 | implementation(compose.runtime)
34 | }
35 | }
36 | val jsTest by getting {
37 | dependencies {
38 | implementation(kotlin("test-js"))
39 | }
40 | }
41 | }
42 | }
43 |
44 | // Workaround for https://youtrack.jetbrains.com/issue/KTIJ-16480
45 | tasks.register("prepareKotlinBuildScriptModel"){}
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/ui/screens/home/Home.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.home
2 |
3 |
4 | import androidx.compose.runtime.Composable
5 | import com.devexperto.kotlinexpert.data.Note
6 | import com.devexperto.kotlinexpert.ui.common.Icon
7 | import com.devexperto.kotlinexpert.ui.theme.AppStyleSheet
8 | import com.devexperto.kotlinexpert.ui.viewmodels.HomeViewModel
9 | import org.jetbrains.compose.web.css.*
10 | import org.jetbrains.compose.web.dom.Div
11 | import org.jetbrains.compose.web.dom.Text
12 |
13 | @Composable
14 | actual fun Home(vm: HomeViewModel, onNoteClick: (noteId: Long) -> Unit) {
15 | Div(attrs = {
16 | style {
17 | display(DisplayStyle.Flex)
18 | flexDirection(FlexDirection.Column)
19 | width(100.percent)
20 | height(100.percent)
21 | }
22 | }
23 |
24 | ) {
25 | TopBar(onFilterClick = vm::onFilterClick)
26 |
27 | Div {
28 |
29 | if (vm.state.loading) {
30 | Text("Cargando...")
31 | }
32 |
33 | vm.state.filteredNotes?.let { notes ->
34 | NotesList(
35 | notes = notes,
36 | onNoteClick = { onNoteClick(it.id) }
37 | )
38 | }
39 | }
40 |
41 | Div(
42 | attrs = {
43 | classes(AppStyleSheet.fab)
44 | onClick { onNoteClick(Note.NEW_NOTE) }
45 | }
46 | ) {
47 | Icon("add")
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/ui/viewmodels/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.viewmodels
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import cafe.adriel.voyager.core.model.ScreenModel
7 | import cafe.adriel.voyager.core.model.coroutineScope
8 | import com.devexperto.kotlinexpert.data.Filter
9 | import com.devexperto.kotlinexpert.data.Note
10 | import com.devexperto.kotlinexpert.data.remote.NotesRepository
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.launch
13 |
14 | class HomeViewModel : ScreenModel {
15 |
16 | var state by mutableStateOf(UiState())
17 | private set
18 |
19 | init {
20 | loadNotes()
21 | }
22 |
23 | private fun loadNotes() {
24 | coroutineScope.launch {
25 | state = UiState(loading = true)
26 | val response = NotesRepository.getAll()
27 | state = UiState(notes = response)
28 | }
29 | }
30 |
31 | fun onFilterClick(filter: Filter) {
32 | state = state.copy(filter = filter)
33 | }
34 |
35 | data class UiState(
36 | val notes: List? = null,
37 | val loading: Boolean = false,
38 | val filter: Filter = Filter.All,
39 | ) {
40 | val filteredNotes: List?
41 | get() = notes?.let {
42 | when (filter) {
43 | Filter.All -> notes
44 | is Filter.ByType -> notes.filter { it.type == filter.type }
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/common/src/commonComposeKmpMain/kotlin/com/devexperto/kotlinexpert/ui/screens/home/TopBar.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.home
2 |
3 | import androidx.compose.material3.*
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.filled.FilterList
6 | import androidx.compose.runtime.*
7 | import com.devexperto.kotlinexpert.data.Filter
8 | import com.devexperto.kotlinexpert.data.Note
9 | import com.devexperto.kotlinexpert.getAppTitle
10 | import com.devexperto.kotlinexpert.ui.screens.DropdownMenu
11 | import com.devexperto.kotlinexpert.ui.screens.DropdownMenuItem
12 |
13 | @OptIn(ExperimentalMaterial3Api::class)
14 | @Composable
15 | fun TopBar(onFilterClick: (Filter) -> Unit) {
16 | TopAppBar(
17 | title = { Text(getAppTitle()) },
18 | actions = { FiltersAction(onFilterClick) }
19 | )
20 | }
21 |
22 | @Composable
23 | private fun FiltersAction(onFilterClick: (Filter) -> Unit) {
24 | var expanded by remember { mutableStateOf(false) }
25 |
26 | IconButton(onClick = { expanded = true }) {
27 |
28 | Icon(imageVector = Icons.Default.FilterList, contentDescription = "Filter")
29 |
30 | DropdownMenu(expanded, onDismissRequest = { expanded = false }) {
31 | listOf(
32 | Filter.All to "All",
33 | Filter.ByType(Note.Type.TEXT) to "Text",
34 | Filter.ByType(Note.Type.AUDIO) to "Audio"
35 | ).forEach { (filter, text) ->
36 | DropdownMenuItem(onClick = {
37 | onFilterClick(filter)
38 | expanded = false
39 | }) {
40 | Text(text)
41 | }
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/ui/screens/home/NotesList.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.home
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.devexperto.kotlinexpert.data.Note
5 | import com.devexperto.kotlinexpert.ui.theme.AppStyleSheet
6 | import org.jetbrains.compose.web.css.*
7 | import org.jetbrains.compose.web.dom.Div
8 | import org.jetbrains.compose.web.dom.H2
9 | import org.jetbrains.compose.web.dom.Span
10 | import org.jetbrains.compose.web.dom.Text
11 |
12 | @Composable
13 | fun NotesList(notes: List, onNoteClick: (Note) -> Unit) {
14 | Div(
15 | attrs = {
16 | style {
17 | display(DisplayStyle.Flex)
18 | flexDirection(FlexDirection.Column)
19 | alignItems(AlignItems.Center)
20 | width(100.percent)
21 | height(100.percent)
22 | }
23 | },
24 |
25 | ) {
26 | notes.forEach { note ->
27 | NoteCard(note, onNoteClick)
28 | }
29 | }
30 | }
31 |
32 | @Composable
33 | fun NoteCard(note: Note, onNoteClick: (Note) -> Unit) {
34 | Div(
35 | attrs = {
36 | classes(AppStyleSheet.noteCard)
37 | onClick { onNoteClick(note) }
38 | }
39 | ) {
40 | Div(attrs = { classes(AppStyleSheet.noteCardHeader) }) {
41 | H2(attrs = { classes(AppStyleSheet.noteCardTitle) }) {
42 | Text(note.title)
43 | }
44 |
45 | if (note.type == Note.Type.AUDIO) {
46 | Span(attrs = { style { marginLeft(8.px) } }) {
47 | Text("🎤")
48 | }
49 | }
50 | }
51 |
52 | Div {
53 | Text(note.description)
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/common/src/commonComposeKmpMain/kotlin/com/devexperto/kotlinexpert/ui/screens/home/Home.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.home
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.*
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.Add
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import com.devexperto.kotlinexpert.data.Note
13 | import com.devexperto.kotlinexpert.ui.viewmodels.HomeViewModel
14 |
15 | @OptIn(ExperimentalMaterial3Api::class)
16 | @Composable
17 | actual fun Home(vm: HomeViewModel, onNoteClick: (Long) -> Unit) {
18 |
19 | MaterialTheme {
20 | Scaffold(
21 | topBar = { TopBar(onFilterClick = vm::onFilterClick) },
22 | floatingActionButton = {
23 | FloatingActionButton(onClick = { onNoteClick(Note.NEW_NOTE) }) {
24 | Icon(imageVector = Icons.Default.Add, contentDescription = "Add Note")
25 | }
26 | }
27 | ) { padding ->
28 | Box(
29 | contentAlignment = Alignment.Center,
30 | modifier = Modifier.padding(padding).fillMaxSize()
31 | ) {
32 | if (vm.state.loading) {
33 | CircularProgressIndicator()
34 | }
35 |
36 | vm.state.filteredNotes?.let { notes ->
37 | NotesList(
38 | notes = notes,
39 | onNoteClick = { onNoteClick(it.id) }
40 | )
41 | }
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/common/src/commonMain/kotlin/com/devexperto/kotlinexpert/ui/viewmodels/DetailViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.viewmodels
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import cafe.adriel.voyager.core.model.ScreenModel
7 | import cafe.adriel.voyager.core.model.coroutineScope
8 | import com.devexperto.kotlinexpert.data.Note
9 | import com.devexperto.kotlinexpert.data.remote.NotesRepository
10 | import kotlinx.coroutines.launch
11 |
12 | class DetailViewModel(private val noteId: Long) : ScreenModel {
13 |
14 | var state by mutableStateOf(UiState())
15 | private set
16 |
17 | init {
18 | if (noteId != Note.NEW_NOTE) {
19 | loadNote()
20 | }
21 | }
22 |
23 | private fun loadNote() {
24 | coroutineScope.launch {
25 | state = UiState(loading = true)
26 | state = UiState(note = NotesRepository.getById(noteId))
27 | }
28 | }
29 |
30 | fun save() {
31 | coroutineScope.launch {
32 | val note = state.note
33 | if (note.id == Note.NEW_NOTE) {
34 | NotesRepository.save(note)
35 | } else {
36 | NotesRepository.update(note)
37 | }
38 | state = state.copy(saved = true)
39 | }
40 | }
41 |
42 | fun update(note: Note) {
43 | state = state.copy(note = note)
44 | }
45 |
46 | fun delete() {
47 | coroutineScope.launch {
48 | NotesRepository.delete(state.note)
49 | state = state.copy(saved = true)
50 | }
51 | }
52 |
53 | data class UiState(
54 | val note: Note = Note("", "", Note.Type.TEXT),
55 | val loading: Boolean = false,
56 | val saved: Boolean = false
57 | )
58 | }
--------------------------------------------------------------------------------
/android/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
19 |
22 |
23 |
24 |
25 |
31 |
--------------------------------------------------------------------------------
/common/src/commonComposeKmpMain/kotlin/com/devexperto/kotlinexpert/ui/screens/home/NotesList.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.home
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.material3.*
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.filled.Mic
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.unit.dp
14 | import com.devexperto.kotlinexpert.data.Note
15 |
16 | @Composable
17 | fun NotesList(notes: List, onNoteClick: (Note) -> Unit) {
18 | LazyColumn(
19 | modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally
20 | ) {
21 | items(notes) { note ->
22 | Card(
23 | modifier = Modifier
24 | .padding(8.dp)
25 | .fillMaxWidth(0.8f)
26 | .clickable { onNoteClick(note) }
27 | ) {
28 | Column(
29 | modifier = Modifier.padding(16.dp)
30 | ) {
31 | Row {
32 | Text(
33 | text = note.title, style = MaterialTheme.typography.titleLarge, modifier = Modifier.weight(1f)
34 | )
35 | if (note.type == Note.Type.AUDIO) {
36 | Icon(
37 | imageVector = Icons.Default.Mic, contentDescription = null
38 | )
39 | }
40 | }
41 |
42 | Spacer(modifier = Modifier.height(8.dp))
43 | Text(text = note.description)
44 | }
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/ui/screens/home/TopBar.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.home
2 |
3 | import androidx.compose.runtime.*
4 | import com.devexperto.kotlinexpert.data.Filter
5 | import com.devexperto.kotlinexpert.data.Note
6 | import com.devexperto.kotlinexpert.ui.common.Icon
7 | import com.devexperto.kotlinexpert.ui.theme.AppStyleSheet
8 | import org.jetbrains.compose.web.dom.Div
9 | import org.jetbrains.compose.web.dom.H1
10 | import org.jetbrains.compose.web.dom.Text
11 |
12 | @Composable
13 | fun TopBar(onFilterClick: (Filter) -> Unit) {
14 | Div(attrs = { classes(AppStyleSheet.topBar) }) {
15 | H1(attrs = { classes(AppStyleSheet.topBarTitle) }) {
16 | Text("Mis notas")
17 | }
18 | FiltersAction(onFilterClick)
19 | }
20 | }
21 |
22 | @Composable
23 | fun FiltersAction(onFilterClick: (Filter) -> Unit) {
24 | var expanded by remember { mutableStateOf(false) }
25 |
26 | Div {
27 | Icon(
28 | iconName = "filter_list",
29 | attrs = {
30 | classes(AppStyleSheet.topBarIcon)
31 | onClick { expanded = !expanded }
32 | })
33 |
34 | if (expanded) {
35 | Div(attrs = { classes(AppStyleSheet.filtersActionExpanded) }) {
36 | listOf(
37 | Filter.All to "All",
38 | Filter.ByType(Note.Type.TEXT) to "Text",
39 | Filter.ByType(Note.Type.AUDIO) to "Audio"
40 | ).forEach { (filter, text) ->
41 | Div(attrs = {
42 | classes(AppStyleSheet.filtersActionExpandedItem)
43 | onClick {
44 | onFilterClick(filter)
45 | expanded = false
46 | }
47 | }) {
48 | Text(text)
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/ui/theme/AppStyleSheet.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.theme
2 |
3 | import org.jetbrains.compose.web.css.*
4 | import org.jetbrains.compose.web.css.keywords.auto
5 |
6 | object AppStyleSheet : StyleSheet() {
7 |
8 | init {
9 | "*" style {
10 | fontFamily("Verdana", "sans-serif")
11 | }
12 |
13 | "body" style {
14 | margin(0.px)
15 | }
16 | }
17 |
18 | val fab by style {
19 | display(DisplayStyle.Flex)
20 | justifyContent(JustifyContent.Center)
21 | alignItems(AlignItems.Center)
22 | position(Position.Fixed)
23 | bottom(16.px)
24 | right(16.px)
25 | width(64.px)
26 | height(64.px)
27 | borderRadius(50.percent)
28 | backgroundColor(Color("#6200EE"))
29 | color(Color.white)
30 | fontSize(24.px)
31 | lineHeight(1.em)
32 | cursor("pointer")
33 | property("box-shadow", "0 5px 5px 0 rgba(0, 0, 0, 0.4)")
34 | self + hover style {
35 | backgroundColor(Color("#6200eecc"))
36 | }
37 | }
38 |
39 | val topBar by style {
40 | display(DisplayStyle.Flex)
41 | flexDirection(FlexDirection.Row)
42 | alignItems(AlignItems.Center)
43 | gap(16.px)
44 | padding(16.px)
45 | backgroundColor(Color("#6200EE"))
46 | }
47 |
48 | val topBarTitle by style {
49 | color(Color.white)
50 | margin(0.px)
51 | fontSize(25.px)
52 | fontWeight("normal")
53 | property("margin-right", auto)
54 | }
55 |
56 | val topBarIcon by style {
57 | color(Color.white)
58 | fontSize(24.px)
59 | cursor("pointer")
60 | borderRadius(50.percent)
61 | padding(12.px)
62 | self + hover style {
63 | backgroundColor(Color("#ffffff4d"))
64 | }
65 | }
66 |
67 | val filtersActionExpanded by style {
68 | display(DisplayStyle.Flex)
69 | flexDirection(FlexDirection.Column)
70 | alignItems(AlignItems.Center)
71 | backgroundColor(Color.white)
72 | borderRadius(4.px)
73 | position(Position.Absolute)
74 | top(16.px)
75 | right(16.px)
76 | property("box-shadow", "0 0 4px rgba(0, 0, 0, 0.25)")
77 | property("z-index", 1)
78 | }
79 |
80 | val filtersActionExpandedItem by style {
81 | justifyContent(JustifyContent.Center)
82 | alignItems(AlignItems.Center)
83 | padding(16.px)
84 | minWidth(150.px)
85 | cursor("pointer")
86 | }
87 |
88 | val noteCard by style {
89 | display(DisplayStyle.Flex)
90 | flexDirection(FlexDirection.Column)
91 | width(80.percent)
92 | maxWidth(600.px)
93 | marginTop(8.px)
94 | marginBottom(8.px)
95 | border(1.px, LineStyle.Solid, Color.black)
96 | borderRadius(4.px)
97 | padding(16.px)
98 | cursor("pointer")
99 | }
100 |
101 | val noteCardHeader by style {
102 | display(DisplayStyle.Flex)
103 | flexDirection(FlexDirection.Row)
104 | alignItems(AlignItems.Center)
105 | width(100.percent)
106 | }
107 |
108 | val noteCardTitle by style {
109 | flex(1)
110 | lineHeight(1.5.em)
111 | margin(0.px)
112 | fontSize(20.px)
113 | fontWeight("normal")
114 | }
115 |
116 | val detailInput by style {
117 | padding(16.px)
118 | borderRadius(4.px)
119 | border(1.px, LineStyle.Solid, Color("#ccc"))
120 | }
121 | }
--------------------------------------------------------------------------------
/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage", "OPT_IN_USAGE", "PropertyName")
2 |
3 | val ktor_version: String by rootProject.project
4 | val voyager_version: String by rootProject.project
5 |
6 | plugins {
7 | kotlin("multiplatform")
8 | kotlin("plugin.serialization")
9 | id("org.jetbrains.compose")
10 | id("com.android.library")
11 | }
12 |
13 | group = "com.example"
14 | version = "1.0-SNAPSHOT"
15 |
16 | kotlin {
17 | targetHierarchy.default()
18 | android()
19 | jvm("desktop") {
20 | jvmToolchain(11)
21 |
22 | }
23 | js(IR) {
24 | browser()
25 | }
26 |
27 | listOf(
28 | iosArm64(),
29 | iosX64(),
30 | iosSimulatorArm64()
31 | ).forEach {
32 | it.binaries.framework {
33 | baseName = "common"
34 | linkerOpts("-framework", "CoreGraphics")
35 | linkerOpts("-framework", "CoreText")
36 | linkerOpts("-framework", "Metal")
37 | }
38 | }
39 |
40 | sourceSets {
41 | val commonMain by getting {
42 | dependencies {
43 | api(compose.runtime)
44 | api(compose.foundation)
45 | api(compose.material3)
46 | implementation(compose.materialIconsExtended)
47 | implementation("io.ktor:ktor-client-core:$ktor_version")
48 | implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
49 | implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
50 | implementation("cafe.adriel.voyager:voyager-navigator:$voyager_version")
51 | }
52 | }
53 | val commonTest by getting {
54 | dependencies {
55 | implementation(kotlin("test"))
56 | }
57 | }
58 |
59 | val commonComposeKmpMain by creating {
60 | dependsOn(commonMain)
61 | }
62 |
63 | val desktopMain by getting {
64 | dependsOn(commonComposeKmpMain)
65 | dependencies {
66 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.6.4")
67 | implementation("io.ktor:ktor-client-okhttp:$ktor_version")
68 | }
69 | }
70 | val desktopTest by getting
71 |
72 | val androidMain by getting {
73 | dependsOn(commonComposeKmpMain)
74 | dependencies {
75 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
76 | implementation("io.ktor:ktor-client-okhttp:$ktor_version")
77 | }
78 | }
79 |
80 | val androidTest by getting
81 |
82 | val jsMain by getting {
83 | dependencies {
84 | implementation(compose.web.core)
85 | implementation(compose.runtime)
86 | implementation("io.ktor:ktor-client-js:$ktor_version")
87 | }
88 | }
89 |
90 | val jsTest by getting
91 |
92 | val iosMain by getting {
93 | dependsOn(commonComposeKmpMain)
94 | dependencies {
95 | implementation("io.ktor:ktor-client-darwin:$ktor_version")
96 | }
97 | }
98 | }
99 | }
100 |
101 | android {
102 | compileSdk = 33
103 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
104 | defaultConfig {
105 | minSdk = 24
106 | targetSdk = 33
107 | }
108 | compileOptions {
109 | sourceCompatibility = JavaVersion.VERSION_1_8
110 | targetCompatibility = JavaVersion.VERSION_1_8
111 | }
112 | }
--------------------------------------------------------------------------------
/common/src/jsMain/kotlin/com/devexperto/kotlinexpert/ui/screens/detail/Detail.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.detail
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.devexperto.kotlinexpert.data.Note
5 | import com.devexperto.kotlinexpert.ui.common.Icon
6 | import com.devexperto.kotlinexpert.ui.theme.AppStyleSheet
7 | import com.devexperto.kotlinexpert.ui.viewmodels.DetailViewModel
8 | import org.jetbrains.compose.web.attributes.placeholder
9 | import org.jetbrains.compose.web.attributes.selected
10 | import org.jetbrains.compose.web.css.*
11 | import org.jetbrains.compose.web.dom.*
12 |
13 | @Composable
14 | actual fun Detail(vm: DetailViewModel, onClose: () -> Unit) {
15 |
16 | val note = vm.state.note
17 |
18 | Div {
19 | TopBar(
20 | note = note,
21 | onClose = onClose,
22 | onSave = vm::save,
23 | onDelete = vm::delete
24 | )
25 |
26 | if (vm.state.saved) {
27 | onClose()
28 | }
29 |
30 | if (vm.state.loading) {
31 | Text("Cargando...")
32 | } else {
33 | Div(
34 | attrs = {
35 | style {
36 | padding(32.px)
37 | display(DisplayStyle.Flex)
38 | flexDirection(FlexDirection.Column)
39 | gap(16.px)
40 | maxWidth(600.px)
41 | property("margin", "0 auto")
42 | }
43 | }
44 | ) {
45 | TextInput(
46 | value = note.title,
47 | attrs = {
48 | classes(AppStyleSheet.detailInput)
49 | placeholder("Title")
50 | onInput { vm.update(note.copy(title = it.value)) }
51 | }
52 | )
53 | TypeDropdown(
54 | value = note.type,
55 | onValueChange = { vm.update(note.copy(type = it)) }
56 | )
57 | TextArea(
58 | value = note.description,
59 | attrs = {
60 | classes(AppStyleSheet.detailInput)
61 | placeholder("Description")
62 | onInput { vm.update(note.copy(description = it.value)) }
63 | }
64 | )
65 | }
66 | }
67 | }
68 | }
69 |
70 | @Composable
71 | private fun TypeDropdown(value: Note.Type, onValueChange: (Note.Type) -> Unit) {
72 | Select(
73 | attrs = {
74 | classes(AppStyleSheet.detailInput)
75 | onChange { onValueChange(Note.Type.valueOf(it.target.value)) }
76 | }
77 | ) {
78 | Note.Type.values().forEach {
79 | Option(
80 | value = it.name,
81 | attrs = {
82 | if (it == value) {
83 | selected()
84 | }
85 | }) {
86 | Text(it.name)
87 | }
88 | }
89 | }
90 | }
91 |
92 | @Composable
93 | private fun TopBar(note: Note, onClose: () -> Unit, onSave: () -> Unit, onDelete: () -> Unit) {
94 | Div(attrs = { classes(AppStyleSheet.topBar) }) {
95 | Icon(
96 | iconName = "arrow_back",
97 | attrs = {
98 | classes(AppStyleSheet.topBarIcon)
99 | onClick { onClose() }
100 | }
101 | )
102 |
103 | H1(attrs = { classes(AppStyleSheet.topBarTitle) }) {
104 | Text(note.title)
105 | }
106 |
107 | Icon(
108 | iconName = "save",
109 | attrs = {
110 | classes(AppStyleSheet.topBarIcon)
111 | onClick { onSave() }
112 | }
113 | )
114 |
115 | if (note.id != Note.NEW_NOTE) {
116 | Icon(
117 | iconName = "delete",
118 | attrs = {
119 | classes(AppStyleSheet.topBarIcon)
120 | onClick { onDelete() }
121 | }
122 | )
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/common/src/commonComposeKmpMain/kotlin/com/devexperto/kotlinexpert/ui/screens/detail/Detail.kt:
--------------------------------------------------------------------------------
1 | package com.devexperto.kotlinexpert.ui.screens.detail
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.ArrowDropDown
9 | import androidx.compose.material.icons.filled.Close
10 | import androidx.compose.material.icons.filled.Delete
11 | import androidx.compose.material.icons.filled.Save
12 | import androidx.compose.material3.*
13 | import androidx.compose.runtime.*
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import com.devexperto.kotlinexpert.data.Note
17 | import com.devexperto.kotlinexpert.ui.screens.DropdownMenu
18 | import com.devexperto.kotlinexpert.ui.screens.DropdownMenuItem
19 | import com.devexperto.kotlinexpert.ui.viewmodels.DetailViewModel
20 |
21 | @OptIn(ExperimentalMaterial3Api::class)
22 | @Composable
23 | actual fun Detail(vm: DetailViewModel, onClose: () -> Unit) {
24 |
25 | val note = vm.state.note
26 |
27 | Scaffold(
28 | topBar = {
29 | TopBar(
30 | note = note,
31 | onClose = onClose,
32 | onSave = vm::save,
33 | onDelete = vm::delete
34 | )
35 | }
36 | ) { padding ->
37 | if (vm.state.saved) {
38 | onClose()
39 | }
40 |
41 | Box(modifier = Modifier.padding(padding)) {
42 | if (vm.state.loading) {
43 | CircularProgressIndicator()
44 | } else {
45 | Column(
46 | modifier = Modifier.padding(32.dp)
47 | ) {
48 | OutlinedTextField(
49 | value = note.title,
50 | onValueChange = { vm.update(note.copy(title = it)) },
51 | modifier = Modifier.fillMaxWidth(),
52 | label = { Text("Title") },
53 | maxLines = 1
54 | )
55 | TypeDropdown(
56 | value = note.type,
57 | onValueChange = { vm.update(note.copy(type = it)) },
58 | modifier = Modifier.fillMaxWidth()
59 | )
60 | OutlinedTextField(
61 | value = note.description,
62 | onValueChange = { vm.update(note.copy(description = it)) },
63 | label = { Text("Description") },
64 | modifier = Modifier.fillMaxWidth().weight(1f)
65 | )
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
72 | @OptIn(ExperimentalMaterial3Api::class)
73 | @Composable
74 | private fun TypeDropdown(value: Note.Type, onValueChange: (Note.Type) -> Unit, modifier: Modifier = Modifier) {
75 |
76 | var expanded by remember { mutableStateOf(false) }
77 |
78 | Box(modifier = modifier, propagateMinConstraints = true) {
79 | OutlinedTextField(
80 | value = value.toString(),
81 | onValueChange = {},
82 | readOnly = true,
83 | label = { Text("Type") },
84 | trailingIcon = {
85 | IconButton(onClick = { expanded = true }) {
86 | Icon(imageVector = Icons.Default.ArrowDropDown, contentDescription = "Show note types")
87 | }
88 | }
89 | )
90 | DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
91 | Note.Type.values().forEach {
92 | DropdownMenuItem(onClick = { onValueChange(it); expanded = false }) {
93 | Text(it.name)
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | @OptIn(ExperimentalMaterial3Api::class)
101 | @Composable
102 | private fun TopBar(note: Note, onClose: () -> Unit, onSave: () -> Unit, onDelete: () -> Unit) {
103 | TopAppBar(
104 | title = { Text(note.title) },
105 | navigationIcon = {
106 | IconButton(onClick = onClose) {
107 | Icon(imageVector = Icons.Default.Close, contentDescription = "Close Detail")
108 | }
109 | },
110 | actions = {
111 | IconButton(onClick = onSave) {
112 | Icon(imageVector = Icons.Default.Save, contentDescription = "Save Note")
113 | }
114 |
115 | if (note.id != Note.NEW_NOTE) {
116 | IconButton(onClick = onDelete) {
117 | Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete Note")
118 | }
119 | }
120 | }
121 | )
122 | }
123 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@jridgewell/gen-mapping@^0.3.0":
6 | version "0.3.3"
7 | resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz"
8 | integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
9 | dependencies:
10 | "@jridgewell/set-array" "^1.0.1"
11 | "@jridgewell/sourcemap-codec" "^1.4.10"
12 | "@jridgewell/trace-mapping" "^0.3.9"
13 |
14 | "@jridgewell/resolve-uri@3.1.0":
15 | version "3.1.0"
16 | resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz"
17 | integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
18 |
19 | "@jridgewell/set-array@^1.0.1":
20 | version "1.1.2"
21 | resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz"
22 | integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
23 |
24 | "@jridgewell/source-map@^0.3.2":
25 | version "0.3.3"
26 | resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz"
27 | integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==
28 | dependencies:
29 | "@jridgewell/gen-mapping" "^0.3.0"
30 | "@jridgewell/trace-mapping" "^0.3.9"
31 |
32 | "@jridgewell/sourcemap-codec@^1.4.10":
33 | version "1.4.15"
34 | resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
35 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
36 |
37 | "@jridgewell/sourcemap-codec@1.4.14":
38 | version "1.4.14"
39 | resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
40 | integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
41 |
42 | "@jridgewell/trace-mapping@^0.3.9":
43 | version "0.3.18"
44 | resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz"
45 | integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
46 | dependencies:
47 | "@jridgewell/resolve-uri" "3.1.0"
48 | "@jridgewell/sourcemap-codec" "1.4.14"
49 |
50 | "@rollup/plugin-terser@^0.4.1":
51 | version "0.4.1"
52 | resolved "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.1.tgz"
53 | integrity sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==
54 | dependencies:
55 | serialize-javascript "^6.0.0"
56 | smob "^0.0.6"
57 | terser "^5.15.1"
58 |
59 | acorn@^8.5.0:
60 | version "8.8.2"
61 | resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz"
62 | integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
63 |
64 | buffer-from@^1.0.0:
65 | version "1.1.2"
66 | resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
67 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
68 |
69 | commander@^2.20.0:
70 | version "2.20.3"
71 | resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
72 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
73 |
74 | randombytes@^2.1.0:
75 | version "2.1.0"
76 | resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz"
77 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
78 | dependencies:
79 | safe-buffer "^5.1.0"
80 |
81 | safe-buffer@^5.1.0:
82 | version "5.2.1"
83 | resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
84 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
85 |
86 | serialize-javascript@^6.0.0:
87 | version "6.0.1"
88 | resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz"
89 | integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
90 | dependencies:
91 | randombytes "^2.1.0"
92 |
93 | smob@^0.0.6:
94 | version "0.0.6"
95 | resolved "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz"
96 | integrity sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==
97 |
98 | source-map-support@~0.5.20:
99 | version "0.5.21"
100 | resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz"
101 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
102 | dependencies:
103 | buffer-from "^1.0.0"
104 | source-map "^0.6.0"
105 |
106 | source-map@^0.6.0:
107 | version "0.6.1"
108 | resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
109 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
110 |
111 | terser@^5.15.1:
112 | version "5.17.1"
113 | resolved "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz"
114 | integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==
115 | dependencies:
116 | "@jridgewell/source-map" "^0.3.2"
117 | acorn "^8.5.0"
118 | commander "^2.20.0"
119 | source-map-support "~0.5.20"
120 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Use "xargs" to parse quoted args.
209 | #
210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
211 | #
212 | # In Bash we could simply go:
213 | #
214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
215 | # set -- "${ARGS[@]}" "$@"
216 | #
217 | # but POSIX shell has neither arrays nor command substitution, so instead we
218 | # post-process each arg (as a line of input to sed) to backslash-escape any
219 | # character that might be a shell metacharacter, then use eval to reverse
220 | # that process (while maintaining the separation between arguments), and wrap
221 | # the whole thing up as a single "set" statement.
222 | #
223 | # This will of course break if any of these variables contains a newline or
224 | # an unmatched quote.
225 | #
226 |
227 | eval "set -- $(
228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
229 | xargs -n1 |
230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
231 | tr '\n' ' '
232 | )" '"$@"'
233 |
234 | exec "$JAVACMD" "$@"
235 |
--------------------------------------------------------------------------------
/ios/NotesAppIos.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A9292E4A2A5D73A1002F8E64 /* NotesAppIosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9292E492A5D73A1002F8E64 /* NotesAppIosApp.swift */; };
11 | A9292E4C2A5D73A1002F8E64 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9292E4B2A5D73A1002F8E64 /* ContentView.swift */; };
12 | A9292E4E2A5D73A2002F8E64 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9292E4D2A5D73A2002F8E64 /* Assets.xcassets */; };
13 | A9292E512A5D73A2002F8E64 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9292E502A5D73A2002F8E64 /* Preview Assets.xcassets */; };
14 | /* End PBXBuildFile section */
15 |
16 | /* Begin PBXFileReference section */
17 | A9292E462A5D73A1002F8E64 /* NotesAppIos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NotesAppIos.app; sourceTree = BUILT_PRODUCTS_DIR; };
18 | A9292E492A5D73A1002F8E64 /* NotesAppIosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesAppIosApp.swift; sourceTree = ""; };
19 | A9292E4B2A5D73A1002F8E64 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
20 | A9292E4D2A5D73A2002F8E64 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
21 | A9292E502A5D73A2002F8E64 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
22 | /* End PBXFileReference section */
23 |
24 | /* Begin PBXFrameworksBuildPhase section */
25 | A9292E432A5D73A1002F8E64 /* Frameworks */ = {
26 | isa = PBXFrameworksBuildPhase;
27 | buildActionMask = 2147483647;
28 | files = (
29 | );
30 | runOnlyForDeploymentPostprocessing = 0;
31 | };
32 | /* End PBXFrameworksBuildPhase section */
33 |
34 | /* Begin PBXGroup section */
35 | A9292E3D2A5D73A1002F8E64 = {
36 | isa = PBXGroup;
37 | children = (
38 | A9292E482A5D73A1002F8E64 /* NotesAppIos */,
39 | A9292E472A5D73A1002F8E64 /* Products */,
40 | );
41 | sourceTree = "";
42 | };
43 | A9292E472A5D73A1002F8E64 /* Products */ = {
44 | isa = PBXGroup;
45 | children = (
46 | A9292E462A5D73A1002F8E64 /* NotesAppIos.app */,
47 | );
48 | name = Products;
49 | sourceTree = "";
50 | };
51 | A9292E482A5D73A1002F8E64 /* NotesAppIos */ = {
52 | isa = PBXGroup;
53 | children = (
54 | A9292E492A5D73A1002F8E64 /* NotesAppIosApp.swift */,
55 | A9292E4B2A5D73A1002F8E64 /* ContentView.swift */,
56 | A9292E4D2A5D73A2002F8E64 /* Assets.xcassets */,
57 | A9292E4F2A5D73A2002F8E64 /* Preview Content */,
58 | );
59 | path = NotesAppIos;
60 | sourceTree = "";
61 | };
62 | A9292E4F2A5D73A2002F8E64 /* Preview Content */ = {
63 | isa = PBXGroup;
64 | children = (
65 | A9292E502A5D73A2002F8E64 /* Preview Assets.xcassets */,
66 | );
67 | path = "Preview Content";
68 | sourceTree = "";
69 | };
70 | /* End PBXGroup section */
71 |
72 | /* Begin PBXNativeTarget section */
73 | A9292E452A5D73A1002F8E64 /* NotesAppIos */ = {
74 | isa = PBXNativeTarget;
75 | buildConfigurationList = A9292E542A5D73A2002F8E64 /* Build configuration list for PBXNativeTarget "NotesAppIos" */;
76 | buildPhases = (
77 | A9292E572A5D7446002F8E64 /* Run Script */,
78 | A9292E422A5D73A1002F8E64 /* Sources */,
79 | A9292E432A5D73A1002F8E64 /* Frameworks */,
80 | A9292E442A5D73A1002F8E64 /* Resources */,
81 | );
82 | buildRules = (
83 | );
84 | dependencies = (
85 | );
86 | name = NotesAppIos;
87 | productName = NotesAppIos;
88 | productReference = A9292E462A5D73A1002F8E64 /* NotesAppIos.app */;
89 | productType = "com.apple.product-type.application";
90 | };
91 | /* End PBXNativeTarget section */
92 |
93 | /* Begin PBXProject section */
94 | A9292E3E2A5D73A1002F8E64 /* Project object */ = {
95 | isa = PBXProject;
96 | attributes = {
97 | BuildIndependentTargetsInParallel = 1;
98 | LastSwiftUpdateCheck = 1430;
99 | LastUpgradeCheck = 1430;
100 | TargetAttributes = {
101 | A9292E452A5D73A1002F8E64 = {
102 | CreatedOnToolsVersion = 14.3.1;
103 | };
104 | };
105 | };
106 | buildConfigurationList = A9292E412A5D73A1002F8E64 /* Build configuration list for PBXProject "NotesAppIos" */;
107 | compatibilityVersion = "Xcode 14.0";
108 | developmentRegion = en;
109 | hasScannedForEncodings = 0;
110 | knownRegions = (
111 | en,
112 | Base,
113 | );
114 | mainGroup = A9292E3D2A5D73A1002F8E64;
115 | productRefGroup = A9292E472A5D73A1002F8E64 /* Products */;
116 | projectDirPath = "";
117 | projectRoot = "";
118 | targets = (
119 | A9292E452A5D73A1002F8E64 /* NotesAppIos */,
120 | );
121 | };
122 | /* End PBXProject section */
123 |
124 | /* Begin PBXResourcesBuildPhase section */
125 | A9292E442A5D73A1002F8E64 /* Resources */ = {
126 | isa = PBXResourcesBuildPhase;
127 | buildActionMask = 2147483647;
128 | files = (
129 | A9292E512A5D73A2002F8E64 /* Preview Assets.xcassets in Resources */,
130 | A9292E4E2A5D73A2002F8E64 /* Assets.xcassets in Resources */,
131 | );
132 | runOnlyForDeploymentPostprocessing = 0;
133 | };
134 | /* End PBXResourcesBuildPhase section */
135 |
136 | /* Begin PBXShellScriptBuildPhase section */
137 | A9292E572A5D7446002F8E64 /* Run Script */ = {
138 | isa = PBXShellScriptBuildPhase;
139 | buildActionMask = 2147483647;
140 | files = (
141 | );
142 | inputFileListPaths = (
143 | );
144 | inputPaths = (
145 | );
146 | name = "Run Script";
147 | outputFileListPaths = (
148 | );
149 | outputPaths = (
150 | );
151 | runOnlyForDeploymentPostprocessing = 0;
152 | shellPath = /bin/sh;
153 | shellScript = "cd \"$SRCROOT/..\"\n./gradlew :common:embedAndSignAppleFrameworkForXcode\n";
154 | };
155 | /* End PBXShellScriptBuildPhase section */
156 |
157 | /* Begin PBXSourcesBuildPhase section */
158 | A9292E422A5D73A1002F8E64 /* Sources */ = {
159 | isa = PBXSourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | A9292E4C2A5D73A1002F8E64 /* ContentView.swift in Sources */,
163 | A9292E4A2A5D73A1002F8E64 /* NotesAppIosApp.swift in Sources */,
164 | );
165 | runOnlyForDeploymentPostprocessing = 0;
166 | };
167 | /* End PBXSourcesBuildPhase section */
168 |
169 | /* Begin XCBuildConfiguration section */
170 | A9292E522A5D73A2002F8E64 /* Debug */ = {
171 | isa = XCBuildConfiguration;
172 | buildSettings = {
173 | ALWAYS_SEARCH_USER_PATHS = NO;
174 | CLANG_ANALYZER_NONNULL = YES;
175 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
177 | CLANG_ENABLE_MODULES = YES;
178 | CLANG_ENABLE_OBJC_ARC = YES;
179 | CLANG_ENABLE_OBJC_WEAK = YES;
180 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
181 | CLANG_WARN_BOOL_CONVERSION = YES;
182 | CLANG_WARN_COMMA = YES;
183 | CLANG_WARN_CONSTANT_CONVERSION = YES;
184 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
186 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
187 | CLANG_WARN_EMPTY_BODY = YES;
188 | CLANG_WARN_ENUM_CONVERSION = YES;
189 | CLANG_WARN_INFINITE_RECURSION = YES;
190 | CLANG_WARN_INT_CONVERSION = YES;
191 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
192 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
193 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
194 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
195 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
196 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
197 | CLANG_WARN_STRICT_PROTOTYPES = YES;
198 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
199 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
200 | CLANG_WARN_UNREACHABLE_CODE = YES;
201 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
202 | COPY_PHASE_STRIP = NO;
203 | DEBUG_INFORMATION_FORMAT = dwarf;
204 | ENABLE_STRICT_OBJC_MSGSEND = YES;
205 | ENABLE_TESTABILITY = YES;
206 | GCC_C_LANGUAGE_STANDARD = gnu11;
207 | GCC_DYNAMIC_NO_PIC = NO;
208 | GCC_NO_COMMON_BLOCKS = YES;
209 | GCC_OPTIMIZATION_LEVEL = 0;
210 | GCC_PREPROCESSOR_DEFINITIONS = (
211 | "DEBUG=1",
212 | "$(inherited)",
213 | );
214 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
215 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
216 | GCC_WARN_UNDECLARED_SELECTOR = YES;
217 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
218 | GCC_WARN_UNUSED_FUNCTION = YES;
219 | GCC_WARN_UNUSED_VARIABLE = YES;
220 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
221 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
222 | MTL_FAST_MATH = YES;
223 | ONLY_ACTIVE_ARCH = YES;
224 | SDKROOT = iphoneos;
225 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
226 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
227 | };
228 | name = Debug;
229 | };
230 | A9292E532A5D73A2002F8E64 /* Release */ = {
231 | isa = XCBuildConfiguration;
232 | buildSettings = {
233 | ALWAYS_SEARCH_USER_PATHS = NO;
234 | CLANG_ANALYZER_NONNULL = YES;
235 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
237 | CLANG_ENABLE_MODULES = YES;
238 | CLANG_ENABLE_OBJC_ARC = YES;
239 | CLANG_ENABLE_OBJC_WEAK = YES;
240 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
241 | CLANG_WARN_BOOL_CONVERSION = YES;
242 | CLANG_WARN_COMMA = YES;
243 | CLANG_WARN_CONSTANT_CONVERSION = YES;
244 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
246 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
247 | CLANG_WARN_EMPTY_BODY = YES;
248 | CLANG_WARN_ENUM_CONVERSION = YES;
249 | CLANG_WARN_INFINITE_RECURSION = YES;
250 | CLANG_WARN_INT_CONVERSION = YES;
251 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
252 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
253 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
255 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
256 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
257 | CLANG_WARN_STRICT_PROTOTYPES = YES;
258 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
259 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
260 | CLANG_WARN_UNREACHABLE_CODE = YES;
261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
262 | COPY_PHASE_STRIP = NO;
263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
264 | ENABLE_NS_ASSERTIONS = NO;
265 | ENABLE_STRICT_OBJC_MSGSEND = YES;
266 | GCC_C_LANGUAGE_STANDARD = gnu11;
267 | GCC_NO_COMMON_BLOCKS = YES;
268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
270 | GCC_WARN_UNDECLARED_SELECTOR = YES;
271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
272 | GCC_WARN_UNUSED_FUNCTION = YES;
273 | GCC_WARN_UNUSED_VARIABLE = YES;
274 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
275 | MTL_ENABLE_DEBUG_INFO = NO;
276 | MTL_FAST_MATH = YES;
277 | SDKROOT = iphoneos;
278 | SWIFT_COMPILATION_MODE = wholemodule;
279 | SWIFT_OPTIMIZATION_LEVEL = "-O";
280 | VALIDATE_PRODUCT = YES;
281 | };
282 | name = Release;
283 | };
284 | A9292E552A5D73A2002F8E64 /* Debug */ = {
285 | isa = XCBuildConfiguration;
286 | buildSettings = {
287 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
288 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
289 | CODE_SIGN_STYLE = Automatic;
290 | CURRENT_PROJECT_VERSION = 1;
291 | DEVELOPMENT_ASSET_PATHS = "\"NotesAppIos/Preview Content\"";
292 | ENABLE_PREVIEWS = YES;
293 | FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../common/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
294 | GENERATE_INFOPLIST_FILE = YES;
295 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
296 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
297 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
298 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
299 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
300 | LD_RUNPATH_SEARCH_PATHS = (
301 | "$(inherited)",
302 | "@executable_path/Frameworks",
303 | );
304 | MARKETING_VERSION = 1.0;
305 | OTHER_LDFLAGS = (
306 | "$(inherited)",
307 | "-framework",
308 | common,
309 | );
310 | PRODUCT_BUNDLE_IDENTIFIER = com.devexperto.kotlinexpert.NotesAppIos;
311 | PRODUCT_NAME = "$(TARGET_NAME)";
312 | SWIFT_EMIT_LOC_STRINGS = YES;
313 | SWIFT_VERSION = 5.0;
314 | TARGETED_DEVICE_FAMILY = "1,2";
315 | };
316 | name = Debug;
317 | };
318 | A9292E562A5D73A2002F8E64 /* Release */ = {
319 | isa = XCBuildConfiguration;
320 | buildSettings = {
321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
322 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
323 | CODE_SIGN_STYLE = Automatic;
324 | CURRENT_PROJECT_VERSION = 1;
325 | DEVELOPMENT_ASSET_PATHS = "\"NotesAppIos/Preview Content\"";
326 | ENABLE_PREVIEWS = YES;
327 | FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../common/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
328 | GENERATE_INFOPLIST_FILE = YES;
329 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
330 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
331 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
332 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
333 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
334 | LD_RUNPATH_SEARCH_PATHS = (
335 | "$(inherited)",
336 | "@executable_path/Frameworks",
337 | );
338 | MARKETING_VERSION = 1.0;
339 | OTHER_LDFLAGS = (
340 | "$(inherited)",
341 | "-framework",
342 | common,
343 | );
344 | PRODUCT_BUNDLE_IDENTIFIER = com.devexperto.kotlinexpert.NotesAppIos;
345 | PRODUCT_NAME = "$(TARGET_NAME)";
346 | SWIFT_EMIT_LOC_STRINGS = YES;
347 | SWIFT_VERSION = 5.0;
348 | TARGETED_DEVICE_FAMILY = "1,2";
349 | };
350 | name = Release;
351 | };
352 | /* End XCBuildConfiguration section */
353 |
354 | /* Begin XCConfigurationList section */
355 | A9292E412A5D73A1002F8E64 /* Build configuration list for PBXProject "NotesAppIos" */ = {
356 | isa = XCConfigurationList;
357 | buildConfigurations = (
358 | A9292E522A5D73A2002F8E64 /* Debug */,
359 | A9292E532A5D73A2002F8E64 /* Release */,
360 | );
361 | defaultConfigurationIsVisible = 0;
362 | defaultConfigurationName = Release;
363 | };
364 | A9292E542A5D73A2002F8E64 /* Build configuration list for PBXNativeTarget "NotesAppIos" */ = {
365 | isa = XCConfigurationList;
366 | buildConfigurations = (
367 | A9292E552A5D73A2002F8E64 /* Debug */,
368 | A9292E562A5D73A2002F8E64 /* Release */,
369 | );
370 | defaultConfigurationIsVisible = 0;
371 | defaultConfigurationName = Release;
372 | };
373 | /* End XCConfigurationList section */
374 | };
375 | rootObject = A9292E3E2A5D73A1002F8E64 /* Project object */;
376 | }
377 |
--------------------------------------------------------------------------------