├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── iosApp
├── iosApp
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── Widgets
│ │ ├── IosWidgetFactory.swift
│ │ ├── IosWidgetSpace.swift
│ │ ├── IosWidgetText.swift
│ │ ├── IosWidgetTextInput.swift
│ │ ├── IosImageButton.swift
│ │ ├── IosStack.swift
│ │ ├── IosWidgetCard.swift
│ │ ├── IosWidgetImage.swift
│ │ └── IosWidgetButton.swift
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ └── SceneDelegate.swift
├── iosApp.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── iosApp.xcscheme
│ └── project.pbxproj
├── Podfile
├── iosApp.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Podfile.lock
├── androidApp
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ └── styles.xml
│ │ └── drawable
│ │ │ ├── like_clicked.xml
│ │ │ ├── dislike_clicked.xml
│ │ │ ├── arrow.xml
│ │ │ ├── like.xml
│ │ │ └── dislike.xml
│ │ ├── java
│ │ └── dev
│ │ │ └── icerock
│ │ │ └── redwoodapp
│ │ │ └── android
│ │ │ ├── SchemaWidgetFactories.kt
│ │ │ ├── widgets
│ │ │ ├── ComposeWidgetFactory.kt
│ │ │ ├── ComposeStack.kt
│ │ │ ├── ComposeCard.kt
│ │ │ ├── ComposeText.kt
│ │ │ ├── ComposeImage.kt
│ │ │ ├── ComposeButton.kt
│ │ │ ├── ComposeSpace.kt
│ │ │ ├── ComposeImageButton.kt
│ │ │ └── ComposeTextInput.kt
│ │ │ ├── types
│ │ │ ├── TextType.kt
│ │ │ └── ButtonType.kt
│ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
└── build.gradle.kts
├── shared
├── src
│ ├── commonMain
│ │ ├── resources
│ │ │ └── MR
│ │ │ │ ├── images
│ │ │ │ ├── like@1x.png
│ │ │ │ ├── like@2x.png
│ │ │ │ ├── like@3x.png
│ │ │ │ ├── line@1x.png
│ │ │ │ ├── line@2x.png
│ │ │ │ ├── line@3x.png
│ │ │ │ ├── dislike@1x.png
│ │ │ │ ├── dislike@2x.png
│ │ │ │ ├── dislike@3x.png
│ │ │ │ ├── settings@1x.png
│ │ │ │ ├── settings@2x.png
│ │ │ │ ├── settings@3x.png
│ │ │ │ ├── arrow_left@1x.png
│ │ │ │ ├── arrow_left@2x.png
│ │ │ │ ├── arrow_left@3x.png
│ │ │ │ ├── ava_preview@1x.png
│ │ │ │ ├── ava_preview@2x.png
│ │ │ │ ├── ava_preview@3x.png
│ │ │ │ ├── like_cliked@1x.png
│ │ │ │ ├── like_cliked@2x.png
│ │ │ │ ├── like_cliked@3x.png
│ │ │ │ ├── dislike_cliked@1x.png
│ │ │ │ ├── dislike_cliked@2x.png
│ │ │ │ ├── dislike_cliked@3x.png
│ │ │ │ ├── ic_favorite_menu@1x.png
│ │ │ │ ├── ic_favorite_menu@2x.png
│ │ │ │ └── ic_favorite_menu@3x.png
│ │ │ │ ├── colors
│ │ │ │ └── colors.xml
│ │ │ │ └── base
│ │ │ │ └── strings.xml
│ │ └── kotlin
│ │ │ └── dev
│ │ │ └── icerock
│ │ │ └── redwoodapp
│ │ │ ├── navigation
│ │ │ ├── NavigationHost.kt
│ │ │ ├── Navigator.kt
│ │ │ ├── TabNavigation.kt
│ │ │ └── DirectNavigation.kt
│ │ │ ├── screens
│ │ │ ├── entity
│ │ │ │ └── CardItem.kt
│ │ │ ├── DetailsScreen.kt
│ │ │ ├── LoginScreen.kt
│ │ │ ├── ProfileScreen.kt
│ │ │ └── PostsList.kt
│ │ │ ├── mainApp.kt
│ │ │ └── Placeholder.kt
│ ├── androidMain
│ │ └── kotlin
│ │ │ └── dev
│ │ │ └── icerock
│ │ │ └── redwoodapp
│ │ │ └── navigation
│ │ │ ├── NavigationHost.kt
│ │ │ ├── navigation.kt
│ │ │ ├── navigationTabs.kt
│ │ │ ├── TabNavigation.kt
│ │ │ └── FlatNavigation.kt
│ └── iosMain
│ │ └── kotlin
│ │ ├── dev
│ │ └── icerock
│ │ │ └── redwoodapp
│ │ │ └── navigation
│ │ │ ├── NavigationHost.kt
│ │ │ ├── ScreenSettingsImpl.kt
│ │ │ ├── TabNavigation.kt
│ │ │ ├── ext.kt
│ │ │ ├── navigationTabs.kt
│ │ │ ├── navigation.kt
│ │ │ └── FlatNavigation.kt
│ │ └── ComposeViewController.kt
└── build.gradle.kts
├── .gitignore
├── schema
├── entity
│ ├── src
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── dev
│ │ │ └── icerock
│ │ │ └── redwood
│ │ │ └── schema
│ │ │ ├── InputType.kt
│ │ │ ├── TextType.kt
│ │ │ └── ButtonType.kt
│ └── build.gradle.kts
├── build.gradle.kts
├── widget
│ └── build.gradle.kts
├── compose
│ └── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── dev
│ └── icerock
│ └── redwood
│ └── schema
│ └── schema.kt
├── gradle.properties
├── settings.gradle.kts
├── shared-ios
├── src
│ └── commonMain
│ │ └── kotlin
│ │ └── dev
│ │ └── icerock
│ │ └── redwoodapp
│ │ ├── RedwoodViewControllerDelegate.kt
│ │ └── ios
│ │ └── exposed.kt
├── build.gradle.kts
└── shared_ios.podspec
├── gradlew.bat
├── README.md
└── gradlew
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/like@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/like@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/like@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/like@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/like@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/like@3x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/line@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/line@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/line@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/line@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/line@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/line@3x.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | .DS_Store
5 | build
6 | captures
7 | .externalNativeBuild
8 | .cxx
9 | local.properties
10 | xcuserdata
11 | Pods
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/navigation/NavigationHost.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 |
4 | expect interface NavigationHost
5 |
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/dislike@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/dislike@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/dislike@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/dislike@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/dislike@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/dislike@3x.png
--------------------------------------------------------------------------------
/schema/entity/src/commonMain/kotlin/dev/icerock/redwood/schema/InputType.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwood.schema
2 |
3 | enum class InputType {
4 | Text,
5 | Password
6 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/settings@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/settings@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/settings@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/settings@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/settings@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/settings@3x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/arrow_left@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/arrow_left@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/arrow_left@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/arrow_left@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/arrow_left@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/arrow_left@3x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/ava_preview@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/ava_preview@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/ava_preview@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/ava_preview@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/ava_preview@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/ava_preview@3x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/like_cliked@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/like_cliked@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/like_cliked@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/like_cliked@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/like_cliked@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/like_cliked@3x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/dislike_cliked@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/dislike_cliked@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/dislike_cliked@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/dislike_cliked@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/dislike_cliked@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/dislike_cliked@3x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/ic_favorite_menu@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/ic_favorite_menu@1x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/ic_favorite_menu@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/ic_favorite_menu@2x.png
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/images/ic_favorite_menu@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icerockdev/redwood-sample/HEAD/shared/src/commonMain/resources/MR/images/ic_favorite_menu@3x.png
--------------------------------------------------------------------------------
/schema/entity/src/commonMain/kotlin/dev/icerock/redwood/schema/TextType.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwood.schema
2 |
3 | enum class TextType {
4 | Header,
5 | Primary,
6 | Secondary
7 | }
--------------------------------------------------------------------------------
/schema/entity/src/commonMain/kotlin/dev/icerock/redwood/schema/ButtonType.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwood.schema
2 |
3 | enum class ButtonType {
4 | Primary,
5 | Secondary,
6 | Action
7 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/navigation/Navigator.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | interface Navigator {
4 | fun navigate(uri: String)
5 |
6 | fun popBackStack()
7 | }
8 |
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/colors/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #9E0D39FF
4 | #4CD964FF
5 |
6 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iosApp/Podfile:
--------------------------------------------------------------------------------
1 |
2 | pre_install do
3 | `cd .. && ./gradlew generateDummyFramework`
4 | end
5 |
6 | target 'iosApp' do
7 | use_frameworks!
8 | platform :ios, '14.1'
9 | pod 'shared_ios', :path => '../shared-ios'
10 | end
11 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/screens/entity/CardItem.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.screens.entity
2 |
3 | data class CardItem(
4 | val data: String,
5 | val text: String,
6 | val isLike: Boolean,
7 | val onClick: ()->Unit
8 | )
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Dec 24 17:11:24 ALMT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/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 |
--------------------------------------------------------------------------------
/schema/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | }
4 |
5 | dependencies {
6 | api("app.cash.redwood:redwood-schema:0.1.0")
7 | api("app.cash.redwood:redwood-layout-schema:0.1.0")
8 | api("dev.icerock.moko:resources:0.20.1")
9 | api(project(":schema:entity"))
10 | }
11 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Gradle
2 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
3 |
4 | #Kotlin
5 | kotlin.code.style=official
6 |
7 | #Android
8 | android.useAndroidX=true
9 | android.nonTransitiveRClass=true
10 |
11 | #MPP
12 | kotlin.mpp.enableCInteropCommonization=true
13 | kotlin.mpp.stability.nowarn=true
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/dev/icerock/redwoodapp/navigation/NavigationHost.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import androidx.compose.runtime.Composable
4 | import app.cash.redwood.widget.Widget
5 |
6 | actual interface NavigationHost {
7 |
8 | @Composable
9 | fun Render(provider: Widget.Provider<@Composable () -> Unit>)
10 | }
11 |
--------------------------------------------------------------------------------
/iosApp/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - shared_ios (0.1.0)
3 |
4 | DEPENDENCIES:
5 | - shared_ios (from `../shared-ios`)
6 |
7 | EXTERNAL SOURCES:
8 | shared_ios:
9 | :path: "../shared-ios"
10 |
11 | SPEC CHECKSUMS:
12 | shared_ios: da642a7d9b1354fcb727dc5689810bc048ea1f7c
13 |
14 | PODFILE CHECKSUM: 781b9b0f72a0db3708da8d02a6b473a5a4b8da5f
15 |
16 | COCOAPODS: 1.11.3
17 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/dev/icerock/redwoodapp/navigation/NavigationHost.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import app.cash.redwood.widget.Widget
4 | import platform.UIKit.UIView
5 | import platform.UIKit.UIViewController
6 |
7 | actual interface NavigationHost {
8 |
9 | fun createViewController(provider: Widget.Provider): UIViewController
10 | }
11 |
--------------------------------------------------------------------------------
/schema/entity/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | }
4 |
5 | kotlin {
6 | iosArm64()
7 | iosX64()
8 | iosSimulatorArm64()
9 |
10 | jvm()
11 |
12 | sourceSets {
13 | val commonMain by getting {
14 | dependencies {
15 | api("dev.icerock.moko:resources:0.20.1")
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | gradlePluginPortal()
5 | mavenCentral()
6 | }
7 | }
8 |
9 | dependencyResolutionManagement {
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "redwood-sample"
17 |
18 | include(":androidApp")
19 | include(":shared", ":shared-ios")
20 | include(":schema", ":schema:widget", ":schema:compose", ":schema:entity")
21 |
--------------------------------------------------------------------------------
/shared/src/commonMain/resources/MR/base/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Login
6 | Password
7 | Login
8 |
9 | List
10 | Settings
11 |
12 | Logout
13 |
14 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/SchemaWidgetFactories.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android
2 |
3 | import app.cash.redwood.layout.widget.RedwoodLayoutWidgetFactory
4 | import dev.icerock.redwood.schema.widget.RedwoodAppSchemaWidgetFactory
5 | import dev.icerock.redwood.schema.widget.RedwoodAppSchemaWidgetFactoryProvider
6 |
7 | class SchemaWidgetFactories(
8 | override val RedwoodAppSchema: RedwoodAppSchemaWidgetFactory,
9 | override val RedwoodLayout: RedwoodLayoutWidgetFactory,
10 | ) : RedwoodAppSchemaWidgetFactoryProvider
--------------------------------------------------------------------------------
/schema/widget/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | id("app.cash.redwood.generator.widget")
4 | }
5 |
6 | kotlin {
7 | iosArm64()
8 | iosX64()
9 | iosSimulatorArm64()
10 |
11 | jvm()
12 |
13 | sourceSets {
14 | commonMain {
15 | dependencies {
16 | api("app.cash.redwood:redwood-layout-widget:0.1.0")
17 | api(project(":schema:entity"))
18 | }
19 | }
20 | }
21 | }
22 |
23 | redwoodSchema {
24 | source.set(project(":schema"))
25 | type.set("dev.icerock.redwood.schema.RedwoodAppSchema")
26 | }
27 |
--------------------------------------------------------------------------------
/schema/compose/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | id("app.cash.redwood.generator.compose")
4 | }
5 |
6 | //archivesBaseName = "schema-compose"
7 |
8 | kotlin {
9 | iosArm64()
10 | iosX64()
11 | iosSimulatorArm64()
12 |
13 | jvm()
14 |
15 | sourceSets {
16 | commonMain {
17 | dependencies {
18 | api("app.cash.redwood:redwood-layout-compose:0.1.0")
19 | api(project(":schema:widget"))
20 | }
21 | }
22 | }
23 | }
24 |
25 | redwoodSchema {
26 | source.set(project(":schema"))
27 | type.set("dev.icerock.redwood.schema.RedwoodAppSchema")
28 | }
29 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/dev/icerock/redwoodapp/navigation/ScreenSettingsImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.dev.icerock.redwoodapp.navigation
2 |
3 | import dev.icerock.moko.resources.desc.StringDesc
4 | import platform.UIKit.UINavigationController
5 | import dev.icerock.redwoodapp.navigation.ScreenSettings
6 |
7 | class ScreenSettingsImpl(): ScreenSettings {
8 |
9 | private var navController: UINavigationController? = null
10 |
11 | fun init(navController: UINavigationController){
12 | this.navController = navController
13 | }
14 |
15 | override fun setTitle(title: StringDesc) {
16 | navController?.navigationBar?.topItem?.title = title.localized()
17 | }
18 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/androidApp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeWidgetFactory.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import dev.icerock.redwood.schema.widget.RedwoodAppSchemaWidgetFactory
5 |
6 | object ComposeWidgetFactory : RedwoodAppSchemaWidgetFactory<@Composable () -> Unit> {
7 | override fun TextInput() = ComposeTextInput()
8 |
9 | override fun Text() = ComposeText()
10 |
11 | override fun Image() = ComposeImage()
12 |
13 | override fun Card() = ComposeCard()
14 |
15 | override fun Stack() = ComposeStack()
16 |
17 | override fun ImageButton() = ComposeImageButton()
18 |
19 | override fun Button() = ComposeButton()
20 |
21 | override fun Space() = ComposeSpace()
22 | }
23 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/drawable/like_clicked.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/navigation/TabNavigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import androidx.compose.runtime.Composable
4 | import dev.icerock.moko.resources.ImageResource
5 | import dev.icerock.moko.resources.desc.StringDesc
6 |
7 | interface TabNavigationDsl {
8 | fun registerScreen(
9 | uri: String,
10 | title: StringDesc? = null,
11 | icon: ImageResource? = null,
12 | screen: @Composable (Navigator) -> Unit
13 | )
14 |
15 | fun registerNavigation(
16 | uri: String,
17 | title: StringDesc? = null,
18 | icon: ImageResource? = null,
19 | childNavigation: (Navigator) -> NavigationHost
20 | )
21 | }
22 |
23 | expect fun navigationTabs(
24 | startDestination: String,
25 | block: TabNavigationDsl.() -> Unit
26 | ): NavigationHost
27 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/drawable/dislike_clicked.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/drawable/arrow.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/navigation/DirectNavigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import androidx.compose.runtime.Composable
4 | import dev.icerock.moko.resources.desc.StringDesc
5 |
6 | interface FlatNavigationDsl {
7 | fun registerScreen(
8 | uri: String,
9 | isToolbarVisible: Boolean = true,
10 | screen: @Composable (Navigator, Map, ScreenSettings) -> Unit
11 | )
12 |
13 | fun registerNavigation(
14 | uri: String,
15 | isToolbarVisible: Boolean = true,
16 | childNavigation: (Navigator, Map, ScreenSettings) -> NavigationHost
17 | )
18 | }
19 |
20 | expect fun navigation(
21 | startDestination: String,
22 | block: FlatNavigationDsl.() -> Unit
23 | ): NavigationHost
24 |
25 | interface ScreenSettings {
26 | fun setTitle(title: StringDesc)
27 | }
--------------------------------------------------------------------------------
/androidApp/src/main/res/drawable/like.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosWidgetFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosWidgetFactory.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosWidgetFactory: WidgetFactory {
13 | func Space() -> Space {
14 | IosWidgetSpace()
15 | }
16 |
17 |
18 | func Button() -> Button {
19 | IosWidgetButton()
20 | }
21 |
22 | func Card() -> Card {
23 | IosWidgetCard()
24 | }
25 |
26 | func ImageButton() -> ImageButton {
27 | IosImageButton()
28 | }
29 |
30 | func Stack() -> Stack {
31 | IosStack()
32 | }
33 |
34 | func Image() -> Image {
35 | IosWidgetImage()
36 | }
37 |
38 | func Text() -> Text {
39 | IosWidgetText()
40 | }
41 |
42 | func TextInput() -> TextInput {
43 | IosWidgetTextInput()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeStack.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import app.cash.redwood.LayoutModifier
10 | import app.cash.redwood.widget.compose.ComposeWidgetChildren
11 |
12 | class ComposeStack: dev.icerock.redwood.schema.widget.Stack<@Composable () -> Unit> {
13 | override var layoutModifiers: LayoutModifier = LayoutModifier
14 | override val value = @Composable {
15 | Box(modifier = Modifier.fillMaxSize()) {
16 | child1.render()
17 | Column(modifier = Modifier.align(Alignment.BottomEnd)) {
18 | child2.render()
19 | }
20 | }
21 | }
22 | override val child1 = ComposeWidgetChildren()
23 | override val child2 = ComposeWidgetChildren()
24 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosWidgetSpace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosWidgetSpace.swift
3 | // iosApp
4 | //
5 | // Created by alobynya on 27.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosWidgetSpace: Space {
13 |
14 | private let root: UIView = {
15 | let container = UIView()
16 |
17 | container.translatesAutoresizingMaskIntoConstraints = false
18 | return container;
19 | }()
20 |
21 | func background(background: GraphicsColor) {
22 | root.backgroundColor = UIColor.clear
23 | }
24 |
25 | func height(height_ height: Int32) {
26 | root.heightAnchor.constraint(equalToConstant: CGFloat(height)).isActive = true
27 | }
28 |
29 | func width(width_ width: Int32) {
30 | root.widthAnchor.constraint(equalToConstant: CGFloat(width)).isActive = true
31 | }
32 |
33 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
34 |
35 | var value: Any { root }
36 |
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/types/TextType.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.types
2 |
3 | import androidx.compose.material.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.text.style.TextOverflow
7 | import androidx.compose.ui.unit.sp
8 |
9 | @Composable
10 | fun PrimaryText(text: String, isSingleLine: Boolean) {
11 | Text(
12 | text = text,
13 | color = Color.Black,
14 | overflow = TextOverflow.Ellipsis,
15 | fontSize = 14.sp,
16 | maxLines = if (isSingleLine) 1 else Int.MAX_VALUE
17 | )
18 | }
19 |
20 | @Composable
21 | fun SecondaryText(text: String, isSingleLine: Boolean) {
22 | Text(
23 | text = text,
24 | color = Color.Gray,
25 | fontSize = 12.sp,
26 | maxLines = if (isSingleLine) 1 else Int.MAX_VALUE
27 | )
28 | }
29 |
30 | @Composable
31 | fun HeaderText(text: String, isSingleLine: Boolean) {
32 | Text(
33 | text = text,
34 | color = Color.Black,
35 | fontSize = 25.sp,
36 | maxLines = if (isSingleLine) 1 else Int.MAX_VALUE
37 | )
38 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosWidgetText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosWidgetText.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosWidgetText: WidgetText {
13 |
14 | private let root: UITextView = {
15 | let view = UITextView()
16 | view.translatesAutoresizingMaskIntoConstraints = false
17 | view.isUserInteractionEnabled = false
18 | view.backgroundColor = UIColor.clear
19 | return view
20 | }()
21 |
22 | func isSingleLine(isSingleLine: Bool) {
23 | // TODO
24 | }
25 |
26 | func text(text_ text: String) {
27 | root.text = text
28 | }
29 |
30 | func textType(textType: EntityTextType?) {
31 | if(textType == EntityTextType.header){
32 | root.font = .boldSystemFont(ofSize: 25)
33 | }
34 | if(textType == EntityTextType.primary){
35 | root.font = .systemFont(ofSize: 12)
36 | }
37 | }
38 |
39 |
40 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
41 |
42 | var value: Any { root }
43 | }
44 |
--------------------------------------------------------------------------------
/shared-ios/src/commonMain/kotlin/dev/icerock/redwoodapp/RedwoodViewControllerDelegate.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp
2 |
3 | import platform.UIKit.UIView
4 | import dev.icerock.redwood.schema.widget.RedwoodAppSchemaWidgetFactory
5 | import dev.icerock.redwood.schema.widget.Button
6 | import dev.icerock.redwood.schema.widget.Card
7 | import dev.icerock.redwood.schema.widget.Image
8 | import dev.icerock.redwood.schema.widget.ImageButton
9 | import dev.icerock.redwood.schema.widget.Stack
10 | import dev.icerock.redwood.schema.widget.Text
11 | import dev.icerock.redwood.schema.widget.TextInput
12 |
13 | interface WidgetFactory : RedwoodAppSchemaWidgetFactory
14 |
15 | @Suppress("unused") // Called from Swift.
16 | interface WidgetButton : Button
17 |
18 | @Suppress("unused") // Called from Swift.
19 | interface WidgetCard : Card
20 |
21 | @Suppress("unused") // Called from Swift.
22 | interface WidgetImageButton : ImageButton
23 |
24 | @Suppress("unused") // Called from Swift.
25 | interface WidgetStack : Stack
26 |
27 | @Suppress("unused") // Called from Swift.
28 | interface WidgetImage : Image
29 |
30 | @Suppress("unused") // Called from Swift.
31 | interface WidgetText : Text
32 |
33 | @Suppress("unused") // Called from Swift.
34 | interface WidgetTextInput : TextInput
35 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/screens/DetailsScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.screens
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import app.cash.redwood.LayoutModifier
6 | import app.cash.redwood.layout.api.Overflow
7 | import app.cash.redwood.layout.api.Padding
8 | import app.cash.redwood.layout.compose.Column
9 | import dev.icerock.moko.resources.desc.desc
10 | import dev.icerock.redwood.schema.TextType
11 | import dev.icerock.redwood.schema.compose.Text
12 | import dev.icerock.redwoodapp.LONG_TEXT
13 | import dev.icerock.redwoodapp.navigation.Navigator
14 | import dev.icerock.redwoodapp.navigation.ScreenSettings
15 |
16 | @Composable
17 | fun DetailsScreen(
18 | navigator: Navigator,
19 | date: String,
20 | details: String,
21 | screenSettings: ScreenSettings
22 | ) {
23 | LaunchedEffect(screenSettings){
24 | screenSettings.setTitle(date.desc())
25 | }
26 | Column(
27 | overflow = Overflow.Scroll
28 | ) {
29 | Text(date, layoutModifier = LayoutModifier.padding(Padding(16)), textType = TextType.Header)
30 | Text(details, layoutModifier = LayoutModifier.padding(Padding(horizontal = 16)))
31 | Text(LONG_TEXT, layoutModifier = LayoutModifier.padding(Padding(16)))
32 | }
33 | }
34 |
35 |
36 |
--------------------------------------------------------------------------------
/shared-ios/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.multiplatform")
3 | kotlin("native.cocoapods")
4 | id("app.cash.redwood")
5 | id("dev.icerock.mobile.multiplatform-resources")
6 | }
7 |
8 | version = "0.1.0"
9 |
10 | kotlin {
11 | ios()
12 | iosSimulatorArm64()
13 |
14 | cocoapods {
15 | homepage = "https://github.com/icerockdev"
16 | summary = "redwood test"
17 |
18 | framework {
19 | isStatic = false
20 |
21 | export(project(":schema:widget"))
22 | export("dev.icerock.moko:resources:0.20.1")
23 | }
24 | }
25 |
26 | sourceSets {
27 | val commonMain by getting {
28 | dependencies {
29 | api(project(":schema:widget"))
30 | api(project(":shared"))
31 | api("dev.icerock.moko:resources:0.20.1")
32 | implementation("app.cash.redwood:redwood-layout-uiview:0.1.0")
33 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
34 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
35 | }
36 | }
37 | getByName("iosSimulatorArm64Main").dependsOn(getByName("iosMain"))
38 | }
39 | }
40 |
41 | multiplatformResources {
42 | multiplatformResourcesPackage = "org.example.shared_ios" // required
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.material.MaterialTheme
8 | import androidx.compose.material.Surface
9 | import androidx.compose.ui.Modifier
10 | import app.cash.redwood.layout.composeui.ComposeUiRedwoodLayoutWidgetFactory
11 | import dev.icerock.redwoodapp.android.widgets.ComposeWidgetFactory
12 | import dev.icerock.redwoodapp.mainApp
13 | import dev.icerock.redwoodapp.navigation.NavigationHost
14 |
15 | class MainActivity : ComponentActivity() {
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | val factories = SchemaWidgetFactories(
19 | RedwoodAppSchema = ComposeWidgetFactory,
20 | RedwoodLayout = ComposeUiRedwoodLayoutWidgetFactory(),
21 | )
22 |
23 | val app: NavigationHost = mainApp()
24 |
25 | setContent {
26 | MaterialTheme {
27 | Surface(
28 | modifier = Modifier.fillMaxSize(),
29 | color = MaterialTheme.colors.background
30 | ) {
31 | app.Render(factories)
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeCard.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.shape.RoundedCornerShape
5 | import androidx.compose.material.Card
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.runtime.mutableStateOf
9 | import androidx.compose.runtime.setValue
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.unit.dp
13 | import app.cash.redwood.LayoutModifier
14 | import app.cash.redwood.widget.compose.ComposeWidgetChildren
15 | import dev.icerock.redwood.schema.widget.Card
16 |
17 | class ComposeCard : Card<@Composable () -> Unit> {
18 | override var layoutModifiers: LayoutModifier = LayoutModifier
19 | private var _onClick: () -> Unit by mutableStateOf({})
20 |
21 | override val value = @Composable {
22 | Card(
23 | modifier = Modifier.clickable {
24 | _onClick()
25 | },
26 | backgroundColor = Color(0xFFF2F2F2),
27 | shape = RoundedCornerShape(8.dp),
28 | elevation = 0.dp
29 | ) {
30 | child.render()
31 | }
32 | }
33 |
34 | override val child = ComposeWidgetChildren()
35 | override fun onClick(onClick: (() -> Unit)?) {
36 | _onClick = onClick ?: {}
37 | }
38 | }
--------------------------------------------------------------------------------
/androidApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | kotlin("android")
4 | id("app.cash.redwood")
5 | }
6 |
7 | android {
8 | namespace = "dev.icerock.redwoodapp.android"
9 | compileSdk = 33
10 | defaultConfig {
11 | applicationId = "dev.icerock.redwoodapp.android"
12 | minSdk = 24
13 | targetSdk = 33
14 | versionCode = 1
15 | versionName = "1.0"
16 | }
17 | buildFeatures {
18 | compose = true
19 | }
20 | composeOptions {
21 | kotlinCompilerExtensionVersion = "1.4.0-alpha01"
22 | }
23 | packagingOptions {
24 | resources {
25 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
26 | }
27 | }
28 | buildTypes {
29 | getByName("release") {
30 | isMinifyEnabled = false
31 | }
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation("androidx.compose.ui:ui:1.3.2")
37 | implementation("androidx.compose.ui:ui-tooling:1.3.2")
38 | implementation("androidx.compose.ui:ui-tooling-preview:1.3.2")
39 | implementation("androidx.compose.foundation:foundation:1.3.1")
40 | implementation("androidx.compose.material:material:1.3.1")
41 | implementation("androidx.activity:activity-compose:1.6.1")
42 |
43 | implementation(project(":shared"))
44 | implementation("app.cash.redwood:redwood-layout-composeui:0.1.0")
45 | implementation("app.cash.redwood:redwood-widget-compose-jvm:0.1.0")
46 |
47 | implementation("com.github.skydoves:landscapist-coil:1.4.8")
48 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 24.12.2022.
6 | // Copyright © 2022 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @main
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/drawable/dislike.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/shared-ios/src/commonMain/kotlin/dev/icerock/redwoodapp/ios/exposed.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.ios
2 |
3 | import app.cash.redwood.LayoutModifier
4 | import app.cash.redwood.layout.uiview.UIViewRedwoodLayoutWidgetFactory
5 | import app.cash.redwood.widget.UIViewChildren
6 | import kotlinx.cinterop.readValue
7 | import platform.CoreGraphics.CGRectZero
8 | import platform.UIKit.UIAccessibilityIdentificationProtocol
9 | import platform.UIKit.UIView
10 | import dev.icerock.redwood.schema.widget.RedwoodAppSchemaWidgetFactories
11 | import dev.icerock.redwood.schema.widget.RedwoodAppSchemaWidgetFactory
12 |
13 | fun layoutModifier(): LayoutModifier = LayoutModifier
14 |
15 | fun createViewChildren(parent: UIView): UIViewChildren = UIViewChildren(parent)
16 |
17 | fun createViewChildrenListener(
18 | parent: UIView,
19 | insert: (UIView, Int) -> Unit
20 | ): UIViewChildren = UIViewChildren(parent, insert = insert)
21 |
22 | fun mainApp() = dev.icerock.redwoodapp.mainApp()
23 |
24 | class UIViewWithIdentifier : UIView(frame = CGRectZero.readValue()),
25 | UIAccessibilityIdentificationProtocol {
26 | private var _accessibilityIdentifier: String? = null
27 |
28 | override fun accessibilityIdentifier(): String? {
29 | return _accessibilityIdentifier
30 | }
31 |
32 | override fun setAccessibilityIdentifier(accessibilityIdentifier: String?) {
33 | _accessibilityIdentifier = accessibilityIdentifier
34 | }
35 | }
36 |
37 | fun widgetProvider(
38 | widgetFactory: RedwoodAppSchemaWidgetFactory
39 | ): RedwoodAppSchemaWidgetFactories = RedwoodAppSchemaWidgetFactories(
40 | RedwoodAppSchema = widgetFactory,
41 | RedwoodLayout = UIViewRedwoodLayoutWidgetFactory()
42 | )
43 |
--------------------------------------------------------------------------------
/shared-ios/shared_ios.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'shared_ios'
3 | spec.version = '0.1.0'
4 | spec.homepage = 'https://github.com/icerockdev'
5 | spec.source = { :http=> ''}
6 | spec.authors = ''
7 | spec.license = ''
8 | spec.summary = 'redwood test'
9 | spec.vendored_frameworks = 'build/cocoapods/framework/shared_ios.framework'
10 | spec.libraries = 'c++'
11 |
12 |
13 |
14 | spec.pod_target_xcconfig = {
15 | 'KOTLIN_PROJECT_PATH' => ':shared-ios',
16 | 'PRODUCT_MODULE_NAME' => 'shared_ios',
17 | }
18 |
19 | spec.script_phases = [
20 | {
21 | :name => 'Build shared_ios',
22 | :execution_position => :before_compile,
23 | :shell_path => '/bin/sh',
24 | :script => <<-SCRIPT
25 | if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
26 | echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
27 | exit 0
28 | fi
29 | set -ev
30 | REPO_ROOT="$PODS_TARGET_SRCROOT"
31 | "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
32 | -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
33 | -Pkotlin.native.cocoapods.archs="$ARCHS" \
34 | -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
35 | SCRIPT
36 | }
37 | ]
38 |
39 | end
--------------------------------------------------------------------------------
/iosApp/iosApp/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosWidgetTextInput.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosWidgetTextInput.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosWidgetTextInput: WidgetTextInput {
13 |
14 | private let root: UITextField = {
15 | let view = FillUITextField()
16 | view.translatesAutoresizingMaskIntoConstraints = false
17 | view.borderStyle = .roundedRect
18 | return view
19 | }()
20 |
21 | func state(state: String) {
22 | root.text = state
23 | }
24 |
25 | func hint(hint: StringDesc) {
26 | root.placeholder = hint.localized()
27 | }
28 |
29 | func onChange(onChange: ((String) -> Void)? = nil) {
30 | let identifier = UIAction.Identifier("TextInputBinding.onTextChanged")
31 | let action = UIAction(
32 | identifier: identifier,
33 | handler: { [unowned self] _ in onChange?(self.root.text ?? "") }
34 | )
35 |
36 | root.removeAction(identifiedBy: identifier, for: .editingChanged)
37 | root.addAction(action, for: .editingChanged)
38 | }
39 |
40 | func inputType(inputType: EntityInputType?) {
41 | if(inputType == EntityInputType.password){
42 | root.isSecureTextEntry = true
43 | }
44 | }
45 |
46 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
47 |
48 | var value: Any { root }
49 | }
50 |
51 | class FillUITextField : UITextField{
52 | override func sizeThatFits(_ size: CGSize) -> CGSize {
53 | let originalSize = super.sizeThatFits(size)
54 | return CGSize(width: size.width, height: originalSize.height)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/dev/icerock/redwoodapp/navigation/TabNavigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import app.cash.redwood.widget.Widget
4 | import platform.UIKit.UIColor
5 | import platform.UIKit.UITabBarController
6 | import platform.UIKit.UIView
7 | import platform.UIKit.UIViewController
8 | import platform.UIKit.backgroundColor
9 | import platform.UIKit.tabBarItem
10 |
11 | data class TabNavigation(
12 | val startDestination: String,
13 | val routes: Map
14 | ) : NavigationHost {
15 |
16 | override fun createViewController(provider: Widget.Provider): UIViewController {
17 | @Suppress("JoinDeclarationAndAssignment")
18 | lateinit var tabBarController: UITabBarController
19 | val navigator: Navigator = object : Navigator {
20 | override fun navigate(uri: String) {
21 | val idx: Int = routes.keys.indexOfFirst { it == uri }
22 | tabBarController.setSelectedIndex(idx.toULong())
23 | }
24 |
25 | override fun popBackStack() {
26 | // do nothing now
27 | }
28 | }
29 | val tabsViewControllers: List = routes.map { (key, value) ->
30 | val vc: UIViewController = value.createViewController(provider, navigator)
31 | vc.tabBarItem.title = value.title?.localized()
32 | vc.tabBarItem.image = value.icon?.toUIImage()
33 | vc
34 | }
35 | tabBarController = UITabBarController()
36 | tabBarController.setViewControllers(tabsViewControllers, animated = false)
37 | tabBarController.tabBar.barTintColor = UIColor.blackColor
38 | tabBarController.tabBar.backgroundColor = UIColor.whiteColor
39 | return tabBarController
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeText.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import app.cash.redwood.LayoutModifier
8 | import dev.icerock.redwood.schema.TextType
9 | import dev.icerock.redwood.schema.widget.Text
10 | import dev.icerock.redwoodapp.android.types.HeaderText
11 | import dev.icerock.redwoodapp.android.types.PrimaryText
12 | import dev.icerock.redwoodapp.android.types.SecondaryText
13 |
14 | class ComposeText : Text<@Composable () -> Unit> {
15 | private var _textState: String by mutableStateOf("")
16 | private var _isSingleLine: Boolean by mutableStateOf(false)
17 | private var _textType: TextType by mutableStateOf(TextType.Primary)
18 |
19 | override var layoutModifiers: LayoutModifier = LayoutModifier
20 | override val value = @Composable {
21 | when (_textType) {
22 | TextType.Primary -> PrimaryText(
23 | text = _textState,
24 | isSingleLine = _isSingleLine
25 | )
26 | TextType.Secondary -> SecondaryText(
27 | text = _textState,
28 | isSingleLine = _isSingleLine
29 | )
30 | TextType.Header -> HeaderText(
31 | text = _textState,
32 | isSingleLine = _isSingleLine
33 | )
34 | }
35 | }
36 |
37 | override fun text(text: String) {
38 | _textState = text
39 | }
40 |
41 | override fun isSingleLine(isSingleLine: Boolean) {
42 | _isSingleLine = isSingleLine
43 | }
44 |
45 | override fun textType(textType: TextType?) {
46 | _textType = textType ?: TextType.Primary
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/dev/icerock/redwoodapp/navigation/navigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import androidx.compose.runtime.Composable
4 | import app.cash.redwood.compose.RedwoodContent
5 | import app.cash.redwood.widget.Widget
6 |
7 | typealias FlatRouteData = @Composable (Widget.Provider<() -> Unit>, Navigator, Map) -> Unit
8 |
9 | actual fun navigation(
10 | startDestination: String,
11 | block: FlatNavigationDsl.() -> Unit
12 | ): NavigationHost {
13 | val routes: MutableMap = mutableMapOf()
14 | val navBarVisibility: MutableMap = mutableMapOf()
15 | val screenSettings = ScreenSettingsImpl()
16 | val dsl = object : FlatNavigationDsl {
17 | override fun registerScreen(
18 | uri: String,
19 | isToolbarVisible: Boolean,
20 | screen: @Composable (Navigator, Map, ScreenSettings) -> Unit
21 | ) {
22 | routes[uri] = { provider, navigator, args ->
23 | RedwoodContent(provider = provider) {
24 | screen(navigator, args, screenSettings)
25 | }
26 | }
27 | navBarVisibility[uri] = isToolbarVisible
28 | }
29 |
30 | override fun registerNavigation(
31 | uri: String,
32 | isToolbarVisible: Boolean,
33 | childNavigation: (Navigator, Map, ScreenSettings) -> NavigationHost
34 | ) {
35 | routes[uri] = @Composable { provider, navigator, args ->
36 | childNavigation(navigator, args, screenSettings).Render(provider)
37 | }
38 | navBarVisibility[uri] = isToolbarVisible
39 | }
40 | }
41 | dsl.block()
42 | return FlatNavigation(startDestination, routes, navBarVisibility, screenSettings)
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/dev/icerock/redwoodapp/navigation/ext.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.dev.icerock.redwoodapp.navigation
2 |
3 | fun String.getParams(placeholder:String) = this.getParams(placeholder,placeholder.getStableParts())
4 |
5 | fun String.getStableParts(): List {
6 | if(contains('{').not()) return listOf(this)
7 | return split('{').map { it.split('}').last() }
8 | }
9 |
10 | fun String.isPlaceholderOf(value: String): Boolean {
11 | return value.checkPlaceholderPart(getStableParts())
12 | }
13 |
14 | fun String.checkPlaceholderPart(stablePart: List): Boolean {
15 | if (isEmpty() && stablePart.isEmpty()) return true
16 | if (stablePart.size == 1 && stablePart.get(0).isEmpty()) return true
17 | if (stablePart.size == 1 && this.equals(stablePart.get(0))) return true
18 | return contains(stablePart.get(0)) && substringAfter(stablePart.get(0)).checkPlaceholderPart(
19 | stablePart.subList(
20 | 1,
21 | stablePart.size
22 | )
23 | )
24 | }
25 |
26 | fun String.getParams(placeholder: String, stablePart: List): Map {
27 | if (isEmpty() && stablePart.isEmpty()) return mapOf()
28 | if (stablePart.size == 1 && this.equals(stablePart.get(0))) return mapOf()
29 | val mapParams = mutableMapOf()
30 | val currentStablePart = stablePart.get(0)
31 | if(currentStablePart.isEmpty()){
32 | mapParams[placeholder.removePrefix("{").removeSuffix("}")] = this
33 | return mapParams
34 | }
35 | mapParams[placeholder.substringBefore(currentStablePart)
36 | .removePrefix("{")
37 | .removeSuffix("}")] = substringBefore(currentStablePart)
38 | val otherParams = substringAfter(currentStablePart).getParams(
39 | placeholder.substringAfter(currentStablePart),
40 | stablePart.subList(1, stablePart.size)
41 | )
42 | mapParams.putAll(otherParams)
43 | return mapParams.filterKeys { it.isNotEmpty() }
44 | }
45 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosImageButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosImageButton.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosImageButton: WidgetImageButton {
13 |
14 | private let root: UIButton = {
15 | var configuration = UIButton.Configuration.filled()
16 | configuration.imagePadding = 10
17 | configuration.baseBackgroundColor = UIColor.clear
18 |
19 | let view = UIButton(configuration: configuration)
20 | view.translatesAutoresizingMaskIntoConstraints = true
21 | view.backgroundColor = UIColor.clear
22 | view.setTitleColor(.black, for: .normal)
23 | view.heightAnchor.constraint(equalToConstant: 60).isActive = true
24 | return view
25 |
26 | }()
27 |
28 |
29 | func icon(icon: ImageResource?) {
30 | root.setImage(icon?.toUIImage(), for: .normal)
31 | }
32 |
33 | func isClicked(isClicked: Bool) {
34 | if(isClicked){
35 | root.setTitleColor(mainColor, for: .normal)
36 |
37 | } else {
38 | root.setTitleColor(.gray, for: .normal)
39 |
40 | }
41 | }
42 |
43 |
44 | func text(text: StringDesc) {
45 | root.setTitle(text.localized(), for: .normal)
46 | root.titleLabel?.font = .italicSystemFont(ofSize: 12)
47 | }
48 |
49 | func onClick(onClick: (() -> Void)? = nil) {
50 | let identifier = UIAction.Identifier("ButtonBinding.onPressed")
51 | let action = UIAction(
52 | identifier: identifier,
53 | handler: { _ in onClick?() }
54 | )
55 |
56 | root.removeAction(identifiedBy: identifier, for: .touchUpInside)
57 | root.addAction(action, for: .touchUpInside)
58 | }
59 |
60 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
61 |
62 | var value: Any {root}
63 | }
64 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/dev/icerock/redwoodapp/navigation/navigationTabs.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("TabNavigationKt")
2 |
3 | package dev.icerock.redwoodapp.navigation
4 |
5 | import androidx.compose.runtime.Composable
6 | import app.cash.redwood.compose.RedwoodContent
7 | import app.cash.redwood.widget.Widget
8 | import dev.icerock.moko.resources.ImageResource
9 | import dev.icerock.moko.resources.desc.StringDesc
10 |
11 | data class TabRouteData(
12 | val title: StringDesc?,
13 | val icon: ImageResource?,
14 | val render: @Composable (Widget.Provider<() -> Unit>, Navigator) -> Unit
15 | )
16 |
17 | actual fun navigationTabs(
18 | startDestination: String,
19 | block: TabNavigationDsl.() -> Unit
20 | ): NavigationHost {
21 | val routes: MutableMap = mutableMapOf()
22 | val dsl = object : TabNavigationDsl {
23 | override fun registerScreen(
24 | uri: String,
25 | title: StringDesc?,
26 | icon: ImageResource?,
27 | screen: @Composable (Navigator) -> Unit
28 | ) {
29 | routes[uri] = TabRouteData(
30 | title = title,
31 | icon = icon,
32 | render = @Composable { provider, navigator ->
33 | RedwoodContent(provider) {
34 | screen(navigator)
35 | }
36 | }
37 | )
38 | }
39 |
40 | override fun registerNavigation(
41 | uri: String,
42 | title: StringDesc?,
43 | icon: ImageResource?,
44 | childNavigation: (Navigator) -> NavigationHost
45 | ) {
46 | routes[uri] = TabRouteData(
47 | title = title,
48 | icon = icon,
49 | render = @Composable { provider, navigator ->
50 | childNavigation(navigator).Render(provider)
51 | }
52 | )
53 | }
54 | }
55 | dsl.block()
56 | return TabNavigation(startDestination, routes)
57 | }
58 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/dev/icerock/redwoodapp/navigation/navigationTabs.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import androidx.compose.runtime.Composable
4 | import app.cash.redwood.widget.Widget
5 | import dev.icerock.moko.resources.ImageResource
6 | import dev.icerock.moko.resources.desc.StringDesc
7 | import platform.UIKit.UIView
8 | import platform.UIKit.UIViewController
9 | import dev.icerock.redwoodapp.ComposeViewController
10 |
11 | data class TabRouteData(
12 | val title: StringDesc?,
13 | val icon: ImageResource?,
14 | val createViewController: (Widget.Provider, Navigator) -> UIViewController
15 | )
16 |
17 | actual fun navigationTabs(
18 | startDestination: String,
19 | block: TabNavigationDsl.() -> Unit
20 | ): NavigationHost {
21 | val routes: MutableMap = mutableMapOf()
22 | val dsl = object : TabNavigationDsl {
23 | override fun registerScreen(
24 | uri: String,
25 | title: StringDesc?,
26 | icon: ImageResource?,
27 | screen: @Composable (Navigator) -> Unit
28 | ) {
29 | routes[uri] = TabRouteData(
30 | title = title,
31 | icon = icon,
32 | createViewController = { provider, navigator ->
33 | ComposeViewController(provider, false) @Composable {
34 | screen(navigator)
35 | }
36 | }
37 | )
38 | }
39 |
40 | override fun registerNavigation(
41 | uri: String,
42 | title: StringDesc?,
43 | icon: ImageResource?,
44 | childNavigation: (Navigator) -> NavigationHost
45 | ) {
46 | routes[uri] = TabRouteData(
47 | title = title,
48 | icon = icon,
49 | createViewController = { provider, navigator ->
50 | childNavigation(navigator).createViewController(provider)
51 | }
52 | )
53 | }
54 | }
55 | dsl.block()
56 | return TabNavigation(startDestination, routes)
57 | }
58 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeImage.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.foundation.layout.height
4 | import androidx.compose.foundation.layout.width
5 | import androidx.compose.foundation.shape.CircleShape
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.runtime.mutableStateOf
9 | import androidx.compose.runtime.setValue
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.clip
12 | import androidx.compose.ui.res.painterResource
13 | import androidx.compose.ui.unit.dp
14 | import app.cash.redwood.LayoutModifier
15 | import com.skydoves.landscapist.coil.CoilImage
16 | import dev.icerock.moko.resources.ImageResource
17 | import dev.icerock.redwood.schema.widget.Image
18 |
19 | class ComposeImage : Image<@Composable () -> Unit> {
20 | private var _urlState: String? by mutableStateOf("")
21 | private var _width: Int? by mutableStateOf(null)
22 | private var _height: Int? by mutableStateOf(null)
23 | private var _placeholder: Int? by mutableStateOf(null)
24 |
25 | override var layoutModifiers: LayoutModifier = LayoutModifier
26 |
27 | override val value = @Composable {
28 | CoilImage(
29 | modifier = if (_height != null && _width != null)
30 | Modifier
31 | .height(_height!!.dp)
32 | .width(_width!!.dp)
33 | .clip(CircleShape)
34 | else
35 | Modifier.clip(CircleShape),
36 | imageModel = _urlState,
37 | placeHolder = _placeholder?.let { painterResource(it) },
38 | error = _placeholder?.let { painterResource(it) }
39 | )
40 | }
41 |
42 | override fun width(width: Int?) {
43 | _width = width
44 | }
45 |
46 | override fun height(height: Int?) {
47 | _height = height
48 | }
49 |
50 | override fun url(url: String?) {
51 | _urlState = url
52 | }
53 |
54 | override fun placeholder(placeholder: ImageResource?) {
55 | _placeholder = placeholder?.drawableResId
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/dev/icerock/redwoodapp/navigation/navigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import androidx.compose.runtime.Composable
4 | import app.cash.redwood.widget.Widget
5 | import platform.UIKit.UIView
6 | import platform.UIKit.UIViewController
7 | import dev.icerock.redwoodapp.ComposeViewController
8 | import dev.icerock.redwoodapp.dev.icerock.redwoodapp.navigation.ScreenSettingsImpl
9 |
10 | typealias FlatRouteData = (Widget.Provider, Navigator, Map) -> UIViewController
11 |
12 | actual fun navigation(
13 | startDestination: String,
14 | block: FlatNavigationDsl.() -> Unit
15 | ): NavigationHost {
16 | val routes: MutableMap = mutableMapOf()
17 | val navBarVisibility: MutableMap = mutableMapOf()
18 | val screenSettings = ScreenSettingsImpl()
19 |
20 | val dsl = object : FlatNavigationDsl {
21 | override fun registerScreen(
22 | uri: String,
23 | isToolbarVisible: Boolean,
24 | screen: @Composable (Navigator, Map, ScreenSettings) -> Unit
25 | ) {
26 | val startUri = uri.substringBefore('?')
27 | routes[startUri] = { provider, navigator, args ->
28 | ComposeViewController(provider, navBarVisibility[startUri] ?: true) @Composable {
29 | screen(navigator, args, screenSettings)
30 | }
31 | }
32 | navBarVisibility[startUri] = isToolbarVisible
33 | }
34 |
35 | override fun registerNavigation(
36 | uri: String,
37 | isToolbarVisible: Boolean,
38 | childNavigation: (Navigator, Map, ScreenSettings) -> NavigationHost
39 | ) {
40 | val startUri = uri.substringBefore('?')
41 | routes[startUri] = { provider, navigator, args ->
42 | childNavigation(navigator, args, screenSettings).createViewController(provider)
43 | }
44 | navBarVisibility[startUri] = isToolbarVisible
45 | }
46 | }
47 | dsl.block()
48 | return FlatNavigation(startDestination, routes, navBarVisibility, screenSettings)
49 | }
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeButton.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.platform.LocalContext
8 | import app.cash.redwood.LayoutModifier
9 | import dev.icerock.moko.resources.desc.StringDesc
10 | import dev.icerock.moko.resources.desc.desc
11 | import dev.icerock.redwood.schema.ButtonType
12 | import dev.icerock.redwood.schema.widget.Button
13 | import dev.icerock.redwoodapp.android.types.ActionButton
14 | import dev.icerock.redwoodapp.android.types.PrimaryButton
15 | import dev.icerock.redwoodapp.android.types.SecondaryButton
16 |
17 | class ComposeButton : Button<@Composable () -> Unit> {
18 | private var _text: StringDesc by mutableStateOf("".desc())
19 | private var _buttonType by mutableStateOf(ButtonType.Primary)
20 | private var _onClick: () -> Unit by mutableStateOf({})
21 | private var _enabled by mutableStateOf(true)
22 |
23 | override var layoutModifiers: LayoutModifier = LayoutModifier
24 | override val value = @Composable {
25 | when (_buttonType) {
26 | ButtonType.Primary -> PrimaryButton(
27 | text = _text.toString(LocalContext.current),
28 | enabled = _enabled,
29 | onClick = _onClick
30 | )
31 | ButtonType.Secondary -> SecondaryButton(
32 | text = _text.toString(LocalContext.current),
33 | enabled = _enabled,
34 | onClick = _onClick
35 | )
36 | ButtonType.Action -> ActionButton(
37 | text = _text.toString(LocalContext.current),
38 | enabled = _enabled,
39 | onClick = _onClick
40 | )
41 | }
42 | }
43 |
44 | override fun text(text: StringDesc) {
45 | _text = text
46 | }
47 |
48 | override fun onClick(onClick: (() -> Unit)?) {
49 | _onClick = onClick ?: {}
50 | }
51 |
52 | override fun buttonType(buttonType: ButtonType) {
53 | _buttonType = buttonType
54 | }
55 |
56 | override fun enabled(enabled: Boolean) {
57 | _enabled = enabled
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosStack.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosStack : WidgetStack {
13 | private let root: MyStackView = {
14 | let container = MyStackView()
15 | container.axis = .horizontal
16 | container.alignment = .lastBaseline
17 | container.distribution = .fillEqually
18 | container.translatesAutoresizingMaskIntoConstraints = true
19 | return container;
20 | }()
21 |
22 | var child1: Redwood_widgetWidgetChildren {
23 | ExposedKt.createViewChildrenListener(parent: root, insert: myInsert)
24 | }
25 |
26 | var child2: Redwood_widgetWidgetChildren {
27 | ExposedKt.createViewChildrenListener(parent: root, insert: myInsert2)
28 | }
29 |
30 | func myInsert(view: UIView,index: KotlinInt){
31 | root.insertArrangedSubview(view, at: 0)
32 | view.bottomAnchor.constraint(equalTo: root.bottomAnchor).isActive = true
33 | view.widthAnchor.constraint(equalTo: root.widthAnchor).isActive = true
34 | view.heightAnchor.constraint(equalTo: root.heightAnchor).isActive = true
35 | view.centerXAnchor.constraint(equalTo: root.centerXAnchor).isActive = true
36 | }
37 |
38 | func myInsert2(view: UIView,index: KotlinInt){
39 | root.insertArrangedSubview(view, at: 0)
40 | // todo fix size logic
41 | let size = view.sizeThatFits(CGSize(width: 200.0,height: 200.0))
42 | var height = 60.0
43 | if(size.height != 0) {
44 | height = size.height
45 |
46 | }
47 | view.widthAnchor.constraint(equalTo: root.widthAnchor).isActive = true
48 | view.heightAnchor.constraint(equalToConstant: height).isActive = true
49 | view.bottomAnchor.constraint(equalTo: root.bottomAnchor).isActive = true
50 | }
51 |
52 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
53 |
54 | var value: Any { root }
55 |
56 | class MyStackView : UIStackView{
57 | override func sizeThatFits(_ size: CGSize) -> CGSize {
58 | return CGSize(width: size.width, height: size.height)
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | id("app.cash.redwood")
4 | id("dev.icerock.mobile.multiplatform-resources")
5 | id("com.android.library")
6 | id("kotlin-parcelize")
7 | }
8 |
9 | android {
10 | namespace = "dev.icerock.redwoodapp.shared"
11 | compileSdk = 33
12 | defaultConfig {
13 | minSdk = 24
14 | targetSdk = 33
15 | }
16 | buildFeatures {
17 | compose = true
18 | }
19 | }
20 |
21 | kotlin {
22 | iosArm64()
23 | iosX64()
24 | iosSimulatorArm64()
25 |
26 | android()
27 |
28 | sourceSets {
29 | val commonMain by getting {
30 | dependencies {
31 | api(project(":schema:compose"))
32 | api("dev.icerock.moko:resources:0.20.1")
33 | api("dev.icerock.moko:parcelize:0.8.0")
34 | api("dev.icerock.moko:mvvm-core:0.15.0")
35 | }
36 | }
37 |
38 | val iosMain by creating {
39 | dependsOn(commonMain)
40 | dependencies {
41 | implementation("app.cash.redwood:redwood-layout-uiview:0.1.0")
42 | }
43 | }
44 |
45 | val iosArm64Main by getting {
46 | dependsOn(iosMain)
47 | }
48 | val iosX64Main by getting {
49 | dependsOn(iosMain)
50 | }
51 | val iosSimulatorArm64Main by getting {
52 | dependsOn(iosMain)
53 | }
54 | val androidMain by getting {
55 | dependencies {
56 | implementation("androidx.activity:activity-compose:1.6.1")
57 | implementation("androidx.compose.ui:ui:1.3.3")
58 | implementation("androidx.compose.ui:ui-tooling:1.3.3")
59 | implementation("androidx.compose.foundation:foundation:1.3.1")
60 | implementation("androidx.compose.foundation:foundation-layout:1.3.1")
61 | implementation("androidx.compose.material:material:1.3.1")
62 | implementation("com.google.android.material:compose-theme-adapter:1.2.1")
63 | implementation("androidx.navigation:navigation-compose:2.5.3")
64 | implementation("androidx.navigation:navigation-runtime-ktx:2.5.3")
65 | }
66 | }
67 | }
68 | }
69 |
70 | multiplatformResources {
71 | multiplatformResourcesPackage = "org.example.library" // required
72 | }
73 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeSpace.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxHeight
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.layout.width
10 | import dev.icerock.redwood.schema.widget.Space
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.setValue
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.unit.dp
18 | import app.cash.redwood.LayoutModifier
19 |
20 | class ComposeSpace : Space<@Composable () -> Unit> {
21 | private var _background: dev.icerock.moko.graphics.Color by mutableStateOf(
22 | dev.icerock.moko.graphics.Color(
23 | 0x00000000
24 | )
25 | )
26 | private var _width: Int by mutableStateOf(-1)
27 | private var _height: Int by mutableStateOf(-1)
28 |
29 | override var layoutModifiers: LayoutModifier = LayoutModifier
30 |
31 | override val value = @Composable {
32 | Spacer(
33 | modifier = when {
34 | _height == -1 && _width == -1 -> Modifier
35 | .fillMaxSize()
36 | .background(Color(_background.rgba))
37 | _height == -1 -> Modifier
38 | .fillMaxHeight()
39 | .width(_width.dp)
40 | .background(Color(_background.rgba))
41 | _width == -1 -> Modifier
42 | .fillMaxWidth()
43 | .height(_height.dp)
44 | .background(Color(_background.rgba))
45 | else -> Modifier
46 | .height(_height.dp)
47 | .width(_width.dp)
48 | .background(Color(_background.rgba))
49 | }
50 | )
51 | }
52 |
53 | override fun background(background: dev.icerock.moko.graphics.Color) {
54 | _background = background
55 | }
56 |
57 | override fun width(width: Int) {
58 | _width = width
59 | }
60 |
61 | override fun height(height: Int) {
62 | _height = height
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/screens/LoginScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.screens
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.setValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import app.cash.redwood.LayoutModifier
9 | import app.cash.redwood.layout.api.CrossAxisAlignment
10 | import app.cash.redwood.layout.api.MainAxisAlignment
11 | import app.cash.redwood.layout.api.Padding
12 | import app.cash.redwood.layout.compose.Column
13 | import dev.icerock.moko.resources.desc.desc
14 | import dev.icerock.redwood.schema.ButtonType
15 | import dev.icerock.redwood.schema.InputType
16 | import dev.icerock.redwood.schema.compose.Button
17 | import dev.icerock.redwood.schema.compose.Image
18 | import dev.icerock.redwood.schema.compose.TextInput
19 | import dev.icerock.redwoodapp.navigation.Navigator
20 | import org.example.library.MR
21 | import dev.icerock.redwoodapp.Box
22 |
23 | @Composable
24 | fun LoginScreen(navigator: Navigator) {
25 | Box {
26 | Column(
27 | horizontalAlignment = CrossAxisAlignment.Center,
28 | verticalAlignment = MainAxisAlignment.Center
29 | ) {
30 | var login: String by remember { mutableStateOf("") }
31 | var password: String by remember { mutableStateOf("") }
32 | Image(
33 | 120,
34 | 120,
35 | placeholder = MR.images.ava_preview,
36 | layoutModifier = LayoutModifier.padding(Padding(bottom = 100)),
37 | url = null
38 | )
39 | TextInput(login,
40 | MR.strings.auth_login.desc(),
41 | layoutModifier = LayoutModifier.padding(Padding(16)),
42 | onChange = {
43 | login = it
44 | }
45 | )
46 | TextInput(
47 | password,
48 | MR.strings.auth_password.desc(),
49 | layoutModifier = LayoutModifier.padding(Padding(horizontal = 16)),
50 | onChange = {
51 | password = it
52 | },
53 | inputType = InputType.Password
54 | )
55 | Button(
56 | MR.strings.auth_button.desc(), buttonType = ButtonType.Primary,
57 | enabled = login.isNotEmpty() && password.isNotEmpty(),
58 | onClick = {
59 | navigator.navigate("tabs")
60 | }, layoutModifier = LayoutModifier.padding(Padding(start = 16, top = 100, end = 16))
61 | )
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeImageButton.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Button
6 | import androidx.compose.material.ButtonDefaults
7 | import androidx.compose.material.Icon
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.platform.LocalContext
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.unit.dp
18 | import app.cash.redwood.LayoutModifier
19 | import dev.icerock.moko.resources.ImageResource
20 | import dev.icerock.moko.resources.desc.StringDesc
21 | import dev.icerock.moko.resources.desc.desc
22 | import dev.icerock.redwoodapp.android.R
23 | import dev.icerock.redwood.schema.widget.ImageButton
24 |
25 | class ComposeImageButton : ImageButton<@Composable () -> Unit> {
26 | private var _text : StringDesc by mutableStateOf("".desc())
27 | private var _icon: Int by mutableStateOf(0)
28 | private var _onClick: () -> Unit by mutableStateOf({})
29 | private var _isClicked by mutableStateOf(true)
30 |
31 | override var layoutModifiers: LayoutModifier = LayoutModifier
32 | override val value = @Composable {
33 | Button(
34 | onClick = _onClick,
35 | colors = ButtonDefaults.buttonColors(
36 | backgroundColor = Color.Transparent
37 | ),
38 | elevation = ButtonDefaults.elevation(0.dp, 0.dp)
39 | ) {
40 | Row {
41 | Icon(
42 | modifier = Modifier.padding(end = 8.dp),
43 | painter = painterResource(_icon),
44 | tint = Color.Unspecified,
45 | contentDescription = null
46 | )
47 | Text(
48 | text = _text.toString(LocalContext.current),
49 | color = if (_isClicked) Color(0xFF0C7BC7) else Color(0xFFA9A9A9)
50 | )
51 | }
52 | }
53 | }
54 |
55 | override fun text(text: StringDesc) {
56 | _text = text
57 | }
58 |
59 | override fun icon(icon: ImageResource?) {
60 | _icon = icon?.drawableResId ?: R.drawable.like
61 | }
62 |
63 | override fun onClick(onClick: (() -> Unit)?) {
64 | _onClick = onClick ?: {}
65 | }
66 |
67 | override fun isClicked(isClicked: Boolean) {
68 | _isClicked = isClicked
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/types/ButtonType.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.types
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material.Button
11 | import androidx.compose.material.ButtonDefaults
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 |
19 | @Composable
20 | fun PrimaryButton(text: String, enabled: Boolean, onClick: () -> Unit) {
21 | Button(
22 | modifier = Modifier.fillMaxWidth().height(40.dp),
23 | onClick = onClick,
24 | enabled = enabled,
25 | shape = RoundedCornerShape(8.dp),
26 | elevation = ButtonDefaults.elevation(0.dp, 0.dp),
27 | colors = ButtonDefaults.buttonColors(
28 | backgroundColor = Color(0xFF0C7BC7),
29 | contentColor = Color.White,
30 | disabledBackgroundColor = Color(0x2828282E),
31 | disabledContentColor = Color(0x61282861),
32 | )
33 | ) {
34 | Text(
35 | text = text.uppercase(),
36 | )
37 | }
38 | }
39 |
40 | @Composable
41 | fun SecondaryButton(text: String, enabled: Boolean, onClick: () -> Unit) {
42 | Button(
43 | modifier = Modifier.fillMaxWidth(),
44 | onClick = onClick,
45 | enabled = enabled,
46 | shape = RoundedCornerShape(8.dp),
47 | elevation = ButtonDefaults.elevation(0.dp, 0.dp),
48 | colors = ButtonDefaults.buttonColors(
49 | backgroundColor = Color.White,
50 | contentColor = Color(0xFF0C7BC7),
51 | disabledBackgroundColor = Color.White,
52 | disabledContentColor = Color(0x2828282E),
53 | ),
54 | border = BorderStroke(1.dp, if (enabled) Color(0xFF0C7BC7) else Color(0x2828282E))
55 | ) {
56 | Text(
57 | text = text.uppercase(),
58 | )
59 | }
60 | }
61 |
62 | @Composable
63 | fun ActionButton(text: String, enabled: Boolean, onClick: () -> Unit) {
64 |
65 | }
66 |
67 | @Preview
68 | @Composable
69 | fun PreviewButton() {
70 | Column(
71 | Modifier
72 | .background(Color.White)
73 | .padding(16.dp)
74 | ) {
75 | PrimaryButton("Text", true, {})
76 | SecondaryButton(text = "text", enabled = true, {})
77 | }
78 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosWidgetCard.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosWidgetCard.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosWidgetCard : WidgetCard{
13 |
14 |
15 | private let root: MyCardView = {
16 | let container = MyCardView()
17 | container.translatesAutoresizingMaskIntoConstraints = true
18 | container.layer.cornerRadius = 16
19 | container.backgroundColor = UIColor(red:242.0/255.0, green:242.0/255.0, blue:242.0/255.0, alpha:1.0)
20 |
21 | container.clipsToBounds = true
22 | return container;
23 | }()
24 |
25 | func onClick(onClick: (() -> Void)? = nil) {
26 | if(onClick != nil){
27 | root.setOnClickListener {
28 | onClick!()
29 | }
30 | }
31 |
32 | }
33 |
34 | var child: Redwood_widgetWidgetChildren {
35 | ExposedKt.createViewChildrenListener(parent: root, insert: myInsert)
36 | }
37 |
38 | func myInsert(view: UIView,index: KotlinInt){
39 | view.translatesAutoresizingMaskIntoConstraints = true
40 | root.insertArrangedSubview(view, at: 0)
41 | view.leadingAnchor.constraint(equalTo:
42 | root.leadingAnchor
43 | ).isActive = true
44 | view.topAnchor.constraint(equalTo:
45 | root.topAnchor).isActive = true
46 | view.leftAnchor.constraint(equalTo:
47 | root.leftAnchor).isActive = true
48 | view.widthAnchor.constraint(equalTo:
49 | root.widthAnchor).isActive = true
50 | root.heightAnchor.constraint(equalTo:
51 | view.heightAnchor).isActive = true
52 | }
53 |
54 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
55 |
56 | var value: Any { root }
57 | }
58 |
59 | class MyCardView: UIStackView{
60 | override func sizeThatFits(_ size: CGSize) -> CGSize {
61 | let childSize = subviews.first?.sizeThatFits(size)
62 | return CGSize(width: size.width, height: childSize?.height ?? 32)
63 | }
64 | }
65 |
66 | extension UIView {
67 |
68 | func setOnClickListener(action :@escaping () -> Void){
69 | let tapRecogniser = ClickListener(target: self, action: #selector(onViewClicked(sender:)))
70 | tapRecogniser.onClick = action
71 | self.addGestureRecognizer(tapRecogniser)
72 | }
73 |
74 | @objc func onViewClicked(sender: ClickListener) {
75 | if let onClick = sender.onClick {
76 | onClick()
77 | }
78 | }
79 |
80 | }
81 |
82 | class ClickListener: UITapGestureRecognizer {
83 | var onClick : (() -> Void)? = nil
84 | }
85 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/dev/icerock/redwoodapp/navigation/FlatNavigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import app.cash.redwood.widget.Widget
4 | import platform.UIKit.UIColor
5 | import platform.UIKit.UINavigationController
6 | import platform.UIKit.UIView
7 | import platform.UIKit.UIViewController
8 | import platform.UIKit.backgroundColor
9 | import dev.icerock.redwoodapp.dev.icerock.redwoodapp.navigation.ScreenSettingsImpl
10 | import dev.icerock.redwoodapp.dev.icerock.redwoodapp.navigation.getParams
11 | import dev.icerock.redwoodapp.dev.icerock.redwoodapp.navigation.getStableParts
12 | import dev.icerock.redwoodapp.dev.icerock.redwoodapp.navigation.isPlaceholderOf
13 |
14 | data class FlatNavigation(
15 | val startDestination: String,
16 | val routes: MutableMap,
17 | val navBarVisibility: MutableMap,
18 | val screenSettings: ScreenSettingsImpl
19 | ) : NavigationHost {
20 |
21 | override fun createViewController(provider: Widget.Provider): UIViewController {
22 | lateinit var navController: UINavigationController
23 |
24 | val navigator: Navigator = object : Navigator {
25 | override fun navigate(uri: String) {
26 | val startUri = uri.substringBefore('?')
27 | val key = routes.keys.find { it.isPlaceholderOf(startUri) } ?: return
28 | val params = uri.substringAfter('?')
29 | val paramsMap = mutableMapOf()
30 | params.split('&').forEach {
31 | paramsMap[it.substringBefore('=')] = it.substringAfter('=')
32 | }
33 | val newViewController: UIViewController =
34 | routes[key]!!(
35 | provider,
36 | this,
37 | paramsMap
38 | .apply {
39 | putAll(startUri.getParams(key, key.getStableParts()))
40 | }
41 | )
42 | navController.pushViewController(newViewController, animated = false)
43 | navController.navigationBarHidden = navBarVisibility[key]?.not() ?: false
44 | }
45 |
46 | override fun popBackStack() {
47 | navController.popViewControllerAnimated(false)
48 | }
49 | }
50 | val rootViewController: UIViewController =
51 | routes[startDestination]!!(provider, navigator, emptyMap())
52 | navController = UINavigationController(rootViewController)
53 | navController.navigationBarHidden = navBarVisibility[startDestination]?.not() ?: false
54 | navController.navigationBar.backgroundColor = UIColor.whiteColor
55 | screenSettings.init(navController)
56 | return navController
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/iosApp/iosApp/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 24.12.2022.
6 | // Copyright © 2022 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
21 | guard let scene = (scene as? UIWindowScene) else { return }
22 |
23 | window = UIWindow(windowScene: scene)
24 | let rootNavigation = ExposedKt.mainApp()
25 |
26 | window?.rootViewController = rootNavigation.createViewController(
27 | provider: ExposedKt.widgetProvider(widgetFactory: IosWidgetFactory())
28 | )
29 | window?.makeKeyAndVisible()
30 | }
31 |
32 | func sceneDidDisconnect(_ scene: UIScene) {
33 | // Called as the scene is being released by the system.
34 | // This occurs shortly after the scene enters the background, or when its session is discarded.
35 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
36 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
37 | }
38 |
39 | func sceneDidBecomeActive(_ scene: UIScene) {
40 | // Called when the scene has moved from an inactive state to an active state.
41 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
42 | }
43 |
44 | func sceneWillResignActive(_ scene: UIScene) {
45 | // Called when the scene will move from an active state to an inactive state.
46 | // This may occur due to temporary interruptions (ex. an incoming phone call).
47 | }
48 |
49 | func sceneWillEnterForeground(_ scene: UIScene) {
50 | // Called as the scene transitions from the background to the foreground.
51 | // Use this method to undo the changes made on entering the background.
52 | }
53 |
54 | func sceneDidEnterBackground(_ scene: UIScene) {
55 | // Called as the scene transitions from the foreground to the background.
56 | // Use this method to save data, release shared resources, and store enough scene-specific state information
57 | // to restore the scene back to its current state.
58 | }
59 |
60 |
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosWidgetImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosWidgetImage.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | class IosWidgetImage: WidgetImage {
13 |
14 | private let root: UIImageView = {
15 | let view = UIImageView()
16 | view.translatesAutoresizingMaskIntoConstraints = true
17 | view.contentMode = .scaleAspectFill
18 | view.clipsToBounds = true
19 | return view
20 | }()
21 |
22 | var _placeholder: UIImage? = nil
23 | var _width: CGFloat? = nil
24 | var _height: CGFloat? = nil
25 |
26 | func url(url: String?) {
27 | if(url != nil){
28 | root.load(url: URL(string:url!)!)
29 | }
30 | }
31 |
32 | func placeholder(placeholder: ImageResource?) {
33 | var image = placeholder?.toUIImage()
34 | _placeholder = image
35 | if(_width != nil && _height != nil){
36 | image = _placeholder?.resizeImageTo(size: CGSize(width: _width!, height: _height!))
37 | }
38 | if(image != nil){
39 | root.layer.cornerRadius = image!.size.width / 2;
40 | root.layer.masksToBounds = true
41 | }
42 | root.image = image
43 | }
44 |
45 | func width(width: KotlinInt?) {
46 | _width = CGFloat(width?.intValue ?? 10)
47 | }
48 |
49 | func height(height: KotlinInt?) {
50 | _height = CGFloat(height?.intValue ?? 10)
51 | }
52 |
53 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
54 |
55 | var value: Any { root }
56 |
57 | func load(url: URL) {
58 | DispatchQueue.global().async { [weak self] in
59 | if let data = try? Data(contentsOf: url) {
60 | if let image = UIImage(data: data) {
61 | DispatchQueue.main.async {
62 | self?.root.image = image
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | }
70 |
71 | extension UIImageView {
72 | func load(url: URL) {
73 | DispatchQueue.global().async { [weak self] in
74 | if let data = try? Data(contentsOf: url) {
75 | if let image = UIImage(data: data) {
76 | DispatchQueue.main.async {
77 | self?.image = image
78 | }
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
85 |
86 | extension UIImage {
87 |
88 | func resizeImageTo(size: CGSize) -> UIImage? {
89 |
90 | UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
91 | self.draw(in: CGRect(origin: CGPoint.zero, size: size))
92 | let resizedImage = UIGraphicsGetImageFromCurrentImageContext()!
93 | UIGraphicsEndImageContext()
94 | return resizedImage
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/screens/ProfileScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.screens
2 |
3 | import androidx.compose.runtime.Composable
4 | import app.cash.redwood.LayoutModifier
5 | import app.cash.redwood.layout.api.Constraint
6 | import app.cash.redwood.layout.api.CrossAxisAlignment
7 | import app.cash.redwood.layout.api.MainAxisAlignment
8 | import app.cash.redwood.layout.api.Padding
9 | import app.cash.redwood.layout.compose.Column
10 | import app.cash.redwood.layout.compose.Row
11 | import dev.icerock.moko.resources.desc.desc
12 | import dev.icerock.redwood.schema.ButtonType
13 | import dev.icerock.redwood.schema.TextType
14 | import dev.icerock.redwood.schema.compose.Button
15 | import dev.icerock.redwood.schema.compose.Image
16 | import dev.icerock.redwood.schema.compose.Stack
17 | import dev.icerock.redwood.schema.compose.Text
18 | import org.example.library.MR
19 | import dev.icerock.redwoodapp.USER_AVATAR
20 | import dev.icerock.redwoodapp.USR_NAME
21 | import dev.icerock.redwoodapp.navigation.Navigator
22 |
23 | @Composable
24 | fun ProfileScreen(navigator: Navigator) {
25 | Stack(
26 | child1 = {
27 | Row(
28 | verticalAlignment = CrossAxisAlignment.Start,
29 | horizontalAlignment = MainAxisAlignment.Center,
30 | width = Constraint.Fill
31 | ) {
32 | Column(
33 | horizontalAlignment = CrossAxisAlignment.Center,
34 | verticalAlignment = MainAxisAlignment.Start,
35 | height = Constraint.Fill
36 | ) {
37 | Image(
38 | width = 120,
39 | height = 120,
40 | url = USER_AVATAR,
41 | placeholder = MR.images.ava_preview,
42 | layoutModifier = LayoutModifier.padding(Padding(top = 120))
43 | )
44 | Text(
45 | USR_NAME,
46 | layoutModifier = LayoutModifier.padding(Padding(top = 12)),
47 | textType = TextType.Header
48 | )
49 | }
50 | }
51 | },
52 | child2 = {
53 | Column(
54 | height = Constraint.Wrap,
55 | horizontalAlignment = CrossAxisAlignment.Center,
56 | width = Constraint.Fill
57 | ) {
58 | Button(
59 | MR.strings.profile_logout.desc(),
60 | buttonType = ButtonType.Secondary,
61 | onClick = {
62 | navigator.navigate("login")
63 | },
64 | layoutModifier = LayoutModifier.padding(
65 | Padding(
66 | horizontal = 16,
67 | vertical = 16
68 | )
69 | )
70 | )
71 | }
72 | }
73 | )
74 | }
--------------------------------------------------------------------------------
/schema/src/main/kotlin/dev/icerock/redwood/schema/schema.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwood.schema
2 |
3 | import androidx.compose.runtime.Composable
4 | import app.cash.redwood.layout.RedwoodLayout
5 | import app.cash.redwood.schema.Children
6 | import app.cash.redwood.schema.Default
7 | import app.cash.redwood.schema.Property
8 | import app.cash.redwood.schema.Schema
9 | import app.cash.redwood.schema.Schema.Dependency
10 | import app.cash.redwood.schema.Widget
11 | import dev.icerock.moko.graphics.Color
12 | import dev.icerock.moko.resources.ImageResource
13 | import dev.icerock.moko.resources.desc.StringDesc
14 |
15 | @Schema(
16 | members = [
17 | TextInput::class,
18 | Text::class,
19 | Image::class,
20 | Stack::class,
21 | Card::class,
22 | ImageButton::class,
23 | Button::class,
24 | Space::class
25 | ],
26 | dependencies = [
27 | Dependency(1, RedwoodLayout::class),
28 | ],
29 | )
30 | interface RedwoodAppSchema
31 |
32 | @Widget(1)
33 | data class TextInput(
34 | @Property(1)
35 | @Default("\"\"")
36 | val state: String,
37 | @Property(2)
38 | val hint: StringDesc,
39 | @Property(3)
40 | @Default("null")
41 | val onChange: (String) -> Unit,
42 | @Property(4)
43 | @Default("null")
44 | val inputType: InputType?,
45 | )
46 |
47 | @Widget(2)
48 | data class Text(
49 | @Property(1) val text: String,
50 | @Property(2)
51 | @Default("false") val isSingleLine: Boolean,
52 | @Property(3)
53 | @Default("TextType.Primary")
54 | val textType: TextType?
55 | )
56 |
57 | @Widget(3)
58 | data class Image(
59 | @Property(1) val width: Int?,
60 | @Property(2) val height: Int?,
61 | @Property(3) val url: String?,
62 | @Property(4) val placeholder: ImageResource?,
63 | )
64 |
65 | @Widget(4)
66 | data class ImageButton(
67 | @Property(1) val text: StringDesc,
68 | @Property(2)
69 | @Default("null") val icon: ImageResource?,
70 | @Property(3)
71 | @Default("true")
72 | val isClicked: Boolean,
73 | @Property(4)
74 | val onClick: () -> Unit
75 | )
76 |
77 | @Widget(5)
78 | data class Button(
79 | @Property(1) val text: StringDesc,
80 | @Property(2)
81 | val buttonType: ButtonType,
82 | @Property(3)
83 | @Default("true")
84 | val enabled: Boolean,
85 | @Property(4)
86 | val onClick: () -> Unit,
87 |
88 | )
89 |
90 | @Widget(6)
91 | data class Card(
92 | @Children(1) val child: @Composable () -> Unit,
93 | @Property(2)
94 | val onClick: () -> Unit,
95 | )
96 |
97 | //todo fix to normal list
98 | @Widget(7)
99 | data class Stack(
100 | @Children(1) val child1: @Composable () -> Unit,
101 | @Children(2) val child2: @Composable () -> Unit,
102 | )
103 |
104 | @Widget(8)
105 | data class Space(
106 | @Property(1) val background: Color = Color(0x00000000),
107 | // -1 max size
108 | @Property(2) val width: Int,
109 | @Property(3) val height: Int,
110 | )
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/mainApp.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp
2 |
3 | import androidx.compose.runtime.Composable
4 | import app.cash.redwood.layout.api.Constraint
5 | import app.cash.redwood.layout.api.CrossAxisAlignment
6 | import app.cash.redwood.layout.api.MainAxisAlignment
7 | import app.cash.redwood.layout.compose.Column
8 | import app.cash.redwood.layout.compose.Row
9 | import dev.icerock.moko.resources.desc.desc
10 | import org.example.library.MR
11 | import dev.icerock.redwoodapp.navigation.NavigationHost
12 | import dev.icerock.redwoodapp.navigation.Navigator
13 | import dev.icerock.redwoodapp.navigation.navigation
14 | import dev.icerock.redwoodapp.navigation.navigationTabs
15 | import dev.icerock.redwoodapp.screens.DetailsScreen
16 | import dev.icerock.redwoodapp.screens.LoginScreen
17 | import dev.icerock.redwoodapp.screens.PostsList
18 | import dev.icerock.redwoodapp.screens.ProfileScreen
19 |
20 | fun mainApp(): NavigationHost {
21 | return navigation(startDestination = "login") {
22 | registerScreen(
23 | uri = "login",
24 | isToolbarVisible = false
25 | ) { navigator, _, _ ->
26 | LoginScreen(navigator)
27 | }
28 | registerNavigation(
29 | uri = "tabs",
30 | isToolbarVisible = false,
31 | childNavigation = { navigator, _, _ ->
32 | mainScreenNavigation(navigator)
33 | }
34 | )
35 | }
36 | }
37 |
38 | private fun mainScreenNavigation(rootNavigator: Navigator): NavigationHost =
39 | navigationTabs(startDestination = "line") {
40 | registerNavigation(
41 | uri = "line",
42 | title = MR.strings.tab_list.desc(),
43 | icon = MR.images.line,
44 | childNavigation = {
45 | secondTabNavigation()
46 | }
47 | )
48 | registerScreen(
49 | uri = "settings",
50 | title = MR.strings.tab_settings.desc(),
51 | icon = MR.images.settings,
52 | screen = {
53 | ProfileScreen(rootNavigator)
54 | }
55 | )
56 | }
57 |
58 | private fun secondTabNavigation() = navigation(startDestination = "start") {
59 | registerScreen(
60 | "start",
61 | isToolbarVisible = true,
62 | ) { navigator, _, screenSettings ->
63 | PostsList(screenSettings) { date, text ->
64 | navigator.navigate("/details/${date}?description=${text}")
65 | }
66 | }
67 | registerScreen(
68 | "/details/{date}?description={description}",
69 | isToolbarVisible = true
70 | ) { navController, args, screenSettings ->
71 | DetailsScreen(
72 | navController,
73 | args["date"].orEmpty(),
74 | args["description"].orEmpty(),
75 | screenSettings
76 | )
77 | }
78 | }
79 |
80 | @Composable
81 | fun Box(content: @Composable () -> Unit) {
82 | Column(
83 | horizontalAlignment = CrossAxisAlignment.Center,
84 | verticalAlignment = MainAxisAlignment.Center,
85 | height = Constraint.Fill
86 | ) {
87 | Row(
88 | verticalAlignment = CrossAxisAlignment.Center,
89 | horizontalAlignment = MainAxisAlignment.Center,
90 | width = Constraint.Fill
91 | ) {
92 | content()
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Widgets/IosWidgetButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IosWidgetButton.swift
3 | // iosApp
4 | //
5 | // Created by Aleksey Mikhailov on 13.01.2023.
6 | // Copyright © 2023 IceRock Development. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared_ios
11 |
12 | //todo remove to coloes
13 | let mainColor = UIColor(red:55.0/255.0, green:121.0/255.0, blue:193.0/255.0, alpha:1.0)
14 |
15 | class IosWidgetButton: WidgetButton {
16 |
17 | private let root: FillButton = {
18 | var configuration = UIButton.Configuration.filled()
19 | configuration.imagePadding = 10
20 | configuration.contentInsets = NSDirectionalEdgeInsets(top: 12, leading: 20, bottom: 12, trailing: 20)
21 | let view = FillButton(configuration: configuration)
22 | view.translatesAutoresizingMaskIntoConstraints = true
23 | return view
24 |
25 | }()
26 |
27 | var _buttonType: EntityButtonType? = nil
28 |
29 | func buttonType(buttonType: EntityButtonType) {
30 | _buttonType = buttonType
31 | if(buttonType == EntityButtonType.primary){
32 | root.backgroundColor = mainColor
33 | root.layer.cornerRadius = 16
34 | root.configuration?.baseBackgroundColor = UIColor.clear
35 | root.setTitleColor(.white, for: UIControl.State.normal)
36 | root.setTitleColor(.white, for: UIControl.State.disabled)
37 | }
38 | if(buttonType == EntityButtonType.secondary){
39 | root.setTitleColor(mainColor, for: UIControl.State.normal)
40 | root.setTitleColor(.lightGray, for: UIControl.State.disabled)
41 | root.backgroundColor = UIColor.clear
42 | root.layer.borderColor = mainColor.cgColor
43 | root.layer.borderWidth = 2
44 | root.layer.cornerRadius = 16
45 | root.contentHorizontalAlignment = .center
46 | root.configuration?.baseBackgroundColor = UIColor.clear
47 | root.updateConfiguration()
48 | }
49 | if(buttonType == EntityButtonType.action){
50 | root.setTitleColor(mainColor, for: UIControl.State.normal)
51 | root.configuration?.baseBackgroundColor = UIColor.clear
52 | root.backgroundColor = UIColor.clear
53 | }
54 | }
55 |
56 | func enabled(enabled: Bool) {
57 | root.isEnabled = enabled
58 | if(_buttonType == EntityButtonType.primary){
59 | if(enabled){
60 | root.backgroundColor = mainColor
61 | }else{
62 | root.backgroundColor = .lightGray
63 | }
64 | }
65 | }
66 |
67 | func text(text: StringDesc) {
68 | root.setTitle(text.localized(), for: .normal)
69 | root.setTitle(text.localized(), for: .disabled)
70 | }
71 |
72 | func onClick(onClick: (() -> Void)? = nil) {
73 | let identifier = UIAction.Identifier("ButtonBinding.onPressed")
74 | let action = UIAction(
75 | identifier: identifier,
76 | handler: { _ in onClick?() }
77 | )
78 |
79 | root.removeAction(identifiedBy: identifier, for: .touchUpInside)
80 | root.addAction(action, for: .touchUpInside)
81 | }
82 |
83 | var layoutModifiers: Redwood_runtimeLayoutModifier = ExposedKt.layoutModifier()
84 |
85 | var value: Any {root}
86 | }
87 |
88 | class FillButton : UIButton{
89 | override func sizeThatFits(_ size: CGSize) -> CGSize {
90 | let originalSize = super.sizeThatFits(size)
91 | return CGSize(width: size.width, height: originalSize.height)
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/dev/icerock/redwoodapp/android/widgets/ComposeTextInput.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.android.widgets
2 |
3 | import androidx.compose.foundation.interaction.MutableInteractionSource
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.foundation.text.BasicTextField
7 | import androidx.compose.material.ExperimentalMaterialApi
8 | import androidx.compose.material.Text
9 | import androidx.compose.material.TextFieldDefaults
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.runtime.setValue
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.platform.LocalContext
18 | import androidx.compose.ui.text.input.PasswordVisualTransformation
19 | import androidx.compose.ui.text.input.VisualTransformation
20 | import androidx.compose.ui.unit.dp
21 | import app.cash.redwood.LayoutModifier
22 | import dev.icerock.moko.resources.desc.StringDesc
23 | import dev.icerock.moko.resources.desc.desc
24 | import dev.icerock.redwood.schema.InputType
25 | import dev.icerock.redwood.schema.widget.TextInput
26 |
27 | class ComposeTextInput : TextInput<@Composable () -> Unit> {
28 | private var _textState by mutableStateOf("")
29 | private var _hintState: StringDesc by mutableStateOf("".desc())
30 | private var _onChangeState: (String) -> Unit by mutableStateOf({})
31 | private var _inputType: InputType? by mutableStateOf(InputType.Text)
32 |
33 | override var layoutModifiers: LayoutModifier = LayoutModifier
34 |
35 | @OptIn(ExperimentalMaterialApi::class)
36 | override val value = @Composable {
37 | val colors = TextFieldDefaults.outlinedTextFieldColors(
38 | cursorColor = Color.Black,
39 | focusedBorderColor = Color(0xFF0C7BC7),
40 | unfocusedBorderColor = Color(0x2828282E),
41 | disabledBorderColor = Color(0xFF0C7BC7)
42 | )
43 |
44 | BasicTextField(
45 | modifier = Modifier.fillMaxWidth(),
46 | value = _textState,
47 | onValueChange = _onChangeState,
48 | visualTransformation = if (_inputType == InputType.Password) {
49 | PasswordVisualTransformation()
50 | } else {
51 | VisualTransformation.None
52 | },
53 | singleLine = true,
54 | decorationBox = { innerTextField ->
55 | TextFieldDefaults.OutlinedTextFieldDecorationBox(
56 | value = _textState,
57 | innerTextField = innerTextField,
58 | placeholder = {
59 | Text(
60 | text = _hintState.toString(LocalContext.current),
61 | color = Color(0xFFA9A9A9)
62 | )
63 | },
64 | enabled = true,
65 | singleLine = false,
66 | visualTransformation = VisualTransformation.None,
67 | interactionSource = remember { MutableInteractionSource() },
68 | border = {
69 | TextFieldDefaults.BorderBox(
70 | enabled = true,
71 | isError = false,
72 | interactionSource = remember { MutableInteractionSource() },
73 | colors = colors,
74 | shape = RoundedCornerShape(8.dp)
75 | )
76 | }
77 | )
78 | }
79 | )
80 | }
81 |
82 | override fun state(state: String) {
83 | _textState = state
84 | }
85 |
86 | override fun hint(hint: StringDesc) {
87 | _hintState = hint
88 | }
89 |
90 | override fun onChange(onChange: ((String) -> Unit)?) {
91 | _onChangeState = onChange ?: {}
92 | }
93 |
94 | override fun inputType(inputType: InputType?) {
95 | _inputType = inputType
96 | }
97 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # redwood-sample
2 |
3 | Sample application for https://github.com/cashapp/redwood
4 |
5 | 
6 |
7 | 
8 |
9 | Login screen example:
10 |
11 | ```kotlin
12 | @Composable
13 | fun LoginScreen(navigator: Navigator) {
14 | Box {
15 | Column(
16 | horizontalAlignment = CrossAxisAlignment.Center,
17 | verticalAlignment = MainAxisAlignment.Center
18 | ) {
19 | var login: String by remember { mutableStateOf("") }
20 | var password: String by remember { mutableStateOf("") }
21 | Image(
22 | 120,
23 | 120,
24 | placeholder = MR.images.ava_preview,
25 | layoutModifier = LayoutModifier.padding(Padding(bottom = 100)),
26 | url = null
27 | )
28 | TextInput(login,
29 | MR.strings.auth_login.desc(),
30 | layoutModifier = LayoutModifier.padding(Padding(16)),
31 | onChange = {
32 | login = it
33 | }
34 | )
35 | TextInput(
36 | password,
37 | MR.strings.auth_password.desc(),
38 | layoutModifier = LayoutModifier.padding(Padding(horizontal = 16)),
39 | onChange = {
40 | password = it
41 | },
42 | inputType = InputType.Password
43 | )
44 | Button(
45 | MR.strings.auth_button.desc(), buttonType = ButtonType.Primary,
46 | enabled = login.isNotEmpty() && password.isNotEmpty(),
47 | onClick = {
48 | navigator.navigate("tabs")
49 | }, layoutModifier = LayoutModifier.padding(Padding(start = 16, top = 100, end = 16))
50 | )
51 | }
52 | }
53 | }
54 | ```
55 |
56 | Flat navigation example:
57 |
58 | ```kotlin
59 | private fun secondTabNavigation() = navigation(startDestination = "start") {
60 | registerScreen(
61 | "start",
62 | isToolbarVisible = true,
63 | ) { navigator, _, screenSettings ->
64 | PostsList(screenSettings) { date, text ->
65 | navigator.navigate("/details/${date}?description=${text}")
66 | }
67 | }
68 | registerScreen(
69 | "/details/{date}?description={description}",
70 | isToolbarVisible = true
71 | ) { navController, args, screenSettings ->
72 | DetailsScreen(
73 | navController,
74 | args["date"].orEmpty(),
75 | args["description"].orEmpty(),
76 | screenSettings
77 | )
78 | }
79 | }
80 | ```
81 |
82 | Tab navigation example:
83 |
84 | ```kotlin
85 | private fun mainScreenNavigation(rootNavigator: Navigator): NavigationHost =
86 | navigationTabs(startDestination = "line") {
87 | registerNavigation(
88 | uri = "line",
89 | title = MR.strings.tab_list.desc(),
90 | icon = MR.images.line,
91 | childNavigation = {
92 | secondTabNavigation()
93 | }
94 | )
95 | registerScreen(
96 | uri = "settings",
97 | title = MR.strings.tab_settings.desc(),
98 | icon = MR.images.settings,
99 | screen = {
100 | ProfileScreen(rootNavigator)
101 | }
102 | )
103 | }
104 | ```
105 |
106 | ## Run
107 | ### Android
108 |
109 | Open in Android Studio and just run `androidApp` configuration.
110 |
111 | ### iOS
112 |
113 | 1. Run `pod install` in `iosApp` directory
114 | 2. Open `iosApp/iosApp.xcworkspace` in Xcode
115 | 3. Run app
116 |
117 | ## Notes
118 |
119 | 1. At now redwood successfully compiles on iOS only with Release configuration. Debug compilation fails.
120 | 2. Demo screen UI code in [shared](https://github.com/icerockdev/redwood-sample/blob/main/shared/src/commonMain/kotlin/ru/alex009/redwoodapp/HelloWorld.kt)
121 | 3. Android UI implemented with Jetpack Compose Material UI in [androidApp](androidApp/src/main/java/ru/alex009/redwoodapp/android/widgets)
122 | 4. iOS UI implemented with UIKit in [iosApp](iosApp/iosApp/Widgets)
123 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/dev/icerock/redwoodapp/navigation/TabNavigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import android.content.Context
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.RowScope
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material.BottomNavigation
9 | import androidx.compose.material.BottomNavigationItem
10 | import androidx.compose.material.Icon
11 | import androidx.compose.material.Scaffold
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.platform.LocalContext
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.navigation.NavBackStackEntry
21 | import androidx.navigation.NavHostController
22 | import androidx.navigation.compose.NavHost
23 | import androidx.navigation.compose.composable
24 | import androidx.navigation.compose.currentBackStackEntryAsState
25 | import androidx.navigation.compose.rememberNavController
26 | import app.cash.redwood.widget.Widget
27 | import dev.icerock.moko.resources.ImageResource
28 | import dev.icerock.moko.resources.desc.StringDesc
29 |
30 | data class TabNavigation(
31 | val startDestination: String,
32 | val routes: Map
33 | ) : NavigationHost {
34 |
35 | @Composable
36 | override fun Render(provider: Widget.Provider<() -> Unit>) {
37 | val navController: NavHostController = rememberNavController()
38 | val nav: Navigator = remember(navController) {
39 | object : Navigator {
40 | override fun navigate(uri: String) {
41 | navController.navigate(uri)
42 | }
43 |
44 | override fun popBackStack() {
45 | navController.popBackStack()
46 | }
47 | }
48 | }
49 |
50 | NavHost(
51 | navController = navController,
52 | startDestination = startDestination
53 | ) {
54 | routes.forEach { item ->
55 | composable(route = item.key) {
56 | Scaffold(
57 | bottomBar = {
58 | BottomBarNavigation(navController)
59 | },
60 | content = { innerPadding ->
61 | Box(modifier = Modifier.padding(innerPadding)) {
62 | item.value.render(provider, nav)
63 | }
64 | }
65 | )
66 | }
67 | }
68 | }
69 | }
70 |
71 | @Composable
72 | private fun BottomBarNavigation(navController: NavHostController) {
73 | BottomNavigation {
74 | val navBackStackEntry: NavBackStackEntry? by navController.currentBackStackEntryAsState()
75 | val currentRoute: String? = navBackStackEntry?.destination?.route
76 |
77 | routes.forEach { (key, data) ->
78 | BottomBarItem(data, currentRoute, key, navController)
79 | }
80 | }
81 | }
82 |
83 | @Composable
84 | private fun RowScope.BottomBarItem(
85 | data: TabRouteData,
86 | currentRoute: String?,
87 | key: String,
88 | navController: NavHostController
89 | ) {
90 | val context: Context = LocalContext.current
91 |
92 | BottomNavigationItem(
93 | modifier = Modifier.background(Color.White),
94 | label = {
95 | val title: StringDesc = data.title ?: return@BottomNavigationItem
96 | Text(text = title.toString(context))
97 | },
98 | selected = currentRoute == key,
99 | selectedContentColor = Color.Black,
100 | unselectedContentColor = Color.Gray,
101 | onClick = {
102 | navController.navigate(key) {
103 | launchSingleTop = true
104 | restoreState = true
105 | }
106 | },
107 | icon = {
108 | val icon: ImageResource = data.icon ?: return@BottomNavigationItem
109 |
110 | Icon(
111 | painter = painterResource(id = icon.drawableResId),
112 | contentDescription = null
113 | )
114 | }
115 | )
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/ComposeViewController.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp
2 |
3 | import androidx.compose.runtime.BroadcastFrameClock
4 | import androidx.compose.runtime.Composable
5 | import app.cash.redwood.compose.RedwoodComposition
6 | import app.cash.redwood.widget.UIViewChildren
7 | import app.cash.redwood.widget.Widget
8 | import kotlinx.cinterop.ObjCAction
9 | import kotlinx.cinterop.convert
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.MainScope
12 | import kotlinx.coroutines.cancel
13 | import kotlinx.coroutines.plus
14 | import platform.Foundation.NSDefaultRunLoopMode
15 | import platform.Foundation.NSRunLoop
16 | import platform.Foundation.NSSelectorFromString
17 | import platform.QuartzCore.CADisplayLink
18 | import platform.UIKit.UIColor
19 | import platform.UIKit.UILayoutConstraintAxisVertical
20 | import platform.UIKit.UIStackView
21 | import platform.UIKit.UIStackViewAlignmentFill
22 | import platform.UIKit.UIStackViewDistributionFill
23 | import platform.UIKit.UIView
24 | import platform.UIKit.UIViewController
25 | import platform.UIKit.addSubview
26 | import platform.UIKit.backgroundColor
27 | import platform.UIKit.bottomAnchor
28 | import platform.UIKit.leadingAnchor
29 | import platform.UIKit.navigationController
30 | import platform.UIKit.navigationItem
31 | import platform.UIKit.safeAreaLayoutGuide
32 | import platform.UIKit.setTranslatesAutoresizingMaskIntoConstraints
33 | import platform.UIKit.tabBarController
34 | import platform.UIKit.topAnchor
35 | import platform.UIKit.widthAnchor
36 |
37 | internal class ComposeViewController(
38 | private val provider: Widget.Provider,
39 | private val isNavigationVisible: Boolean,
40 | private val compose: @Composable () -> Unit,
41 | ) : UIViewController(null, null) {
42 | private lateinit var displayLink: CADisplayLink
43 | private lateinit var delegate: RedwoodViewControllerDelegate
44 |
45 | override fun viewDidLoad() {
46 | super.viewDidLoad()
47 | val container = UIStackView()
48 | container.setAxis(UILayoutConstraintAxisVertical)
49 | container.setAlignment(UIStackViewAlignmentFill)
50 | container.setDistribution(UIStackViewDistributionFill)
51 | container.setTranslatesAutoresizingMaskIntoConstraints(false)
52 | container.backgroundColor = UIColor.whiteColor
53 |
54 | tabBarController?.navigationItem?.hidesBackButton = true
55 |
56 | view.addSubview(container)
57 | container.leadingAnchor.constraintEqualToAnchor(view.safeAreaLayoutGuide.leadingAnchor).active =
58 | true
59 | container.topAnchor.constraintEqualToAnchor(view.safeAreaLayoutGuide.topAnchor).active =
60 | true
61 | container.widthAnchor.constraintEqualToAnchor(view.safeAreaLayoutGuide.widthAnchor).active =
62 | true
63 | container.bottomAnchor.constraintEqualToAnchor(view.safeAreaLayoutGuide.bottomAnchor).active =
64 | true
65 |
66 | val delegate = RedwoodViewControllerDelegate(container, compose)
67 | this.delegate = delegate
68 | }
69 |
70 | override fun viewDidAppear(animated: Boolean) {
71 | super.viewDidAppear(animated)
72 |
73 | val displayLink = CADisplayLink.displayLinkWithTarget(
74 | target = this,
75 | selector = NSSelectorFromString("tickClock")
76 | )
77 | displayLink.addToRunLoop(NSRunLoop.currentRunLoop, NSDefaultRunLoopMode)
78 | this.displayLink = displayLink
79 | navigationController?.navigationBarHidden = isNavigationVisible.not()
80 | }
81 |
82 | @ObjCAction
83 | fun tickClock() {
84 | delegate.tickClock()
85 | }
86 |
87 | override fun viewDidDisappear(animated: Boolean) {
88 | super.viewDidDisappear(animated)
89 |
90 | displayLink.invalidate()
91 | }
92 |
93 | inner class RedwoodViewControllerDelegate(
94 | root: UIStackView,
95 | compose: @Composable () -> Unit
96 | ) {
97 | private val clock = BroadcastFrameClock()
98 | private val scope: CoroutineScope = MainScope() + clock
99 |
100 | init {
101 | val children = UIViewChildren(
102 | parent = root,
103 | insert = { view, index ->
104 | root.insertArrangedSubview(
105 | view,
106 | atIndex = index.convert()
107 | )
108 | }
109 | )
110 | val composition = RedwoodComposition(
111 | scope = scope,
112 | container = children,
113 | provider = provider
114 | )
115 | composition.setContent {
116 | compose.invoke()
117 | }
118 | }
119 |
120 | fun tickClock() {
121 | clock.sendFrame(0L) // Compose does not use frame time.
122 | }
123 |
124 | fun dispose() {
125 | scope.cancel()
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/Placeholder.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp
2 |
3 |
4 | const val LONG_TEXT: String =
5 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ultrices eros in cursus turpis massa tincidunt dui. Venenatis cras sed felis eget velit. Tincidunt praesent semper feugiat nibh sed pulvinar proin gravida hendrerit. Arcu bibendum at varius vel pharetra. Imperdiet sed euismod nisi porta lorem mollis. Phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet. Interdum varius sit amet mattis vulputate enim nulla aliquet. Amet massa vitae tortor condimentum lacinia. Quam id leo in vitae turpis massa sed elementum. Pellentesque eu tincidunt tortor aliquam nulla facilisi cras fermentum odio.\n" +
6 | "\n" +
7 | "Posuere ac ut consequat semper viverra nam. Est ante in nibh mauris cursus mattis molestie. Tortor consequat id porta nibh venenatis cras sed. Ut placerat orci nulla pellentesque dignissim enim sit. Luctus accumsan tortor posuere ac ut. Consequat interdum varius sit amet. Dui sapien eget mi proin sed libero enim sed faucibus. Ut consequat semper viverra nam. A pellentesque sit amet porttitor eget dolor. Massa sed elementum tempus egestas sed sed risus pretium quam. Quis risus sed vulputate odio ut enim. Tortor id aliquet lectus proin nibh. Orci porta non pulvinar neque laoreet suspendisse interdum. Ultrices tincidunt arcu non sodales neque sodales ut etiam sit. Eget lorem dolor sed viverra ipsum. Ultrices eros in cursus turpis massa. Nunc pulvinar sapien et ligula ullamcorper malesuada proin libero nunc. Pellentesque habitant morbi tristique senectus et netus et.\n" +
8 | "\n" +
9 | "Nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Amet luctus venenatis lectus magna fringilla urna porttitor. Convallis posuere morbi leo urna. Ornare massa eget egestas purus viverra accumsan in. Sed augue lacus viverra vitae congue eu consequat ac. Volutpat commodo sed egestas egestas fringilla phasellus faucibus. Dictum at tempor commodo ullamcorper a lacus vestibulum sed. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Nec feugiat nisl pretium fusce. Mi bibendum neque egestas congue quisque egestas. In nulla posuere sollicitudin aliquam ultrices sagittis orci. Nisi lacus sed viverra tellus in. Laoreet suspendisse interdum consectetur libero id faucibus nisl tincidunt. Massa placerat duis ultricies lacus sed. Sit amet facilisis magna etiam. Amet nulla facilisi morbi tempus iaculis urna id volutpat. Non tellus orci ac auctor.\n" +
10 | "\n" +
11 | "Maecenas pharetra convallis posuere morbi. Aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc sed. Et netus et malesuada fames. Augue lacus viverra vitae congue eu consequat. Id diam maecenas ultricies mi eget mauris. Pharetra convallis posuere morbi leo urna molestie at elementum. Vitae elementum curabitur vitae nunc sed velit. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Amet tellus cras adipiscing enim eu turpis egestas pretium aenean. Velit euismod in pellentesque massa placerat duis ultricies lacus. Potenti nullam ac tortor vitae purus. Velit laoreet id donec ultrices tincidunt arcu. Quis enim lobortis scelerisque fermentum dui faucibus. Eros in cursus turpis massa tincidunt dui ut ornare. Ridiculus mus mauris vitae ultricies leo integer malesuada. Dui accumsan sit amet nulla facilisi morbi.\n" +
12 | "\n" +
13 | "Quis imperdiet massa tincidunt nunc pulvinar sapien et. Enim nulla aliquet porttitor lacus luctus accumsan tortor. Nam libero justo laoreet sit amet cursus sit. Est ultricies integer quis auctor elit sed vulputate mi sit. Urna id volutpat lacus laoreet non curabitur gravida. Vitae elementum curabitur vitae nunc sed velit dignissim. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida cum. Condimentum id venenatis a condimentum vitae sapien pellentesque habitant morbi. Massa tempor nec feugiat nisl pretium fusce id velit ut. Sit amet est placerat in egestas. Aenean et tortor at risus viverra adipiscing at in tellus. Magna eget est lorem ipsum dolor sit amet. Viverra maecenas accumsan lacus vel facilisis volutpat. Eu consequat ac felis donec et odio pellentesque diam volutpat. Ac orci phasellus egestas tellus rutrum tellus pellentesque eu. Convallis tellus id interdum velit laoreet id donec ultrices."
14 |
15 | val NEWS_LIST = listOf>(
16 | Pair(
17 | "1 Сентября 2022 в 12:01",
18 | "Никого не смущает огромная популяция кенгуру, которых в австралии почти столько же, сколько и людей, проживающих там? "
19 | ),
20 | Pair(
21 | "2 Сентября 2022 в 12:01",
22 | "DICE подтвердила, что крупное обновление 3.2 для Battlefield 2042 появится на следующей неделе. "
23 | ),
24 | Pair(
25 | "3 Сентября 2022 в 12:01",
26 | "Журналисты из DSOGaming проанализировали очередную видеоигровую новинку. На этот раз их длинные руки дотянулись до экшена Forspoken от Square Enix. "
27 | ),
28 | Pair(
29 | "4 Сентября 2022 в 12:01",
30 | "Студия Arkane представила трейлер Redfall, посвящённый бонусам за предзаказ. "
31 | ),
32 | Pair(
33 | "5 Сентября 2022 в 12:01",
34 | "На этой неделе в Steam вновь стартовало несколько распродаж, а также начались бесплатные выходные в Scarlet Nexus"
35 | ),
36 | Pair(
37 | "6 Сентября 2022 в 12:01",
38 | "Обновленная The Witcher 3 доступна уже давно, однако CD Projekt RED продолжает выпускать различные трейлеры, чтобы еще лучше прорекламировать тайтл."
39 | ),
40 | Pair(
41 | "7 Сентября 2022 в 12:01",
42 | "Одним из неожиданных и приятных сюрпризов конференции Xbox Developer_Direct стал анонс Hi-Fi Rush — ритм-экшена от создателей The Evil Within."
43 | )
44 | )
45 |
46 | const val USR_NAME = "Иванов Петр"
47 |
48 | const val USER_AVATAR = "https://www.dierennieuws.nl/wp-content/uploads/2022/01/kat-in-sneeuw-800x445.jpg"
49 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/dev/icerock/redwoodapp/screens/PostsList.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.screens
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import app.cash.redwood.LayoutModifier
10 | import app.cash.redwood.layout.api.Constraint
11 | import app.cash.redwood.layout.api.MainAxisAlignment
12 | import app.cash.redwood.layout.api.Overflow
13 | import app.cash.redwood.layout.api.Padding
14 | import app.cash.redwood.layout.compose.Column
15 | import app.cash.redwood.layout.compose.Row
16 | import dev.icerock.moko.graphics.Color
17 | import dev.icerock.moko.resources.desc.desc
18 | import org.example.library.MR
19 | import dev.icerock.redwood.schema.TextType
20 | import dev.icerock.redwood.schema.compose.Card
21 | import dev.icerock.redwood.schema.compose.ImageButton
22 | import dev.icerock.redwood.schema.compose.Space
23 | import dev.icerock.redwood.schema.compose.Text
24 | import dev.icerock.redwoodapp.NEWS_LIST
25 | import dev.icerock.redwoodapp.navigation.ScreenSettings
26 | import dev.icerock.redwoodapp.screens.entity.CardItem
27 |
28 | @Composable
29 | fun PostsList(screenSettings: ScreenSettings, routeToCreate: (String, String) -> Unit) {
30 | LaunchedEffect(screenSettings){
31 | screenSettings.setTitle("Posts".desc())
32 | }
33 |
34 | val itemsList by remember {
35 | mutableStateOf(
36 | NEWS_LIST.mapIndexed { index, it ->
37 | CardItem(
38 | data = it.first,
39 | text = it.second,
40 | isLike = index % 2 == 0,
41 | ) {
42 | routeToCreate(it.first, it.second)
43 | }
44 | }
45 | )
46 | }
47 |
48 | Column(
49 | padding = Padding(horizontal = 16),
50 | width = Constraint.Fill,
51 | height = Constraint.Fill
52 | ) {
53 | Column(
54 | width = Constraint.Fill,
55 | overflow = Overflow.Scroll
56 | ) {
57 | for (cardItem in itemsList) {
58 | Item(
59 | data = cardItem.data,
60 | text = cardItem.text,
61 | isLike = cardItem.isLike,
62 | onClick = cardItem.onClick
63 | )
64 | }
65 | Space(
66 | background = Color(0x00000000),
67 | width = 0,
68 | height = 80
69 | )
70 | }
71 | }
72 | }
73 |
74 | @Composable
75 | fun Item(data: String, text: String, isLike: Boolean, onClick: () -> Unit) {
76 | Column(padding = Padding(top = 16)) {
77 | Card(
78 | onClick = {
79 | onClick()
80 | },
81 | child = {
82 | Column(
83 | padding = Padding(top = 16, start = 16, bottom = 8, end = 16)
84 | ) {
85 | Text(
86 | text = data,
87 | isSingleLine = true,
88 | textType = TextType.Secondary,
89 | layoutModifier = LayoutModifier.padding(Padding(bottom = 0))
90 | )
91 | Text(
92 | text = text,
93 | isSingleLine = false,
94 | textType = TextType.Primary,
95 | layoutModifier = LayoutModifier.padding(Padding(bottom = 0))
96 | )
97 | Row(
98 | width = Constraint.Fill,
99 | horizontalAlignment = MainAxisAlignment.End
100 | ) {
101 | var like: Int by remember { mutableStateOf(16) }
102 | var dislike: Int by remember { mutableStateOf(7) }
103 | var isLiked: Boolean? by remember { mutableStateOf(isLike) }
104 | ImageButton(
105 | text = like.toString().desc(),
106 | icon = if (isLiked == true) MR.images.like_cliked else MR.images.like,
107 | isClicked = isLiked == true,
108 | onClick = {
109 | when (isLiked) {
110 | null -> {
111 | like += 1
112 | isLiked = true
113 | }
114 | true -> {
115 | like -= 1
116 | isLiked = null
117 | }
118 | else -> {
119 | like += 1
120 | dislike -= 1
121 | isLiked = true
122 | }
123 | }
124 |
125 | },
126 | layoutModifier = LayoutModifier.padding(Padding(end = 8))
127 | )
128 | ImageButton(
129 | text = dislike.toString().desc(),
130 | icon = if (isLiked == false) MR.images.dislike_cliked else MR.images.dislike,
131 | isClicked = isLiked == false,
132 | onClick = {
133 | when (isLiked) {
134 | null -> {
135 | dislike += 1
136 | isLiked = false
137 | }
138 | false -> {
139 | dislike -= 1
140 | isLiked = null
141 | }
142 | else -> {
143 | like -= 1
144 | dislike += 1
145 | isLiked = false
146 | }
147 | }
148 | },
149 | )
150 | }
151 | }
152 | })
153 | }
154 | }
155 |
156 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/dev/icerock/redwoodapp/navigation/FlatNavigation.kt:
--------------------------------------------------------------------------------
1 | package dev.icerock.redwoodapp.navigation
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material.Icon
7 | import androidx.compose.material.IconButton
8 | import androidx.compose.material.Scaffold
9 | import androidx.compose.material.Text
10 | import androidx.compose.material.TopAppBar
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.graphics.Color
19 | import androidx.compose.ui.platform.LocalContext
20 | import androidx.compose.ui.res.painterResource
21 | import androidx.compose.ui.unit.dp
22 | import androidx.navigation.NamedNavArgument
23 | import androidx.navigation.NavHostController
24 | import androidx.navigation.NavType
25 | import androidx.navigation.compose.NavHost
26 | import androidx.navigation.compose.composable
27 | import androidx.navigation.compose.rememberNavController
28 | import androidx.navigation.navArgument
29 | import app.cash.redwood.widget.Widget
30 | import dev.icerock.moko.resources.desc.StringDesc
31 | import dev.icerock.moko.resources.desc.desc
32 | import dev.icerock.redwoodapp.shared.R
33 |
34 | data class FlatNavigation(
35 | val startDestination: String,
36 | val routes: MutableMap,
37 | val navBarVisibility: MutableMap,
38 | val screenSettings: ScreenSettingsImpl
39 | ) : NavigationHost {
40 |
41 | @Composable
42 | override fun Render(provider: Widget.Provider<() -> Unit>) {
43 | val navController: NavHostController = rememberNavController()
44 |
45 | val nav: Navigator = remember(navController) {
46 | object : Navigator {
47 | override fun navigate(uri: String) {
48 | navController.navigate(uri)
49 | }
50 |
51 | override fun popBackStack() {
52 | navController.popBackStack()
53 | }
54 | }
55 | }
56 |
57 | NavHost(
58 | navController = navController,
59 | startDestination = startDestination
60 | ) {
61 | routes.forEach { item ->
62 | val argsNameList: List = item.key.getArgs()// + item.key.getParams()
63 | composable(
64 | route = item.key,
65 | arguments = if (argsNameList.isEmpty()) {
66 | listOf()
67 | } else {
68 | val result: MutableList = mutableListOf()
69 | argsNameList.forEach {
70 | result.add(
71 | navArgument(it) { type = NavType.StringType }
72 | )
73 | }
74 | result
75 | }
76 | ) { entry ->
77 | Scaffold(
78 | topBar = {
79 | if (navBarVisibility[navController.currentDestination?.route] == true) {
80 | TopAppBar(
81 | backgroundColor = Color.White,
82 | contentColor = Color.Black,
83 | elevation = 2.dp,
84 | title = {
85 | Text(
86 | text = screenSettings.text.toString(LocalContext.current)
87 | )
88 | },
89 | navigationIcon = {
90 | if (navController.backQueue.size != 2) {
91 | IconButton(
92 | onClick = {
93 | navController.popBackStack()
94 | }
95 | ) {
96 | Icon(
97 | painter = painterResource(id = R.drawable.arrow_left),
98 | contentDescription = null
99 | )
100 | }
101 | }
102 | }
103 | )
104 | }
105 | }
106 | ) { innerPadding ->
107 | Box(modifier = androidx.compose.ui.Modifier.padding(innerPadding)) {
108 | val argsMap = mutableMapOf()
109 |
110 | argsNameList.forEach {
111 | val accountType =
112 | if (argsNameList.isEmpty()) null else entry.arguments?.getString(
113 | it
114 | )
115 | if (accountType != null) {
116 | argsMap[it] = accountType
117 | }
118 | }
119 |
120 | val tmp = entry.arguments?.toString()?.substringBefore('}')
121 | val params = tmp?.substringAfter('?')
122 | params?.split('&')?.forEach {
123 | argsMap[it.substringBefore('=')] = it.substringAfter('=')
124 | }
125 | item.value(provider, nav, argsMap)
126 | }
127 | }
128 | }
129 | }
130 | }
131 | }
132 | }
133 |
134 | fun String.getArgs(): List {
135 | val result = mutableListOf()
136 |
137 | val tmp = this.substringBefore('?').split('/')
138 | if (tmp.isEmpty() || tmp.size == 1) return listOf()
139 | tmp.forEach {
140 | if (it.contains('}')) {
141 | result.add(
142 | it.removePrefix("{")
143 | .removeSuffix("}")
144 | )
145 | }
146 | }
147 |
148 | return result
149 | }
150 |
151 | fun String.getParams(): List {
152 | val result = mutableListOf()
153 |
154 | val tmp = this.substringAfter('?').split('&')
155 | if (tmp.isEmpty() || tmp.size == 1) return listOf()
156 | tmp.forEach {
157 | if (it.contains('}')) {
158 | result.add(
159 | it.removePrefix("{")
160 | .removeSuffix("}")
161 | )
162 | }
163 | }
164 |
165 | return result
166 | }
167 |
168 |
169 | class ScreenSettingsImpl() : ScreenSettings {
170 |
171 | var text: StringDesc by mutableStateOf("".desc())
172 | override fun setTitle(title: StringDesc) {
173 | this.text = title
174 | }
175 |
176 | }
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 225D67DA2971B5F000A27CD1 /* IosWidgetFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67D92971B5F000A27CD1 /* IosWidgetFactory.swift */; };
11 | 225D67DC2971B63000A27CD1 /* IosWidgetButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67DB2971B63000A27CD1 /* IosWidgetButton.swift */; };
12 | 225D67DE2971B65D00A27CD1 /* IosWidgetText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67DD2971B65D00A27CD1 /* IosWidgetText.swift */; };
13 | 225D67E02971B69200A27CD1 /* IosWidgetImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67DF2971B69200A27CD1 /* IosWidgetImage.swift */; };
14 | 225D67E22971B6B400A27CD1 /* IosWidgetTextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67E12971B6B400A27CD1 /* IosWidgetTextInput.swift */; };
15 | 225D67E42971B6E500A27CD1 /* IosWidgetCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67E32971B6E500A27CD1 /* IosWidgetCard.swift */; };
16 | 225D67E62971B71100A27CD1 /* IosImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67E52971B71100A27CD1 /* IosImageButton.swift */; };
17 | 225D67E82971B74100A27CD1 /* IosStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225D67E72971B74100A27CD1 /* IosStack.swift */; };
18 | 227ECA252957564C00A4CBC8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227ECA242957564C00A4CBC8 /* AppDelegate.swift */; };
19 | 227ECA272957564C00A4CBC8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227ECA262957564C00A4CBC8 /* SceneDelegate.swift */; };
20 | 227ECA2E2957564D00A4CBC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 227ECA2D2957564D00A4CBC8 /* Assets.xcassets */; };
21 | 227ECA312957564D00A4CBC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 227ECA2F2957564D00A4CBC8 /* LaunchScreen.storyboard */; };
22 | 449B364B2983D6960008710D /* IosWidgetSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449B364A2983D6960008710D /* IosWidgetSpace.swift */; };
23 | 617E7C3EEB7B6042DA8DC122 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7F363DBFAC730C3510B9993 /* Pods_iosApp.framework */; };
24 | /* End PBXBuildFile section */
25 |
26 | /* Begin PBXFileReference section */
27 | 225D67D92971B5F000A27CD1 /* IosWidgetFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosWidgetFactory.swift; sourceTree = ""; };
28 | 225D67DB2971B63000A27CD1 /* IosWidgetButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosWidgetButton.swift; sourceTree = ""; };
29 | 225D67DD2971B65D00A27CD1 /* IosWidgetText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosWidgetText.swift; sourceTree = ""; };
30 | 225D67DF2971B69200A27CD1 /* IosWidgetImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosWidgetImage.swift; sourceTree = ""; };
31 | 225D67E12971B6B400A27CD1 /* IosWidgetTextInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosWidgetTextInput.swift; sourceTree = ""; };
32 | 225D67E32971B6E500A27CD1 /* IosWidgetCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosWidgetCard.swift; sourceTree = ""; };
33 | 225D67E52971B71100A27CD1 /* IosImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosImageButton.swift; sourceTree = ""; };
34 | 225D67E72971B74100A27CD1 /* IosStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosStack.swift; sourceTree = ""; };
35 | 227ECA222957564C00A4CBC8 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
36 | 227ECA242957564C00A4CBC8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 227ECA262957564C00A4CBC8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
38 | 227ECA2D2957564D00A4CBC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
39 | 227ECA302957564D00A4CBC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
40 | 227ECA322957564D00A4CBC8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | 449B364A2983D6960008710D /* IosWidgetSpace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosWidgetSpace.swift; sourceTree = ""; };
42 | C7F363DBFAC730C3510B9993 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
43 | D1D27B09DFB8AAD8A46FED88 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
44 | E3291FEB2BF60736ED0C2E34 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 227ECA1F2957564C00A4CBC8 /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | 617E7C3EEB7B6042DA8DC122 /* Pods_iosApp.framework in Frameworks */,
53 | );
54 | runOnlyForDeploymentPostprocessing = 0;
55 | };
56 | /* End PBXFrameworksBuildPhase section */
57 |
58 | /* Begin PBXGroup section */
59 | 225D67D82971B5DC00A27CD1 /* Widgets */ = {
60 | isa = PBXGroup;
61 | children = (
62 | 225D67D92971B5F000A27CD1 /* IosWidgetFactory.swift */,
63 | 225D67DB2971B63000A27CD1 /* IosWidgetButton.swift */,
64 | 225D67DD2971B65D00A27CD1 /* IosWidgetText.swift */,
65 | 225D67DF2971B69200A27CD1 /* IosWidgetImage.swift */,
66 | 225D67E12971B6B400A27CD1 /* IosWidgetTextInput.swift */,
67 | 225D67E32971B6E500A27CD1 /* IosWidgetCard.swift */,
68 | 225D67E52971B71100A27CD1 /* IosImageButton.swift */,
69 | 225D67E72971B74100A27CD1 /* IosStack.swift */,
70 | 449B364A2983D6960008710D /* IosWidgetSpace.swift */,
71 | );
72 | path = Widgets;
73 | sourceTree = "";
74 | };
75 | 227ECA232957564C00A4CBC8 /* iosApp */ = {
76 | isa = PBXGroup;
77 | children = (
78 | 225D67D82971B5DC00A27CD1 /* Widgets */,
79 | 227ECA242957564C00A4CBC8 /* AppDelegate.swift */,
80 | 227ECA262957564C00A4CBC8 /* SceneDelegate.swift */,
81 | 227ECA2D2957564D00A4CBC8 /* Assets.xcassets */,
82 | 227ECA2F2957564D00A4CBC8 /* LaunchScreen.storyboard */,
83 | 227ECA322957564D00A4CBC8 /* Info.plist */,
84 | );
85 | path = iosApp;
86 | sourceTree = "";
87 | };
88 | 3C21008B3D36E73BD2F7A4B2 /* Frameworks */ = {
89 | isa = PBXGroup;
90 | children = (
91 | C7F363DBFAC730C3510B9993 /* Pods_iosApp.framework */,
92 | );
93 | name = Frameworks;
94 | sourceTree = "";
95 | };
96 | 7555FF72242A565900829871 = {
97 | isa = PBXGroup;
98 | children = (
99 | 227ECA232957564C00A4CBC8 /* iosApp */,
100 | 7555FF7C242A565900829871 /* Products */,
101 | BF56A6FBFB5F6891C39BB1EB /* Pods */,
102 | 3C21008B3D36E73BD2F7A4B2 /* Frameworks */,
103 | );
104 | sourceTree = "";
105 | };
106 | 7555FF7C242A565900829871 /* Products */ = {
107 | isa = PBXGroup;
108 | children = (
109 | 227ECA222957564C00A4CBC8 /* iosApp.app */,
110 | );
111 | name = Products;
112 | sourceTree = "";
113 | };
114 | BF56A6FBFB5F6891C39BB1EB /* Pods */ = {
115 | isa = PBXGroup;
116 | children = (
117 | D1D27B09DFB8AAD8A46FED88 /* Pods-iosApp.debug.xcconfig */,
118 | E3291FEB2BF60736ED0C2E34 /* Pods-iosApp.release.xcconfig */,
119 | );
120 | path = Pods;
121 | sourceTree = "";
122 | };
123 | /* End PBXGroup section */
124 |
125 | /* Begin PBXNativeTarget section */
126 | 227ECA212957564C00A4CBC8 /* iosApp */ = {
127 | isa = PBXNativeTarget;
128 | buildConfigurationList = 227ECA332957564D00A4CBC8 /* Build configuration list for PBXNativeTarget "iosApp" */;
129 | buildPhases = (
130 | 2971D91016EFB5BC6C39CA0D /* [CP] Check Pods Manifest.lock */,
131 | 227ECA1E2957564C00A4CBC8 /* Sources */,
132 | 227ECA1F2957564C00A4CBC8 /* Frameworks */,
133 | 227ECA202957564C00A4CBC8 /* Resources */,
134 | 5901CC8F25A8A56A1E5F1D81 /* [CP] Embed Pods Frameworks */,
135 | );
136 | buildRules = (
137 | );
138 | dependencies = (
139 | );
140 | name = iosApp;
141 | productName = iosApp;
142 | productReference = 227ECA222957564C00A4CBC8 /* iosApp.app */;
143 | productType = "com.apple.product-type.application";
144 | };
145 | /* End PBXNativeTarget section */
146 |
147 | /* Begin PBXProject section */
148 | 7555FF73242A565900829871 /* Project object */ = {
149 | isa = PBXProject;
150 | attributes = {
151 | LastSwiftUpdateCheck = 1410;
152 | LastUpgradeCheck = 1130;
153 | ORGANIZATIONNAME = "IceRock Development";
154 | TargetAttributes = {
155 | 227ECA212957564C00A4CBC8 = {
156 | CreatedOnToolsVersion = 14.1;
157 | };
158 | };
159 | };
160 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
161 | compatibilityVersion = "Xcode 9.3";
162 | developmentRegion = en;
163 | hasScannedForEncodings = 0;
164 | knownRegions = (
165 | en,
166 | Base,
167 | );
168 | mainGroup = 7555FF72242A565900829871;
169 | productRefGroup = 7555FF7C242A565900829871 /* Products */;
170 | projectDirPath = "";
171 | projectRoot = "";
172 | targets = (
173 | 227ECA212957564C00A4CBC8 /* iosApp */,
174 | );
175 | };
176 | /* End PBXProject section */
177 |
178 | /* Begin PBXResourcesBuildPhase section */
179 | 227ECA202957564C00A4CBC8 /* Resources */ = {
180 | isa = PBXResourcesBuildPhase;
181 | buildActionMask = 2147483647;
182 | files = (
183 | 227ECA312957564D00A4CBC8 /* LaunchScreen.storyboard in Resources */,
184 | 227ECA2E2957564D00A4CBC8 /* Assets.xcassets in Resources */,
185 | );
186 | runOnlyForDeploymentPostprocessing = 0;
187 | };
188 | /* End PBXResourcesBuildPhase section */
189 |
190 | /* Begin PBXShellScriptBuildPhase section */
191 | 2971D91016EFB5BC6C39CA0D /* [CP] Check Pods Manifest.lock */ = {
192 | isa = PBXShellScriptBuildPhase;
193 | buildActionMask = 2147483647;
194 | files = (
195 | );
196 | inputFileListPaths = (
197 | );
198 | inputPaths = (
199 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
200 | "${PODS_ROOT}/Manifest.lock",
201 | );
202 | name = "[CP] Check Pods Manifest.lock";
203 | outputFileListPaths = (
204 | );
205 | outputPaths = (
206 | "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
207 | );
208 | runOnlyForDeploymentPostprocessing = 0;
209 | shellPath = /bin/sh;
210 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
211 | showEnvVarsInLog = 0;
212 | };
213 | 5901CC8F25A8A56A1E5F1D81 /* [CP] Embed Pods Frameworks */ = {
214 | isa = PBXShellScriptBuildPhase;
215 | buildActionMask = 2147483647;
216 | files = (
217 | );
218 | inputFileListPaths = (
219 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
220 | );
221 | name = "[CP] Embed Pods Frameworks";
222 | outputFileListPaths = (
223 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
224 | );
225 | runOnlyForDeploymentPostprocessing = 0;
226 | shellPath = /bin/sh;
227 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n";
228 | showEnvVarsInLog = 0;
229 | };
230 | /* End PBXShellScriptBuildPhase section */
231 |
232 | /* Begin PBXSourcesBuildPhase section */
233 | 227ECA1E2957564C00A4CBC8 /* Sources */ = {
234 | isa = PBXSourcesBuildPhase;
235 | buildActionMask = 2147483647;
236 | files = (
237 | 225D67DA2971B5F000A27CD1 /* IosWidgetFactory.swift in Sources */,
238 | 225D67E02971B69200A27CD1 /* IosWidgetImage.swift in Sources */,
239 | 225D67DE2971B65D00A27CD1 /* IosWidgetText.swift in Sources */,
240 | 225D67E42971B6E500A27CD1 /* IosWidgetCard.swift in Sources */,
241 | 225D67E82971B74100A27CD1 /* IosStack.swift in Sources */,
242 | 449B364B2983D6960008710D /* IosWidgetSpace.swift in Sources */,
243 | 225D67DC2971B63000A27CD1 /* IosWidgetButton.swift in Sources */,
244 | 227ECA252957564C00A4CBC8 /* AppDelegate.swift in Sources */,
245 | 227ECA272957564C00A4CBC8 /* SceneDelegate.swift in Sources */,
246 | 225D67E22971B6B400A27CD1 /* IosWidgetTextInput.swift in Sources */,
247 | 225D67E62971B71100A27CD1 /* IosImageButton.swift in Sources */,
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | };
251 | /* End PBXSourcesBuildPhase section */
252 |
253 | /* Begin PBXVariantGroup section */
254 | 227ECA2F2957564D00A4CBC8 /* LaunchScreen.storyboard */ = {
255 | isa = PBXVariantGroup;
256 | children = (
257 | 227ECA302957564D00A4CBC8 /* Base */,
258 | );
259 | name = LaunchScreen.storyboard;
260 | sourceTree = "";
261 | };
262 | /* End PBXVariantGroup section */
263 |
264 | /* Begin XCBuildConfiguration section */
265 | 227ECA342957564D00A4CBC8 /* Debug */ = {
266 | isa = XCBuildConfiguration;
267 | baseConfigurationReference = D1D27B09DFB8AAD8A46FED88 /* Pods-iosApp.debug.xcconfig */;
268 | buildSettings = {
269 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
270 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
272 | CODE_SIGN_STYLE = Automatic;
273 | CURRENT_PROJECT_VERSION = 1;
274 | DEBUG_INFORMATION_FORMAT = dwarf;
275 | DEVELOPMENT_TEAM = 668D83X95V;
276 | GENERATE_INFOPLIST_FILE = YES;
277 | INFOPLIST_FILE = iosApp/Info.plist;
278 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
279 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
280 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
281 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
282 | IPHONEOS_DEPLOYMENT_TARGET = 15.4;
283 | LD_RUNPATH_SEARCH_PATHS = (
284 | "$(inherited)",
285 | "@executable_path/Frameworks",
286 | );
287 | MARKETING_VERSION = 1.0;
288 | PRODUCT_BUNDLE_IDENTIFIER = dev.icerock.redwood.iosApp;
289 | PRODUCT_NAME = "$(TARGET_NAME)";
290 | SWIFT_EMIT_LOC_STRINGS = YES;
291 | SWIFT_VERSION = 5.0;
292 | TARGETED_DEVICE_FAMILY = "1,2";
293 | };
294 | name = Debug;
295 | };
296 | 227ECA352957564D00A4CBC8 /* Release */ = {
297 | isa = XCBuildConfiguration;
298 | baseConfigurationReference = E3291FEB2BF60736ED0C2E34 /* Pods-iosApp.release.xcconfig */;
299 | buildSettings = {
300 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
301 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
303 | CODE_SIGN_STYLE = Automatic;
304 | CURRENT_PROJECT_VERSION = 1;
305 | DEVELOPMENT_TEAM = 668D83X95V;
306 | GENERATE_INFOPLIST_FILE = YES;
307 | INFOPLIST_FILE = iosApp/Info.plist;
308 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
309 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
310 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
311 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
312 | IPHONEOS_DEPLOYMENT_TARGET = 15.4;
313 | LD_RUNPATH_SEARCH_PATHS = (
314 | "$(inherited)",
315 | "@executable_path/Frameworks",
316 | );
317 | MARKETING_VERSION = 1.0;
318 | PRODUCT_BUNDLE_IDENTIFIER = dev.icerock.redwood.iosApp;
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | SWIFT_EMIT_LOC_STRINGS = YES;
321 | SWIFT_VERSION = 5.0;
322 | TARGETED_DEVICE_FAMILY = "1,2";
323 | };
324 | name = Release;
325 | };
326 | 7555FFA3242A565B00829871 /* Debug */ = {
327 | isa = XCBuildConfiguration;
328 | buildSettings = {
329 | ALWAYS_SEARCH_USER_PATHS = NO;
330 | CLANG_ANALYZER_NONNULL = YES;
331 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
333 | CLANG_CXX_LIBRARY = "libc++";
334 | CLANG_ENABLE_MODULES = YES;
335 | CLANG_ENABLE_OBJC_ARC = YES;
336 | CLANG_ENABLE_OBJC_WEAK = YES;
337 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
338 | CLANG_WARN_BOOL_CONVERSION = YES;
339 | CLANG_WARN_COMMA = YES;
340 | CLANG_WARN_CONSTANT_CONVERSION = YES;
341 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
342 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
343 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
344 | CLANG_WARN_EMPTY_BODY = YES;
345 | CLANG_WARN_ENUM_CONVERSION = YES;
346 | CLANG_WARN_INFINITE_RECURSION = YES;
347 | CLANG_WARN_INT_CONVERSION = YES;
348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
349 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
350 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
351 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
352 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
354 | CLANG_WARN_STRICT_PROTOTYPES = YES;
355 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
356 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
357 | CLANG_WARN_UNREACHABLE_CODE = YES;
358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
359 | COPY_PHASE_STRIP = NO;
360 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
361 | ENABLE_STRICT_OBJC_MSGSEND = YES;
362 | ENABLE_TESTABILITY = YES;
363 | GCC_C_LANGUAGE_STANDARD = gnu11;
364 | GCC_DYNAMIC_NO_PIC = NO;
365 | GCC_NO_COMMON_BLOCKS = YES;
366 | GCC_OPTIMIZATION_LEVEL = 0;
367 | GCC_PREPROCESSOR_DEFINITIONS = (
368 | "DEBUG=1",
369 | "$(inherited)",
370 | );
371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
373 | GCC_WARN_UNDECLARED_SELECTOR = YES;
374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
375 | GCC_WARN_UNUSED_FUNCTION = YES;
376 | GCC_WARN_UNUSED_VARIABLE = YES;
377 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
378 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
379 | MTL_FAST_MATH = YES;
380 | ONLY_ACTIVE_ARCH = YES;
381 | SDKROOT = iphoneos;
382 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
383 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
384 | };
385 | name = Debug;
386 | };
387 | 7555FFA4242A565B00829871 /* Release */ = {
388 | isa = XCBuildConfiguration;
389 | buildSettings = {
390 | ALWAYS_SEARCH_USER_PATHS = NO;
391 | CLANG_ANALYZER_NONNULL = YES;
392 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
394 | CLANG_CXX_LIBRARY = "libc++";
395 | CLANG_ENABLE_MODULES = YES;
396 | CLANG_ENABLE_OBJC_ARC = YES;
397 | CLANG_ENABLE_OBJC_WEAK = YES;
398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
399 | CLANG_WARN_BOOL_CONVERSION = YES;
400 | CLANG_WARN_COMMA = YES;
401 | CLANG_WARN_CONSTANT_CONVERSION = YES;
402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
404 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
405 | CLANG_WARN_EMPTY_BODY = YES;
406 | CLANG_WARN_ENUM_CONVERSION = YES;
407 | CLANG_WARN_INFINITE_RECURSION = YES;
408 | CLANG_WARN_INT_CONVERSION = YES;
409 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
410 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
411 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
412 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
413 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
415 | CLANG_WARN_STRICT_PROTOTYPES = YES;
416 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
417 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
418 | CLANG_WARN_UNREACHABLE_CODE = YES;
419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
420 | COPY_PHASE_STRIP = NO;
421 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
422 | ENABLE_NS_ASSERTIONS = NO;
423 | ENABLE_STRICT_OBJC_MSGSEND = YES;
424 | GCC_C_LANGUAGE_STANDARD = gnu11;
425 | GCC_NO_COMMON_BLOCKS = YES;
426 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
427 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
428 | GCC_WARN_UNDECLARED_SELECTOR = YES;
429 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
430 | GCC_WARN_UNUSED_FUNCTION = YES;
431 | GCC_WARN_UNUSED_VARIABLE = YES;
432 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
433 | MTL_ENABLE_DEBUG_INFO = NO;
434 | MTL_FAST_MATH = YES;
435 | SDKROOT = iphoneos;
436 | SWIFT_COMPILATION_MODE = wholemodule;
437 | SWIFT_OPTIMIZATION_LEVEL = "-O";
438 | VALIDATE_PRODUCT = YES;
439 | };
440 | name = Release;
441 | };
442 | /* End XCBuildConfiguration section */
443 |
444 | /* Begin XCConfigurationList section */
445 | 227ECA332957564D00A4CBC8 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
446 | isa = XCConfigurationList;
447 | buildConfigurations = (
448 | 227ECA342957564D00A4CBC8 /* Debug */,
449 | 227ECA352957564D00A4CBC8 /* Release */,
450 | );
451 | defaultConfigurationIsVisible = 0;
452 | defaultConfigurationName = Release;
453 | };
454 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
455 | isa = XCConfigurationList;
456 | buildConfigurations = (
457 | 7555FFA3242A565B00829871 /* Debug */,
458 | 7555FFA4242A565B00829871 /* Release */,
459 | );
460 | defaultConfigurationIsVisible = 0;
461 | defaultConfigurationName = Release;
462 | };
463 | /* End XCConfigurationList section */
464 | };
465 | rootObject = 7555FF73242A565900829871 /* Project object */;
466 | }
467 |
--------------------------------------------------------------------------------