├── .gitignore
├── .idea
├── .gitignore
├── gradle.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.md
├── assets
└── hero.jpeg
├── build.gradle.kts
├── composeApp
├── build.gradle.kts
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ ├── kotlin
│ │ ├── com
│ │ │ ├── builtwithpaper
│ │ │ │ └── ContainerSize.android.kt
│ │ │ └── example
│ │ │ │ └── MainActivity.kt
│ │ └── material3
│ │ │ ├── Ripple.kt
│ │ │ └── tokens
│ │ │ └── StateTokens.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ └── strings.xml
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ ├── builtwithpaper
│ │ ├── BottomNavigation.kt
│ │ ├── Buttons.kt
│ │ ├── Card.kt
│ │ ├── CheckBox.kt
│ │ ├── ContainerSize.kt
│ │ ├── Modifier.kt
│ │ ├── ProgressBar.kt
│ │ ├── RadioButton.kt
│ │ ├── ScreenWidthBreakpoint.kt
│ │ ├── Slider.kt
│ │ ├── SocialIcons.kt
│ │ ├── Text.kt
│ │ ├── TextField.kt
│ │ ├── TextInput.kt
│ │ ├── Theme.kt
│ │ ├── Toggle.kt
│ │ └── TopAppBar.kt
│ │ └── example
│ │ ├── AddContact.kt
│ │ ├── App.kt
│ │ ├── Contacts.kt
│ │ ├── Conversation.kt
│ │ ├── Conversations.kt
│ │ ├── Profile.kt
│ │ ├── Search.kt
│ │ └── Settings.kt
│ └── iosMain
│ └── kotlin
│ └── com
│ ├── builtwithpaper
│ ├── ContainerSize.ios.kt
│ └── LightIndication.kt
│ └── example
│ └── MainViewController.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── iosApp
├── Configuration
│ └── Config.xcconfig
├── iosApp.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
└── iosApp
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ └── app-icon-1024.png
│ └── Contents.json
│ ├── ContentView.swift
│ ├── Info.plist
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── iOSApp.swift
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | **/build/
4 | xcuserdata
5 | !src/**/build/
6 | local.properties
7 | kotlin-js-store/
8 | .DS_Store
9 | captures
10 | .externalNativeBuild
11 | .cxx
12 | *.xcodeproj/*
13 | !*.xcodeproj/project.pbxproj
14 | !*.xcodeproj/xcshareddata/
15 | !*.xcodeproj/project.xcworkspace/
16 | !*.xcworkspace/contents.xcworkspacedata
17 | **/xcshareddata/WorkspaceSettings.xcsettings
18 | venv/
19 | .cache/
20 | .kotlin/
21 |
22 | # CMake
23 | cmake-build-*/
24 |
25 | # File-based project format
26 | *.iws
27 |
28 | # IntelliJ
29 | out/
30 |
31 | state_ui.json
32 | templates.json
33 | keyValueStore.txt
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Composable Horizons
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bubbles
2 |
3 | This app was designed and exported entirely via Paper.
4 |
5 | No code was written by hand. You can [customize this app by clicking here](https://builtwithpaper.com/app/?app_template=bubbles).
6 |
7 | 
8 |
9 | ## Project Structure
10 |
11 | There are 3 important folders in `composeApp/src`:
12 |
13 | - commonMain: Contains the shared sourceSet across all targets. This is where your designs live.
14 | - androidMain: Contains Android specific theming, such as the Material RippleEffect.
15 | - iosMain: iOS specific theming such as the light highlight.
16 |
17 | ## Supported Targets
18 |
19 | Paper currently exports to iOS and Android
--------------------------------------------------------------------------------
/assets/hero.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/assets/hero.jpeg
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | // this is necessary to avoid the plugins to be loaded multiple times
3 | // in each subproject's classloader
4 | alias(libs.plugins.androidApplication) apply false
5 | alias(libs.plugins.composeMultiplatform) apply false
6 | alias(libs.plugins.composeCompiler) apply false
7 | alias(libs.plugins.kotlinMultiplatform) apply false
8 | }
--------------------------------------------------------------------------------
/composeApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
3 |
4 | plugins {
5 | alias(libs.plugins.kotlinMultiplatform)
6 | alias(libs.plugins.androidApplication)
7 | alias(libs.plugins.composeMultiplatform)
8 | alias(libs.plugins.composeCompiler)
9 | }
10 |
11 | kotlin {
12 | androidTarget {
13 | @OptIn(ExperimentalKotlinGradlePluginApi::class)
14 | compilerOptions {
15 | jvmTarget.set(JvmTarget.JVM_11)
16 | }
17 | }
18 |
19 | listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
20 | iosTarget.binaries.framework {
21 | baseName = "ComposeApp"
22 | isStatic = true
23 | }
24 | }
25 |
26 | sourceSets {
27 | commonMain.dependencies {
28 | implementation(compose.runtime)
29 | implementation(compose.foundation)
30 | implementation(compose.ui)
31 | implementation(compose.components.resources)
32 | implementation(compose.components.uiToolingPreview)
33 | implementation(libs.androidx.lifecycle.viewmodel)
34 | implementation(libs.androidx.lifecycle.runtime.compose)
35 | implementation(libs.navigation.compose)
36 | implementation(libs.compose.unstyled)
37 |
38 | implementation(libs.addcoilmultiplatform)
39 | implementation(libs.icons.lucide)
40 | }
41 |
42 | androidMain.dependencies {
43 | implementation(compose.preview)
44 | implementation(libs.androidx.activity.compose)
45 | implementation(libs.androidx.material.ripple)
46 | implementation("androidx.window:window:1.3.0")
47 | }
48 | }
49 | }
50 |
51 | android {
52 | namespace = "com.example"
53 | compileSdk = libs.versions.android.compileSdk.get().toInt()
54 |
55 | defaultConfig {
56 | applicationId = "com.example"
57 | minSdk = libs.versions.android.minSdk.get().toInt()
58 | targetSdk = libs.versions.android.targetSdk.get().toInt()
59 | versionCode = 1
60 | versionName = "1.0"
61 | }
62 | packaging {
63 | resources {
64 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
65 | }
66 | }
67 | buildTypes {
68 | getByName("release") {
69 | isMinifyEnabled = false
70 | }
71 | }
72 | compileOptions {
73 | sourceCompatibility = JavaVersion.VERSION_11
74 | targetCompatibility = JavaVersion.VERSION_11
75 | }
76 | }
77 |
78 | dependencies {
79 | debugImplementation(compose.uiTooling)
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/builtwithpaper/ContainerSize.android.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.toComposeRect
5 | import androidx.compose.ui.platform.LocalConfiguration
6 | import androidx.compose.ui.platform.LocalContext
7 | import androidx.compose.ui.platform.LocalDensity
8 | import androidx.compose.ui.unit.DpSize
9 | import androidx.window.layout.WindowMetricsCalculator
10 |
11 | @Composable
12 | actual fun currentContainerSize(): DpSize {
13 | // Observe view configuration changes and recalculate the size class on each change. We can't
14 | // use Activity#onConfigurationChanged as this will sometimes fail to be called on different
15 | // API levels, hence why this function needs to be @Composable so we can observe the
16 | // ComposeView's configuration changes.
17 | LocalConfiguration.current
18 | val density = LocalDensity.current
19 | val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(LocalContext.current)
20 | return with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
21 | }
22 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.LocalIndication
7 | import androidx.compose.material3.ripple
8 | import androidx.compose.runtime.CompositionLocalProvider
9 |
10 | class MainActivity : ComponentActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 |
14 | setContent {
15 | CompositionLocalProvider(LocalIndication provides ripple()) {
16 | App()
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/material3/Ripple.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package androidx.compose.material3
18 |
19 | import androidx.compose.foundation.Indication
20 | import androidx.compose.foundation.IndicationNodeFactory
21 | import androidx.compose.foundation.interaction.Interaction
22 | import androidx.compose.foundation.interaction.InteractionSource
23 | import androidx.compose.foundation.interaction.PressInteraction
24 | import androidx.compose.material.ripple.RippleAlpha
25 | import androidx.compose.material.ripple.createRippleModifierNode
26 | import androidx.compose.material3.tokens.StateTokens
27 | import androidx.compose.runtime.Immutable
28 | import androidx.compose.runtime.ProvidableCompositionLocal
29 | import androidx.compose.runtime.Stable
30 | import androidx.compose.runtime.compositionLocalOf
31 | import androidx.compose.ui.graphics.Color
32 | import androidx.compose.ui.graphics.ColorProducer
33 | import androidx.compose.ui.graphics.isSpecified
34 | import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
35 | import androidx.compose.ui.node.DelegatableNode
36 | import androidx.compose.ui.node.DelegatingNode
37 | import androidx.compose.ui.node.ObserverModifierNode
38 | import androidx.compose.ui.node.currentValueOf
39 | import androidx.compose.ui.node.observeReads
40 | import androidx.compose.ui.unit.Dp
41 |
42 | /**
43 | * Creates a Ripple using the provided values and values inferred from the theme.
44 | *
45 | * A Ripple is a Material implementation of [Indication] that expresses different [Interaction]s by
46 | * drawing ripple animations and state layers.
47 | *
48 | * A Ripple responds to [PressInteraction.Press] by starting a new ripple animation, and responds to
49 | * other [Interaction]s by showing a fixed state layer with varying alpha values depending on the
50 | * [Interaction].
51 | *
52 | * [MaterialTheme] provides Ripples using [androidx.compose.foundation.LocalIndication], so a Ripple
53 | * will be used as the default [Indication] inside components such as
54 | * [androidx.compose.foundation.clickable] and [androidx.compose.foundation.indication], in addition
55 | * to Material provided components that use a Ripple as well.
56 | *
57 | * You can also explicitly create a Ripple and provide it to custom components in order to change
58 | * the parameters from the default, such as to create an unbounded ripple with a fixed size.
59 | *
60 | * To create a Ripple with a manually defined color that can change over time, see the other
61 | * [ripple] overload with a [ColorProducer] parameter. This will avoid unnecessary recompositions
62 | * when changing the color, and preserve existing ripple state when the color changes.
63 | *
64 | * @param bounded If true, ripples are clipped by the bounds of the target layout. Unbounded ripples
65 | * always animate from the target layout center, bounded ripples animate from the touch position.
66 | * @param radius the radius for the ripple. If [Dp.Unspecified] is provided then the size will be
67 | * calculated based on the target layout size.
68 | * @param color the color of the ripple. This color is usually the same color used by the text or
69 | * iconography in the component. This color will then have [RippleDefaults.RippleAlpha] applied to
70 | * calculate the final color used to draw the ripple. If [Color.Unspecified] is provided the color
71 | * used will be [LocalContentColor] instead.
72 | */
73 | @Stable
74 | fun ripple(
75 | bounded: Boolean = true,
76 | radius: Dp = Dp.Unspecified,
77 | color: Color = Color.Unspecified
78 | ): IndicationNodeFactory {
79 | return if (radius == Dp.Unspecified && color == Color.Unspecified) {
80 | if (bounded) return DefaultBoundedRipple else DefaultUnboundedRipple
81 | } else {
82 | RippleNodeFactory(bounded, radius, color)
83 | }
84 | }
85 |
86 | /**
87 | * Creates a Ripple using the provided values and values inferred from the theme.
88 | *
89 | * A Ripple is a Material implementation of [Indication] that expresses different [Interaction]s by
90 | * drawing ripple animations and state layers.
91 | *
92 | * A Ripple responds to [PressInteraction.Press] by starting a new ripple animation, and responds to
93 | * other [Interaction]s by showing a fixed state layer with varying alpha values depending on the
94 | * [Interaction].
95 | *
96 | * [MaterialTheme] provides Ripples using [androidx.compose.foundation.LocalIndication], so a Ripple
97 | * will be used as the default [Indication] inside components such as
98 | * [androidx.compose.foundation.clickable] and [androidx.compose.foundation.indication], in addition
99 | * to Material provided components that use a Ripple as well.
100 | *
101 | * You can also explicitly create a Ripple and provide it to custom components in order to change
102 | * the parameters from the default, such as to create an unbounded ripple with a fixed size.
103 | *
104 | * To create a Ripple with a static color, see the [ripple] overload with a [Color] parameter. This
105 | * overload is optimized for Ripples that have dynamic colors that change over time, to reduce
106 | * unnecessary recompositions.
107 | *
108 | * @param color the color of the ripple. This color is usually the same color used by the text or
109 | * iconography in the component. This color will then have [RippleDefaults.RippleAlpha] applied to
110 | * calculate the final color used to draw the ripple. If you are creating this [ColorProducer]
111 | * outside of composition (where it will be automatically remembered), make sure that its instance
112 | * is stable (such as by remembering the object that holds it), or remember the returned [ripple]
113 | * object to make sure that ripple nodes are not being created each recomposition.
114 | * @param bounded If true, ripples are clipped by the bounds of the target layout. Unbounded ripples
115 | * always animate from the target layout center, bounded ripples animate from the touch position.
116 | * @param radius the radius for the ripple. If [Dp.Unspecified] is provided then the size will be
117 | * calculated based on the target layout size.
118 | */
119 | @Stable
120 | fun ripple(
121 | color: ColorProducer,
122 | bounded: Boolean = true,
123 | radius: Dp = Dp.Unspecified
124 | ): IndicationNodeFactory {
125 | return RippleNodeFactory(bounded, radius, color)
126 | }
127 |
128 | /** Default values used by [ripple]. */
129 | object RippleDefaults {
130 | /**
131 | * Represents the default [RippleAlpha] that will be used for a ripple to indicate different
132 | * states.
133 | */
134 | val RippleAlpha: RippleAlpha =
135 | RippleAlpha(
136 | pressedAlpha = StateTokens.PressedStateLayerOpacity,
137 | focusedAlpha = StateTokens.FocusStateLayerOpacity,
138 | draggedAlpha = StateTokens.DraggedStateLayerOpacity,
139 | hoveredAlpha = StateTokens.HoverStateLayerOpacity
140 | )
141 | }
142 |
143 | /**
144 | * CompositionLocal used for providing [RippleConfiguration] down the tree. This acts as a
145 | * tree-local 'override' for ripples used inside components that you cannot directly control, such
146 | * as to change the color of a specific component's ripple, or disable it entirely by providing
147 | * `null`.
148 | *
149 | * In most cases you should rely on the default theme behavior for consistency with other components
150 | * - this exists as an escape hatch for individual components and is not intended to be used for
151 | * full theme customization across an application. For this use case you should instead build your
152 | * own custom ripple that queries your design system theme values directly using
153 | * [createRippleModifierNode].
154 | */
155 | val LocalRippleConfiguration: ProvidableCompositionLocal =
156 | compositionLocalOf {
157 | RippleConfiguration()
158 | }
159 |
160 | /**
161 | * Configuration for [ripple] appearance, provided using [LocalRippleConfiguration]. In most cases
162 | * the default values should be used, for custom design system use cases you should instead build
163 | * your own custom ripple using [createRippleModifierNode]. To disable the ripple, provide `null`
164 | * using [LocalRippleConfiguration].
165 | *
166 | * @param color the color override for the ripple. If [Color.Unspecified], then the default color
167 | * from the theme will be used instead. Note that if the ripple has a color explicitly set with
168 | * the parameter on [ripple], that will always be used instead of this value.
169 | * @param rippleAlpha the [RippleAlpha] override for this ripple. If null, then the default alpha
170 | * will be used instead.
171 | */
172 | @Immutable
173 | class RippleConfiguration(
174 | val color: Color = Color.Unspecified,
175 | val rippleAlpha: RippleAlpha? = null
176 | ) {
177 | override fun equals(other: Any?): Boolean {
178 | if (this === other) return true
179 | if (other !is RippleConfiguration) return false
180 |
181 | if (color != other.color) return false
182 | if (rippleAlpha != other.rippleAlpha) return false
183 |
184 | return true
185 | }
186 |
187 | override fun hashCode(): Int {
188 | var result = color.hashCode()
189 | result = 31 * result + (rippleAlpha?.hashCode() ?: 0)
190 | return result
191 | }
192 |
193 | override fun toString(): String {
194 | return "RippleConfiguration(color=$color, rippleAlpha=$rippleAlpha)"
195 | }
196 | }
197 |
198 | @Stable
199 | private class RippleNodeFactory
200 | private constructor(
201 | private val bounded: Boolean,
202 | private val radius: Dp,
203 | private val colorProducer: ColorProducer?,
204 | private val color: Color
205 | ) : IndicationNodeFactory {
206 | constructor(
207 | bounded: Boolean,
208 | radius: Dp,
209 | colorProducer: ColorProducer
210 | ) : this(bounded, radius, colorProducer, Color.Unspecified)
211 |
212 | constructor(bounded: Boolean, radius: Dp, color: Color) : this(bounded, radius, null, color)
213 |
214 | override fun create(interactionSource: InteractionSource): DelegatableNode {
215 | val colorProducer = colorProducer ?: ColorProducer { color }
216 | return DelegatingThemeAwareRippleNode(interactionSource, bounded, radius, colorProducer)
217 | }
218 |
219 | override fun equals(other: Any?): Boolean {
220 | if (this === other) return true
221 | if (other !is RippleNodeFactory) return false
222 |
223 | if (bounded != other.bounded) return false
224 | if (radius != other.radius) return false
225 | if (colorProducer != other.colorProducer) return false
226 | return color == other.color
227 | }
228 |
229 | override fun hashCode(): Int {
230 | var result = bounded.hashCode()
231 | result = 31 * result + radius.hashCode()
232 | result = 31 * result + colorProducer.hashCode()
233 | result = 31 * result + color.hashCode()
234 | return result
235 | }
236 | }
237 |
238 | private class DelegatingThemeAwareRippleNode(
239 | private val interactionSource: InteractionSource,
240 | private val bounded: Boolean,
241 | private val radius: Dp,
242 | private val color: ColorProducer,
243 | ) : DelegatingNode(), CompositionLocalConsumerModifierNode, ObserverModifierNode {
244 | private var rippleNode: DelegatableNode? = null
245 |
246 | override fun onAttach() {
247 | updateConfiguration()
248 | }
249 |
250 | override fun onObservedReadsChanged() {
251 | updateConfiguration()
252 | }
253 |
254 | /**
255 | * Handles [LocalRippleConfiguration] changing between null / non-null. Changes to
256 | * [RippleConfiguration.color] and [RippleConfiguration.rippleAlpha] are handled as part of the
257 | * ripple definition.
258 | */
259 | private fun updateConfiguration() {
260 | observeReads {
261 | val configuration = currentValueOf(LocalRippleConfiguration)
262 | if (configuration == null) {
263 | removeRipple()
264 | } else {
265 | if (rippleNode == null) attachNewRipple()
266 | }
267 | }
268 | }
269 |
270 | private fun attachNewRipple() {
271 | val calculateColor = ColorProducer {
272 | val userDefinedColor = color()
273 | if (userDefinedColor.isSpecified) {
274 | userDefinedColor
275 | } else {
276 | // If this is null, the ripple will be removed, so this should always be non-null in
277 | // normal use
278 | val rippleConfiguration = currentValueOf(LocalRippleConfiguration)
279 | if (rippleConfiguration?.color?.isSpecified == true) {
280 | rippleConfiguration.color
281 | } else {
282 | Color.Unspecified
283 | }
284 | }
285 | }
286 |
287 | val calculateRippleAlpha = {
288 | // If this is null, the ripple will be removed, so this should always be non-null in
289 | // normal use
290 | val rippleConfiguration = currentValueOf(LocalRippleConfiguration)
291 | rippleConfiguration?.rippleAlpha ?: RippleDefaults.RippleAlpha
292 | }
293 |
294 | rippleNode =
295 | delegate(
296 | createRippleModifierNode(
297 | interactionSource,
298 | bounded,
299 | radius,
300 | calculateColor,
301 | calculateRippleAlpha
302 | )
303 | )
304 | }
305 |
306 | private fun removeRipple() {
307 | rippleNode?.let { undelegate(it) }
308 | rippleNode = null
309 | }
310 | }
311 |
312 | private val DefaultBoundedRipple =
313 | RippleNodeFactory(bounded = true, radius = Dp.Unspecified, color = Color.Unspecified)
314 | private val DefaultUnboundedRipple =
315 | RippleNodeFactory(bounded = false, radius = Dp.Unspecified, color = Color.Unspecified)
316 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/material3/tokens/StateTokens.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | // VERSION: v0_210
17 | // GENERATED CODE - DO NOT MODIFY BY HAND
18 | package androidx.compose.material3.tokens
19 |
20 | internal object StateTokens {
21 | const val DraggedStateLayerOpacity = 0.16f
22 | const val FocusStateLayerOpacity = 0.1f
23 | const val HoverStateLayerOpacity = 0.08f
24 | const val PressedStateLayerOpacity = 0.1f
25 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Bubbles
3 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/BottomNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.RowScope
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.selection.selectableGroup
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.unit.dp
16 |
17 | @Composable
18 | fun BottomNavigation(
19 | modifier: Modifier = Modifier,
20 | contentColor: Color = LocalOnSurfaceColor.current,
21 | backgroundColor: Color = LocalSurfaceColor.current,
22 | contentPadding: PaddingValues = PaddingValues(0.dp),
23 | content: @Composable RowScope.() -> Unit
24 | ) {
25 | Row(
26 | modifier = modifier.background(backgroundColor).padding(contentPadding).selectableGroup(),
27 | verticalAlignment = Alignment.CenterVertically,
28 | horizontalArrangement = Arrangement.SpaceEvenly,
29 | ) {
30 | CompositionLocalProvider(LocalContentColor provides contentColor) {
31 | content()
32 | }
33 | }
34 | }
35 |
36 | @Composable
37 | fun TabItem(
38 | selected: Boolean,
39 | onSelected: () -> Unit,
40 | modifier: Modifier = Modifier,
41 | enabled: Boolean = true,
42 | selectedColor: Color = LocalPrimaryColor.current,
43 | contentColor: Color = LocalContentColor.current,
44 | content: @Composable () -> Unit
45 | ) {
46 | GhostButton(
47 | enabled = enabled,
48 | modifier = modifier,
49 | onClick = onSelected,
50 | contentColor = if (selected) selectedColor else contentColor,
51 | ) {
52 | content()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/Buttons.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.LocalIndication
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.border
7 | import androidx.compose.foundation.clickable
8 | import androidx.compose.foundation.interaction.MutableInteractionSource
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.PaddingValues
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.CompositionLocalProvider
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.clip
18 | import androidx.compose.ui.draw.shadow
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.graphics.RectangleShape
21 | import androidx.compose.ui.graphics.Shape
22 | import androidx.compose.ui.graphics.isUnspecified
23 | import androidx.compose.ui.semantics.Role
24 | import androidx.compose.ui.unit.Dp
25 | import androidx.compose.ui.unit.dp
26 |
27 | val DefaultButtonPaddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp)
28 | val DefaultButtonBorderWidth = 1.dp
29 |
30 | @Composable
31 | fun PrimaryButton(
32 | onClick: () -> Unit,
33 | enabled: Boolean = true,
34 | modifier: Modifier = Modifier,
35 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
36 | contentColor: Color = LocalOnPrimaryColor.current,
37 | backgroundColor: Color = LocalPrimaryColor.current,
38 | borderColor: Color = LocalPrimaryColor.current,
39 | borderWidth: Dp = DefaultButtonBorderWidth,
40 | elevation: Dp = 0.dp,
41 | contentPadding: PaddingValues = DefaultButtonPaddingValues,
42 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
43 | content: @Composable () -> Unit
44 | ) {
45 | val backgroundColor = if (enabled) backgroundColor else backgroundColor.copy(alpha = 0.50f)
46 | val borderColor = if (enabled) borderColor else borderColor.copy(alpha = 0.50f)
47 |
48 | UnstyledButton(
49 | onClick = onClick,
50 | enabled = enabled,
51 | shape = shape,
52 | backgroundColor = backgroundColor,
53 | modifier = modifier,
54 | contentPadding = contentPadding,
55 | borderColor = borderColor,
56 | elevation = elevation,
57 | borderWidth = borderWidth,
58 | interactionSource = interactionSource,
59 | ) {
60 | CompositionLocalProvider(LocalContentColor provides contentColor) {
61 | content()
62 | }
63 | }
64 | }
65 |
66 | @Composable
67 | fun SecondaryButton(
68 | onClick: () -> Unit,
69 | modifier: Modifier = Modifier,
70 | enabled: Boolean = true,
71 | contentColor: Color = LocalOnSecondaryColor.current,
72 | backgroundColor: Color = LocalSecondaryColor.current,
73 | borderColor: Color = LocalSecondaryColor.current,
74 | borderWidth: Dp = DefaultButtonBorderWidth,
75 | elevation: Dp = 0.dp,
76 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
77 | contentPadding: PaddingValues = DefaultButtonPaddingValues,
78 | content: @Composable () -> Unit
79 | ) {
80 | val contentColor = if (enabled) contentColor else contentColor.copy(alpha = 0.50f)
81 | val backgroundColor = if (enabled) backgroundColor else backgroundColor.copy(alpha = 0.50f)
82 | val borderColor = if (enabled) borderColor else borderColor.copy(alpha = 0.50f)
83 |
84 | UnstyledButton(
85 | enabled = enabled,
86 | onClick = onClick,
87 | shape = shape,
88 | backgroundColor = backgroundColor,
89 | modifier = modifier,
90 | borderColor = borderColor,
91 | borderWidth = borderWidth,
92 | elevation = elevation,
93 | contentPadding = contentPadding,
94 | ) {
95 | CompositionLocalProvider(LocalContentColor provides contentColor) {
96 | content()
97 | }
98 | }
99 | }
100 |
101 | @Composable
102 | fun OutlinedButton(
103 | onClick: () -> Unit,
104 | modifier: Modifier = Modifier,
105 | enabled: Boolean = true,
106 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
107 | contentColor: Color = LocalContentColor.current,
108 | backgroundColor: Color = Color.Transparent,
109 | borderColor: Color = LocalOutlineColor.current,
110 | borderWidth: Dp = DefaultButtonBorderWidth,
111 | contentPadding: PaddingValues = DefaultButtonPaddingValues,
112 | elevation: Dp = 0.dp,
113 | content: @Composable () -> Unit
114 | ) {
115 | val contentColor = if (enabled) contentColor else contentColor.copy(alpha = 0.50f)
116 | val backgroundColor = if (enabled) backgroundColor else backgroundColor.copy(alpha = 0.50f)
117 | val borderColor = if (enabled) borderColor else borderColor.copy(alpha = 0.50f)
118 |
119 | UnstyledButton(
120 | onClick = onClick,
121 | shape = shape,
122 | enabled = enabled,
123 | backgroundColor = backgroundColor,
124 | modifier = modifier,
125 | contentPadding = contentPadding,
126 | elevation = elevation,
127 | borderWidth = borderWidth,
128 | borderColor = borderColor,
129 | ) {
130 | CompositionLocalProvider(LocalContentColor provides contentColor) {
131 | content()
132 | }
133 | }
134 | }
135 |
136 |
137 | @Composable
138 | fun GhostButton(
139 | onClick: () -> Unit,
140 | enabled: Boolean = true,
141 | modifier: Modifier = Modifier,
142 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
143 | contentColor: Color = LocalContentColor.current,
144 | backgroundColor: Color = Color.Transparent,
145 | borderColor: Color = Color.Transparent,
146 | borderWidth: Dp = DefaultButtonBorderWidth,
147 | contentPadding: PaddingValues = DefaultButtonPaddingValues,
148 | content: @Composable () -> Unit
149 | ) {
150 | val contentColor = if (enabled) contentColor else contentColor.copy(alpha = 0.50f)
151 | val borderColor = when {
152 | borderColor.isUnspecified || borderColor == Color.Transparent -> Color.Transparent
153 | enabled -> borderColor
154 | else -> borderColor.copy(alpha = 0.50f)
155 | }
156 | UnstyledButton(
157 | onClick = onClick,
158 | shape = shape,
159 | backgroundColor = backgroundColor,
160 | modifier = modifier,
161 | enabled = enabled,
162 | borderColor = borderColor,
163 | borderWidth = borderWidth,
164 | contentPadding = contentPadding,
165 | ) {
166 | CompositionLocalProvider(LocalContentColor provides contentColor) {
167 | content()
168 | }
169 | }
170 | }
171 |
172 | @Composable
173 | fun DestructiveButton(
174 | onClick: () -> Unit,
175 | enabled: Boolean = true,
176 | modifier: Modifier = Modifier,
177 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
178 | contentColor: Color = LocalOnDestructiveColor.current,
179 | backgroundColor: Color = LocalDestructiveColor.current,
180 | borderColor: Color = LocalDestructiveColor.current,
181 | borderWidth: Dp = DefaultButtonBorderWidth,
182 | contentPadding: PaddingValues = DefaultButtonPaddingValues,
183 | elevation: Dp = 0.dp,
184 | content: @Composable () -> Unit
185 | ) {
186 | val backgroundColor = if (enabled) backgroundColor else backgroundColor.copy(alpha = 0.50f)
187 | val borderColor = if (enabled) borderColor else borderColor.copy(alpha = 0.50f)
188 |
189 | UnstyledButton(
190 | onClick = onClick,
191 | shape = shape,
192 | backgroundColor = backgroundColor,
193 | modifier = modifier,
194 | enabled = enabled,
195 | borderColor = borderColor,
196 | elevation = elevation,
197 | borderWidth = borderWidth,
198 | contentPadding = contentPadding,
199 | ) {
200 | CompositionLocalProvider(LocalContentColor provides contentColor) {
201 | content()
202 | }
203 | }
204 | }
205 |
206 | @Composable
207 | private fun UnstyledButton(
208 | onClick: () -> Unit,
209 | enabled: Boolean = true,
210 | shape: Shape = RectangleShape,
211 | backgroundColor: Color = Color.Unspecified,
212 | elevation: Dp = 0.dp,
213 | contentPadding: PaddingValues = PaddingValues(0.dp),
214 | borderColor: Color = Color.Unspecified,
215 | borderWidth: Dp = 0.dp,
216 | modifier: Modifier = Modifier,
217 | indication: Indication? = LocalIndication.current,
218 | interactionSource: MutableInteractionSource? = null,
219 | content: @Composable () -> Unit = {}
220 | ) {
221 | Box(
222 | modifier = modifier
223 | .then(if (elevation > 0.dp) Modifier.shadow(elevation, shape) else Modifier)
224 | .clip(shape)
225 | .background(backgroundColor)
226 | .clickable(
227 | onClick = onClick,
228 | role = Role.Button,
229 | enabled = enabled,
230 | indication = indication,
231 | interactionSource = interactionSource
232 | )
233 | .border(borderWidth, borderColor, shape)
234 | .padding(contentPadding)
235 | ) {
236 | content()
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/Card.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.PaddingValues
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.CompositionLocalProvider
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.clip
13 | import androidx.compose.ui.draw.shadow
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.Shape
16 | import androidx.compose.ui.unit.Dp
17 | import androidx.compose.ui.unit.dp
18 | import androidx.compose.ui.zIndex
19 |
20 | @Composable
21 | fun Card(
22 | modifier: Modifier = Modifier,
23 | contentPadding: PaddingValues = PaddingValues(0.dp),
24 | elevation: Dp = 0.dp,
25 | backgroundColor: Color = LocalSurfaceColor.current,
26 | contentColor: Color = LocalOnSurfaceColor.current,
27 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
28 | borderWidth: Dp = 0.dp,
29 | borderColor: Color = LocalOutlineColor.current,
30 | content: @Composable () -> Unit = {}
31 | ) {
32 | Box(
33 | modifier
34 | .shadow(elevation = elevation, shape = shape)
35 | .zIndex(elevation.value)
36 | .border(width = borderWidth, color = borderColor, shape = shape)
37 | .clip(shape)
38 | .background(backgroundColor, shape)
39 | .padding(contentPadding)
40 | ) {
41 | CompositionLocalProvider(LocalContentColor provides contentColor) {
42 | content()
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/CheckBox.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.LocalIndication
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.interaction.MutableInteractionSource
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.draw.alpha
15 | import androidx.compose.ui.draw.clip
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.Shape
18 | import androidx.compose.ui.semantics.Role
19 | import androidx.compose.ui.unit.Dp
20 | import androidx.compose.ui.unit.dp
21 | import com.composables.core.Icon
22 | import com.composables.icons.lucide.Check
23 | import com.composables.icons.lucide.Lucide
24 |
25 | @Composable
26 | fun Checkbox(
27 | checked: Boolean,
28 | modifier: Modifier = Modifier,
29 | backgroundColor: Color = Color.Transparent,
30 | contentColor: Color = LocalContentColor.current,
31 | enabled: Boolean = true,
32 | onCheckedChange: ((Boolean) -> Unit)? = null,
33 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
34 | borderColor: Color = LocalOutlineColor.current,
35 | borderWidth: Dp = 1.dp,
36 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
37 | checkIcon: @Composable () -> Unit,
38 | ) {
39 | val checkboxAlpha = if (checked) 1f else 0f
40 |
41 | Box(
42 | modifier
43 | .border(borderWidth, borderColor, shape)
44 | .clip(shape)
45 | .background(backgroundColor)
46 | then if (onCheckedChange != null) {
47 | Modifier.clickable(
48 | enabled = enabled,
49 | indication = LocalIndication.current,
50 | interactionSource = interactionSource,
51 | role = Role.Checkbox,
52 | ) {
53 | onCheckedChange(checked.not())
54 | }
55 | } else {
56 | Modifier
57 | }
58 | ) {
59 | Box(modifier = Modifier.alpha(checkboxAlpha)) {
60 | CompositionLocalProvider(LocalContentColor provides contentColor) {
61 | checkIcon()
62 | }
63 | }
64 | }
65 | }
66 |
67 |
68 | @Composable
69 | fun Check() {
70 | Icon(Lucide.Check, contentDescription = null, tint = LocalContentColor.current)
71 | }
72 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/ContainerSize.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.unit.DpSize
5 |
6 | @Composable
7 | expect fun currentContainerSize(): DpSize
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/Modifier.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | inline fun buildModifier(builder: @Composable MutableList.() -> Unit): Modifier {
8 | val list = mutableListOf()
9 | builder(list)
10 | return list.fold(Modifier as Modifier) { acc, modifier ->
11 | acc then modifier
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/ProgressBar.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.animation.core.AnimationSpec
4 | import androidx.compose.animation.core.LinearEasing
5 | import androidx.compose.animation.core.animateFloatAsState
6 | import androidx.compose.animation.core.tween
7 | import androidx.compose.foundation.background
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.fillMaxHeight
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.heightIn
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.clip
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.graphics.Shape
19 | import androidx.compose.ui.unit.dp
20 |
21 | @Composable
22 | fun ProgressBar(
23 | progress: Float,
24 | modifier: Modifier = Modifier,
25 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
26 | color: Color = LocalPrimaryColor.current,
27 | backgroundColor: Color = LocalSecondaryColor.current,
28 | animationSpec: AnimationSpec = tween(easing = LinearEasing),
29 | ) {
30 | val cappedProgress = progress.coerceIn(0f, 1f)
31 | val animatedProgress by animateFloatAsState(targetValue = cappedProgress, animationSpec = animationSpec)
32 |
33 | Box(modifier.clip(shape).background(backgroundColor).heightIn(min = 2.dp)) {
34 | Box(Modifier.matchParentSize()) {
35 | Box(
36 | Modifier
37 | .background(color)
38 | .fillMaxHeight()
39 | .fillMaxWidth(animatedProgress)
40 | )
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/RadioButton.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.LocalIndication
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.interaction.MutableInteractionSource
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.PaddingValues
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.layout.size
12 | import androidx.compose.foundation.shape.CircleShape
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.CompositionLocalProvider
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.alpha
18 | import androidx.compose.ui.draw.clip
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.graphics.Shape
21 | import androidx.compose.ui.semantics.Role
22 | import androidx.compose.ui.unit.Dp
23 | import androidx.compose.ui.unit.dp
24 |
25 | @Composable
26 | fun RadioButton(
27 | selected: Boolean,
28 | modifier: Modifier = Modifier,
29 | backgroundColor: Color = LocalSurfaceColor.current,
30 | contentColor: Color = LocalOnSurfaceColor.current,
31 | selectedColor: Color = LocalPrimaryColor.current,
32 | enabled: Boolean = true,
33 | onSelectedChange: ((Boolean) -> Unit)? = null,
34 | shape: Shape = CircleShape,
35 | borderColor: Color = LocalOutlineColor.current,
36 | borderWidth: Dp = 1.dp,
37 | contentPadding: PaddingValues = PaddingValues(0.dp),
38 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
39 | radio: @Composable () -> Unit,
40 | ) {
41 | val checkboxAlpha = if (selected) 1f else 0f
42 |
43 | Box(
44 | modifier
45 | .border(borderWidth, borderColor, shape)
46 | .clip(shape)
47 | .background(backgroundColor)
48 | then if (onSelectedChange != null) {
49 | Modifier.clickable(
50 | enabled = enabled,
51 | indication = LocalIndication.current,
52 | interactionSource = interactionSource,
53 | role = Role.RadioButton,
54 | ) {
55 | onSelectedChange(selected.not())
56 | }
57 | } else {
58 | Modifier
59 | }.padding(contentPadding)
60 | ) {
61 | Box(modifier = Modifier.alpha(checkboxAlpha)) {
62 | CompositionLocalProvider(
63 | LocalContentColor provides if (selected) selectedColor else contentColor
64 | ) {
65 | radio()
66 | }
67 | }
68 | }
69 | }
70 |
71 | @Composable
72 | fun Radio() {
73 | Box(
74 | modifier = Modifier
75 | .size(24.dp)
76 | .padding(4.dp)
77 | .clip(CircleShape)
78 | .background(LocalPrimaryColor.current)
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/ScreenWidthBreakpoint.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalComposeUiApi::class)
2 |
3 | package com.builtwithpaper
4 |
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.ExperimentalComposeUiApi
7 | import androidx.compose.ui.unit.Dp
8 | import androidx.compose.ui.unit.dp
9 |
10 | sealed class ScreenWidthBreakpoint(val minWidth: Dp) {
11 | object Default : ScreenWidthBreakpoint(0.dp)
12 | object Small : ScreenWidthBreakpoint(640.dp)
13 | object Medium : ScreenWidthBreakpoint(768.dp)
14 | object Large : ScreenWidthBreakpoint(1024.dp)
15 | object ExtraLarge : ScreenWidthBreakpoint(1280.dp)
16 | object ExtraExtraLarge : ScreenWidthBreakpoint(1536.dp)
17 | }
18 |
19 | /**
20 | * Calculates the current [ScreenWidthBreakpoint] according to the current application's container width.
21 | */
22 | @Composable
23 | fun currentScreenWidthBreakpoint(): ScreenWidthBreakpoint {
24 | val screenWidth = currentContainerSize().width
25 |
26 | val breakpoint = when {
27 | screenWidth > 1536.dp -> ScreenWidthBreakpoint.ExtraExtraLarge
28 | screenWidth > 1280.dp -> ScreenWidthBreakpoint.ExtraLarge
29 | screenWidth > 1024.dp -> ScreenWidthBreakpoint.Large
30 | screenWidth > 768.dp -> ScreenWidthBreakpoint.Medium
31 | screenWidth > 640.dp -> ScreenWidthBreakpoint.Small
32 | else -> ScreenWidthBreakpoint.Default
33 | }
34 | return breakpoint
35 | }
36 |
37 | infix fun ScreenWidthBreakpoint.isAtLeast(other: ScreenWidthBreakpoint): Boolean {
38 | return minWidth >= other.minWidth
39 | }
40 |
41 | infix fun ScreenWidthBreakpoint.isAtMost(other: ScreenWidthBreakpoint): Boolean {
42 | return minWidth <= other.minWidth
43 | }
44 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/Slider.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.gestures.Orientation
5 | import androidx.compose.foundation.gestures.draggable
6 | import androidx.compose.foundation.gestures.rememberDraggableState
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.foundation.layout.offset
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.foundation.layout.width
14 | import androidx.compose.foundation.shape.RoundedCornerShape
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.runtime.getValue
17 | import androidx.compose.runtime.mutableStateOf
18 | import androidx.compose.runtime.remember
19 | import androidx.compose.runtime.setValue
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.draw.shadow
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.graphics.Shape
25 | import androidx.compose.ui.layout.onPlaced
26 | import androidx.compose.ui.platform.LocalDensity
27 | import androidx.compose.ui.unit.Dp
28 | import androidx.compose.ui.unit.dp
29 |
30 | @Composable
31 | fun Slider(
32 | value: Float,
33 | onValueChange: (Float) -> Unit,
34 | modifier: Modifier = Modifier,
35 | enabled: Boolean = true,
36 | trackColor: Color = LocalPrimaryColor.current,
37 | trackShape: Shape = RoundedCornerShape(LocalCornerRadius.current),
38 | thumb: @Composable () -> Unit
39 | ) {
40 | var thumbWidth by remember { mutableStateOf(0.dp) }
41 | var trackWidth by remember { mutableStateOf(0) }
42 |
43 | val density = LocalDensity.current
44 |
45 | val thumbPosition = trackWidth * value
46 |
47 | Box(modifier = modifier, contentAlignment = Alignment.CenterStart) {
48 | Box(
49 | Modifier
50 | .fillMaxWidth()
51 | .padding(horizontal = thumbWidth / 2)
52 | .height(4.dp)
53 | .background(trackColor.copy(0.44f), trackShape)
54 | .onPlaced {
55 | trackWidth = with(density) { it.size.width }
56 | }
57 | )
58 | Box(
59 | Modifier
60 | .padding(horizontal = thumbWidth / 2)
61 | .width(with(density) { thumbPosition.toDp() })
62 | .height(4.dp)
63 | .background(trackColor, trackShape)
64 | )
65 |
66 | Box(
67 | Modifier
68 | .onPlaced { thumbWidth = with(density) { it.size.width.toDp() } }
69 | .offset(x = with(density) { thumbPosition.toDp() })
70 | .draggable(
71 | enabled = enabled,
72 | orientation = Orientation.Horizontal,
73 | state = rememberDraggableState { deltaPx ->
74 | val valueDelta = deltaPx / trackWidth
75 | val newValue = (value + valueDelta).coerceIn(0f, 1f)
76 | onValueChange(newValue)
77 | }
78 | )
79 | ) {
80 | thumb()
81 | }
82 | }
83 | }
84 |
85 |
86 | @Composable
87 | fun SliderThumb(
88 | color: Color = LocalSurfaceColor.current,
89 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
90 | elevation: Dp = 4.dp,
91 | ) {
92 | Box(
93 | Modifier
94 | .size(28.dp)
95 | .shadow(elevation, shape)
96 | .background(color, shape)
97 | )
98 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/SocialIcons.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.PathFillType
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap
7 | import androidx.compose.ui.graphics.StrokeJoin
8 | import androidx.compose.ui.graphics.vector.ImageVector
9 | import androidx.compose.ui.graphics.vector.path
10 | import androidx.compose.ui.unit.dp
11 |
12 |
13 | public val Google: ImageVector
14 | get() {
15 | if (_Google != null) {
16 | return _Google!!
17 | }
18 | _Google = ImageVector.Builder(
19 | name = "Google",
20 | defaultWidth = 24.dp,
21 | defaultHeight = 24.dp,
22 | viewportWidth = 16f,
23 | viewportHeight = 16f
24 | ).apply {
25 | path(
26 | fill = SolidColor(Color(0xFF000000)),
27 | fillAlpha = 1.0f,
28 | stroke = null,
29 | strokeAlpha = 1.0f,
30 | strokeLineWidth = 1.0f,
31 | strokeLineCap = StrokeCap.Butt,
32 | strokeLineJoin = StrokeJoin.Miter,
33 | strokeLineMiter = 1.0f,
34 | pathFillType = PathFillType.NonZero
35 | ) {
36 | moveTo(15.545f, 6.558f)
37 | arcToRelative(9.4f, 9.4f, 0f, isMoreThanHalf = false, isPositiveArc = true, 0.139f, 1.626f)
38 | curveToRelative(0f, 2.434f, -0.87f, 4.492f, -2.384f, 5.885f)
39 | horizontalLineToRelative(0.002f)
40 | curveTo(11.978f, 15.292f, 10.158f, 16f, 8f, 16f)
41 | arcTo(8f, 8f, 0f, isMoreThanHalf = true, isPositiveArc = true, 8f, 0f)
42 | arcToRelative(7.7f, 7.7f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5.352f, 2.082f)
43 | lineToRelative(-2.284f, 2.284f)
44 | arcTo(4.35f, 4.35f, 0f, isMoreThanHalf = false, isPositiveArc = false, 8f, 3.166f)
45 | curveToRelative(-2.087f, 0f, -3.86f, 1.408f, -4.492f, 3.304f)
46 | arcToRelative(4.8f, 4.8f, 0f, isMoreThanHalf = false, isPositiveArc = false, 0f, 3.063f)
47 | horizontalLineToRelative(0.003f)
48 | curveToRelative(0.635f, 1.893f, 2.405f, 3.301f, 4.492f, 3.301f)
49 | curveToRelative(1.078f, 0f, 2.004f, -0.276f, 2.722f, -0.764f)
50 | horizontalLineToRelative(-0.003f)
51 | arcToRelative(3.7f, 3.7f, 0f, isMoreThanHalf = false, isPositiveArc = false, 1.599f, -2.431f)
52 | horizontalLineTo(8f)
53 | verticalLineToRelative(-3.08f)
54 | close()
55 | }
56 | }.build()
57 | return _Google!!
58 | }
59 |
60 | private var _Google: ImageVector? = null
61 |
62 |
63 | public val Github: ImageVector
64 | get() {
65 | if (_Github != null) {
66 | return _Github!!
67 | }
68 | _Github = ImageVector.Builder(
69 | name = "Github",
70 | defaultWidth = 24.dp,
71 | defaultHeight = 24.dp,
72 | viewportWidth = 16f,
73 | viewportHeight = 16f
74 | ).apply {
75 | path(
76 | fill = SolidColor(Color(0xFF000000)),
77 | fillAlpha = 1.0f,
78 | stroke = null,
79 | strokeAlpha = 1.0f,
80 | strokeLineWidth = 1.0f,
81 | strokeLineCap = StrokeCap.Butt,
82 | strokeLineJoin = StrokeJoin.Miter,
83 | strokeLineMiter = 1.0f,
84 | pathFillType = PathFillType.NonZero
85 | ) {
86 | moveTo(8f, 0f)
87 | curveTo(3.58f, 0f, 0f, 3.58f, 0f, 8f)
88 | curveToRelative(0f, 3.54f, 2.29f, 6.53f, 5.47f, 7.59f)
89 | curveToRelative(0.4f, 0.07f, 0.55f, -0.17f, 0.55f, -0.38f)
90 | curveToRelative(0f, -0.19f, -0.01f, -0.82f, -0.01f, -1.49f)
91 | curveToRelative(-2.01f, 0.37f, -2.53f, -0.49f, -2.69f, -0.94f)
92 | curveToRelative(-0.09f, -0.23f, -0.48f, -0.94f, -0.82f, -1.13f)
93 | curveToRelative(-0.28f, -0.15f, -0.68f, -0.52f, -0.01f, -0.53f)
94 | curveToRelative(0.63f, -0.01f, 1.08f, 0.58f, 1.23f, 0.82f)
95 | curveToRelative(0.72f, 1.21f, 1.87f, 0.87f, 2.33f, 0.66f)
96 | curveToRelative(0.07f, -0.52f, 0.28f, -0.87f, 0.51f, -1.07f)
97 | curveToRelative(-1.78f, -0.2f, -3.64f, -0.89f, -3.64f, -3.95f)
98 | curveToRelative(0f, -0.87f, 0.31f, -1.59f, 0.82f, -2.15f)
99 | curveToRelative(-0.08f, -0.2f, -0.36f, -1.02f, 0.08f, -2.12f)
100 | curveToRelative(0f, 0f, 0.67f, -0.21f, 2.2f, 0.82f)
101 | curveToRelative(0.64f, -0.18f, 1.32f, -0.27f, 2f, -0.27f)
102 | reflectiveCurveToRelative(1.36f, 0.09f, 2f, 0.27f)
103 | curveToRelative(1.53f, -1.04f, 2.2f, -0.82f, 2.2f, -0.82f)
104 | curveToRelative(0.44f, 1.1f, 0.16f, 1.92f, 0.08f, 2.12f)
105 | curveToRelative(0.51f, 0.56f, 0.82f, 1.27f, 0.82f, 2.15f)
106 | curveToRelative(0f, 3.07f, -1.87f, 3.75f, -3.65f, 3.95f)
107 | curveToRelative(0.29f, 0.25f, 0.54f, 0.73f, 0.54f, 1.48f)
108 | curveToRelative(0f, 1.07f, -0.01f, 1.93f, -0.01f, 2.2f)
109 | curveToRelative(0f, 0.21f, 0.15f, 0.46f, 0.55f, 0.38f)
110 | arcTo(8.01f, 8.01f, 0f, isMoreThanHalf = false, isPositiveArc = false, 16f, 8f)
111 | curveToRelative(0f, -4.42f, -3.58f, -8f, -8f, -8f)
112 | }
113 | }.build()
114 | return _Github!!
115 | }
116 |
117 | private var _Github: ImageVector? = null
118 |
119 |
120 | public val Facebook: ImageVector
121 | get() {
122 | if (_Facebook != null) {
123 | return _Facebook!!
124 | }
125 | _Facebook = ImageVector.Builder(
126 | name = "Facebook",
127 | defaultWidth = 24.dp,
128 | defaultHeight = 24.dp,
129 | viewportWidth = 16f,
130 | viewportHeight = 16f
131 | ).apply {
132 | path(
133 | fill = SolidColor(Color(0xFF000000)),
134 | fillAlpha = 1.0f,
135 | stroke = null,
136 | strokeAlpha = 1.0f,
137 | strokeLineWidth = 1.0f,
138 | strokeLineCap = StrokeCap.Butt,
139 | strokeLineJoin = StrokeJoin.Miter,
140 | strokeLineMiter = 1.0f,
141 | pathFillType = PathFillType.NonZero
142 | ) {
143 | moveTo(16f, 8.049f)
144 | curveToRelative(0f, -4.446f, -3.582f, -8.05f, -8f, -8.05f)
145 | curveTo(3.58f, 0f, -0.002f, 3.603f, -0.002f, 8.05f)
146 | curveToRelative(0f, 4.017f, 2.926f, 7.347f, 6.75f, 7.951f)
147 | verticalLineToRelative(-5.625f)
148 | horizontalLineToRelative(-2.03f)
149 | verticalLineTo(8.05f)
150 | horizontalLineTo(6.75f)
151 | verticalLineTo(6.275f)
152 | curveToRelative(0f, -2.017f, 1.195f, -3.131f, 3.022f, -3.131f)
153 | curveToRelative(0.876f, 0f, 1.791f, 0.157f, 1.791f, 0.157f)
154 | verticalLineToRelative(1.98f)
155 | horizontalLineToRelative(-1.009f)
156 | curveToRelative(-0.993f, 0f, -1.303f, 0.621f, -1.303f, 1.258f)
157 | verticalLineToRelative(1.51f)
158 | horizontalLineToRelative(2.218f)
159 | lineToRelative(-0.354f, 2.326f)
160 | horizontalLineTo(9.25f)
161 | verticalLineTo(16f)
162 | curveToRelative(3.824f, -0.604f, 6.75f, -3.934f, 6.75f, -7.951f)
163 | }
164 | }.build()
165 | return _Facebook!!
166 | }
167 |
168 | private var _Facebook: ImageVector? = null
169 |
170 | public val Apple: ImageVector
171 | get() {
172 | if (_Apple != null) {
173 | return _Apple!!
174 | }
175 | _Apple = ImageVector.Builder(
176 | name = "Apple",
177 | defaultWidth = 24.dp,
178 | defaultHeight = 24.dp,
179 | viewportWidth = 16f,
180 | viewportHeight = 16f
181 | ).apply {
182 | path(
183 | fill = SolidColor(Color(0xFF000000)),
184 | fillAlpha = 1.0f,
185 | stroke = null,
186 | strokeAlpha = 1.0f,
187 | strokeLineWidth = 1.0f,
188 | strokeLineCap = StrokeCap.Butt,
189 | strokeLineJoin = StrokeJoin.Miter,
190 | strokeLineMiter = 1.0f,
191 | pathFillType = PathFillType.NonZero
192 | ) {
193 | moveTo(11.182f, 0.008f)
194 | curveTo(11.148f, -0.03f, 9.923f, 0.023f, 8.857f, 1.18f)
195 | curveToRelative(-1.066f, 1.156f, -0.902f, 2.482f, -0.878f, 2.516f)
196 | reflectiveCurveToRelative(1.52f, 0.087f, 2.475f, -1.258f)
197 | reflectiveCurveToRelative(0.762f, -2.391f, 0.728f, -2.43f)
198 | moveToRelative(3.314f, 11.733f)
199 | curveToRelative(-0.048f, -0.096f, -2.325f, -1.234f, -2.113f, -3.422f)
200 | reflectiveCurveToRelative(1.675f, -2.789f, 1.698f, -2.854f)
201 | reflectiveCurveToRelative(-0.597f, -0.79f, -1.254f, -1.157f)
202 | arcToRelative(3.7f, 3.7f, 0f, isMoreThanHalf = false, isPositiveArc = false, -1.563f, -0.434f)
203 | curveToRelative(-0.108f, -0.003f, -0.483f, -0.095f, -1.254f, 0.116f)
204 | curveToRelative(-0.508f, 0.139f, -1.653f, 0.589f, -1.968f, 0.607f)
205 | curveToRelative(-0.316f, 0.018f, -1.256f, -0.522f, -2.267f, -0.665f)
206 | curveToRelative(-0.647f, -0.125f, -1.333f, 0.131f, -1.824f, 0.328f)
207 | curveToRelative(-0.49f, 0.196f, -1.422f, 0.754f, -2.074f, 2.237f)
208 | curveToRelative(-0.652f, 1.482f, -0.311f, 3.83f, -0.067f, 4.56f)
209 | reflectiveCurveToRelative(0.625f, 1.924f, 1.273f, 2.796f)
210 | curveToRelative(0.576f, 0.984f, 1.34f, 1.667f, 1.659f, 1.899f)
211 | reflectiveCurveToRelative(1.219f, 0.386f, 1.843f, 0.067f)
212 | curveToRelative(0.502f, -0.308f, 1.408f, -0.485f, 1.766f, -0.472f)
213 | curveToRelative(0.357f, 0.013f, 1.061f, 0.154f, 1.782f, 0.539f)
214 | curveToRelative(0.571f, 0.197f, 1.111f, 0.115f, 1.652f, -0.105f)
215 | curveToRelative(0.541f, -0.221f, 1.324f, -1.059f, 2.238f, -2.758f)
216 | quadToRelative(0.52f, -1.185f, 0.473f, -1.282f)
217 | }
218 | path(
219 | fill = SolidColor(Color(0xFF000000)),
220 | fillAlpha = 1.0f,
221 | stroke = null,
222 | strokeAlpha = 1.0f,
223 | strokeLineWidth = 1.0f,
224 | strokeLineCap = StrokeCap.Butt,
225 | strokeLineJoin = StrokeJoin.Miter,
226 | strokeLineMiter = 1.0f,
227 | pathFillType = PathFillType.NonZero
228 | ) {
229 | moveTo(11.182f, 0.008f)
230 | curveTo(11.148f, -0.03f, 9.923f, 0.023f, 8.857f, 1.18f)
231 | curveToRelative(-1.066f, 1.156f, -0.902f, 2.482f, -0.878f, 2.516f)
232 | reflectiveCurveToRelative(1.52f, 0.087f, 2.475f, -1.258f)
233 | reflectiveCurveToRelative(0.762f, -2.391f, 0.728f, -2.43f)
234 | moveToRelative(3.314f, 11.733f)
235 | curveToRelative(-0.048f, -0.096f, -2.325f, -1.234f, -2.113f, -3.422f)
236 | reflectiveCurveToRelative(1.675f, -2.789f, 1.698f, -2.854f)
237 | reflectiveCurveToRelative(-0.597f, -0.79f, -1.254f, -1.157f)
238 | arcToRelative(3.7f, 3.7f, 0f, isMoreThanHalf = false, isPositiveArc = false, -1.563f, -0.434f)
239 | curveToRelative(-0.108f, -0.003f, -0.483f, -0.095f, -1.254f, 0.116f)
240 | curveToRelative(-0.508f, 0.139f, -1.653f, 0.589f, -1.968f, 0.607f)
241 | curveToRelative(-0.316f, 0.018f, -1.256f, -0.522f, -2.267f, -0.665f)
242 | curveToRelative(-0.647f, -0.125f, -1.333f, 0.131f, -1.824f, 0.328f)
243 | curveToRelative(-0.49f, 0.196f, -1.422f, 0.754f, -2.074f, 2.237f)
244 | curveToRelative(-0.652f, 1.482f, -0.311f, 3.83f, -0.067f, 4.56f)
245 | reflectiveCurveToRelative(0.625f, 1.924f, 1.273f, 2.796f)
246 | curveToRelative(0.576f, 0.984f, 1.34f, 1.667f, 1.659f, 1.899f)
247 | reflectiveCurveToRelative(1.219f, 0.386f, 1.843f, 0.067f)
248 | curveToRelative(0.502f, -0.308f, 1.408f, -0.485f, 1.766f, -0.472f)
249 | curveToRelative(0.357f, 0.013f, 1.061f, 0.154f, 1.782f, 0.539f)
250 | curveToRelative(0.571f, 0.197f, 1.111f, 0.115f, 1.652f, -0.105f)
251 | curveToRelative(0.541f, -0.221f, 1.324f, -1.059f, 2.238f, -2.758f)
252 | quadToRelative(0.52f, -1.185f, 0.473f, -1.282f)
253 | }
254 | }.build()
255 | return _Apple!!
256 | }
257 |
258 | private var _Apple: ImageVector? = null
259 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/Text.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.text.BasicText
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.text.AnnotatedString
11 | import androidx.compose.ui.text.TextStyle
12 | import androidx.compose.ui.text.font.FontFamily
13 | import androidx.compose.ui.text.font.FontWeight
14 | import androidx.compose.ui.text.style.TextAlign
15 | import androidx.compose.ui.text.style.TextOverflow
16 | import androidx.compose.ui.unit.TextUnit
17 |
18 | @Composable
19 | fun Text(
20 | text: String,
21 | modifier: Modifier = Modifier,
22 | style: TextStyle = LocalTextStyle.current,
23 | textAlign: TextAlign = TextAlign.Unspecified,
24 | lineHeight: TextUnit = TextUnit.Unspecified,
25 | fontSize: TextUnit = style.fontSize,
26 | fontWeight: FontWeight? = style.fontWeight,
27 | color: Color = Color.Unspecified,
28 | fontFamily: FontFamily? = style.fontFamily,
29 | singleLine: Boolean = false,
30 | minLines: Int = 1,
31 | maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
32 | overflow: TextOverflow = TextOverflow.Clip
33 | ) {
34 | val currentColor = listOf(
35 | color, style.color, LocalContentColor.current
36 | ).firstOrNull { it != Color.Unspecified } ?: Color.Unspecified
37 |
38 | val currentTextAlign = listOf(
39 | textAlign,
40 | style.textAlign,
41 | ).firstOrNull { it != TextAlign.Unspecified } ?: TextAlign.Unspecified
42 |
43 | val currentLineHeight = listOf(
44 | lineHeight,
45 | style.lineHeight,
46 | ).firstOrNull { it != TextUnit.Unspecified } ?: TextUnit.Unspecified
47 |
48 | val currentStyle by remember(
49 | style,
50 | currentTextAlign,
51 | fontSize,
52 | currentColor,
53 | fontWeight,
54 | fontFamily,
55 | currentLineHeight
56 | ) {
57 | mutableStateOf(
58 | style.copy(
59 | textAlign = currentTextAlign,
60 | fontSize = fontSize,
61 | color = currentColor,
62 | fontWeight = fontWeight,
63 | fontFamily = fontFamily,
64 | lineHeight = currentLineHeight
65 | )
66 | )
67 | }
68 | BasicText(
69 | text = text,
70 | modifier = modifier,
71 | style = currentStyle,
72 | minLines = minLines,
73 | maxLines = maxLines,
74 | overflow = overflow,
75 | )
76 | }
77 |
78 | @Composable
79 | fun Text(
80 | text: AnnotatedString,
81 | modifier: Modifier = Modifier,
82 | style: TextStyle = LocalTextStyle.current,
83 | textAlign: TextAlign = TextAlign.Unspecified,
84 | fontSize: TextUnit = style.fontSize,
85 | fontWeight: FontWeight? = style.fontWeight,
86 | color: Color = Color.Unspecified,
87 | lineHeight: TextUnit = TextUnit.Unspecified,
88 | fontFamily: FontFamily? = style.fontFamily,
89 | singleLine: Boolean = false,
90 | minLines: Int = 1,
91 | maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
92 | overflow: TextOverflow = TextOverflow.Clip
93 | ) {
94 | val currentColor = listOf(
95 | color, style.color, LocalContentColor.current
96 | ).firstOrNull { it != Color.Unspecified } ?: Color.Unspecified
97 |
98 | val currentTextAlign = listOf(
99 | textAlign,
100 | style.textAlign,
101 | ).firstOrNull { it != TextAlign.Unspecified } ?: TextAlign.Unspecified
102 |
103 | val currentLineHeight = listOf(
104 | lineHeight,
105 | style.lineHeight,
106 | ).firstOrNull { it != TextUnit.Unspecified } ?: TextUnit.Unspecified
107 |
108 | val currentStyle by remember(
109 | style,
110 | currentTextAlign,
111 | fontSize,
112 | currentColor,
113 | fontWeight,
114 | fontFamily,
115 | currentLineHeight
116 | ) {
117 | mutableStateOf(
118 | style.copy(
119 | textAlign = currentTextAlign,
120 | fontSize = fontSize,
121 | color = currentColor,
122 | fontWeight = fontWeight,
123 | fontFamily = fontFamily,
124 | lineHeight = currentLineHeight
125 | )
126 | )
127 | }
128 | BasicText(
129 | text = text,
130 | modifier = modifier,
131 | style = currentStyle,
132 | minLines = minLines,
133 | maxLines = maxLines,
134 | overflow = overflow,
135 | )
136 | }
137 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/TextField.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.interaction.MutableInteractionSource
6 | import androidx.compose.foundation.layout.IntrinsicSize
7 | import androidx.compose.foundation.layout.PaddingValues
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.foundation.layout.widthIn
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.foundation.text.KeyboardOptions
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.graphics.Shape
17 | import androidx.compose.ui.graphics.isSpecified
18 | import androidx.compose.ui.text.TextStyle
19 | import androidx.compose.ui.text.font.FontFamily
20 | import androidx.compose.ui.text.font.FontWeight
21 | import androidx.compose.ui.text.style.TextAlign
22 | import androidx.compose.ui.text.style.TextOverflow
23 | import androidx.compose.ui.unit.Dp
24 | import androidx.compose.ui.unit.TextUnit
25 | import androidx.compose.ui.unit.dp
26 | import androidx.compose.ui.unit.isSpecified
27 |
28 | @Composable
29 | fun TextField(
30 | value: String,
31 | editable: Boolean = true,
32 | onValueChange: (String) -> Unit,
33 | modifier: Modifier = Modifier,
34 | contentPadding: PaddingValues = PaddingValues(0.dp),
35 | placeholderText: String = "",
36 | contentColor: Color = LocalContentColor.current,
37 | backgroundColor: Color = Color.Unspecified,
38 | borderWidth: Dp = 0.dp,
39 | borderColor: Color = LocalOutlineColor.current,
40 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
41 | textStyle: TextStyle = TextStyle.Default,
42 | textAlign: TextAlign = TextAlign.Unspecified,
43 | fontSize: TextUnit = TextUnit.Unspecified,
44 | fontWeight: FontWeight? = null,
45 | fontFamily: FontFamily? = null,
46 | singleLine: Boolean = false,
47 | minLines: Int = 1,
48 | maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
49 | keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
50 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
51 | overflow: TextOverflow = TextOverflow.Ellipsis
52 | ) {
53 | val overrideColorOrUnspecified = if (contentColor.isSpecified) {
54 | contentColor
55 | } else if (textStyle.color.isSpecified) {
56 | textStyle.color
57 | } else {
58 | LocalContentColor.current
59 | }
60 |
61 | val overrideTextAlign = if (textAlign != TextAlign.Unspecified) {
62 | textAlign
63 | } else textStyle.textAlign
64 |
65 | val overrideFontSize = if (fontSize != TextUnit.Unspecified) {
66 | fontSize
67 | } else textStyle.fontSize
68 |
69 | val overrideFontWeight = fontWeight ?: textStyle.fontWeight
70 | val overrideFontFamily = fontFamily ?: textStyle.fontFamily
71 |
72 | val overriddenStyle = textStyle.merge(
73 | fontWeight = overrideFontWeight,
74 | fontSize = overrideFontSize,
75 | fontFamily = overrideFontFamily,
76 | textAlign = overrideTextAlign,
77 | )
78 | TextInput(
79 | modifier = modifier.widthIn(min = 2.dp).width(IntrinsicSize.Max) then buildModifier {
80 | if (borderWidth.isSpecified && borderWidth > 0.dp && borderColor.isSpecified) {
81 | add(Modifier.border(borderWidth, borderColor, shape))
82 | }
83 | add(
84 | Modifier
85 | .background(backgroundColor, shape)
86 | )
87 | },
88 | interactionSource = interactionSource,
89 | value = value,
90 | onValueChange = onValueChange,
91 | editable = editable,
92 | keyboardOptions = keyboardOptions,
93 | minLines = minLines,
94 | maxLines = maxLines,
95 | singleLine = singleLine,
96 | style = overriddenStyle,
97 | overflow = overflow,
98 | contentPadding = contentPadding,
99 | placeholder = placeholderText,
100 | )
101 | }
102 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/TextInput.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.focusable
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.interaction.collectIsFocusedAsState
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.IntrinsicSize
8 | import androidx.compose.foundation.layout.PaddingValues
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.width
11 | import androidx.compose.foundation.layout.widthIn
12 | import androidx.compose.foundation.text.BasicTextField
13 | import androidx.compose.foundation.text.KeyboardOptions
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.LaunchedEffect
16 | import androidx.compose.runtime.derivedStateOf
17 | import androidx.compose.runtime.getValue
18 | import androidx.compose.runtime.mutableStateOf
19 | import androidx.compose.runtime.remember
20 | import androidx.compose.runtime.setValue
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.focus.FocusRequester
23 | import androidx.compose.ui.focus.focusRequester
24 | import androidx.compose.ui.graphics.Brush
25 | import androidx.compose.ui.graphics.Color
26 | import androidx.compose.ui.graphics.SolidColor
27 | import androidx.compose.ui.graphics.isSpecified
28 | import androidx.compose.ui.text.TextRange
29 | import androidx.compose.ui.text.TextStyle
30 | import androidx.compose.ui.text.font.FontFamily
31 | import androidx.compose.ui.text.font.FontWeight
32 | import androidx.compose.ui.text.input.TextFieldValue
33 | import androidx.compose.ui.text.style.TextAlign
34 | import androidx.compose.ui.text.style.TextOverflow
35 | import androidx.compose.ui.unit.TextUnit
36 | import androidx.compose.ui.unit.dp
37 |
38 | @Composable
39 | fun TextInput(
40 | value: String,
41 | onValueChange: (String) -> Unit,
42 | editable: Boolean = true,
43 | modifier: Modifier = Modifier,
44 | style: TextStyle = LocalTextStyle.current,
45 | textAlign: TextAlign = style.textAlign,
46 | fontSize: TextUnit = style.fontSize,
47 | lineHeight: TextUnit = TextUnit.Unspecified,
48 | fontWeight: FontWeight? = style.fontWeight,
49 | color: Color = style.color,
50 | fontFamily: FontFamily? = style.fontFamily,
51 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
52 | singleLine: Boolean = false,
53 | overflow: TextOverflow = TextOverflow.Ellipsis,
54 | minLines: Int = 1,
55 | maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
56 | keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
57 | contentPadding: PaddingValues = PaddingValues(0.dp),
58 | cursorBrush: Brush = SolidColor(Color.Black),
59 | placeholder: String = "",
60 | ) {
61 | val placeholderColor = color.copy(alpha = 0.4f)
62 |
63 | val currentColor = listOf(
64 | color,
65 | style.color,
66 | LocalContentColor.current
67 | ).firstOrNull { it != Color.Unspecified } ?: Color.Unspecified
68 |
69 | val currentTextAlign = listOf(
70 | textAlign,
71 | style.textAlign,
72 | ).firstOrNull { it != TextAlign.Unspecified } ?: TextAlign.Unspecified
73 |
74 |
75 | val currentLineHeight = listOf(
76 | lineHeight,
77 | style.lineHeight,
78 | ).firstOrNull { it != TextUnit.Unspecified } ?: TextUnit.Unspecified
79 |
80 | val currentStyle = style.copy(
81 | textAlign = currentTextAlign,
82 | fontSize = fontSize,
83 | color = currentColor,
84 | fontWeight = fontWeight,
85 | fontFamily = fontFamily,
86 | lineHeight = currentLineHeight
87 | )
88 |
89 | var wasEditable by remember { mutableStateOf(editable) }
90 |
91 | var textRange by remember { mutableStateOf(TextRange(value.length, value.length)) }
92 |
93 | val isFocused by interactionSource.collectIsFocusedAsState()
94 |
95 | LaunchedEffect(editable) {
96 | if (wasEditable.not() && editable) {
97 | // just changed to editable. select all text
98 | textRange = TextRange(0, value.length)
99 | }
100 | wasEditable = editable
101 | }
102 |
103 | if (editable) {
104 | val textFieldValue by derivedStateOf { TextFieldValue(value, textRange) }
105 | val focusRequester = remember { FocusRequester() }
106 |
107 | LaunchedEffect(Unit) {
108 | if (isFocused) {
109 | focusRequester.requestFocus()
110 | }
111 | }
112 |
113 | BasicTextField(
114 | value = textFieldValue,
115 | onValueChange = {
116 | onValueChange(it.text)
117 | textRange = it.selection
118 | },
119 | textStyle = currentStyle,
120 | singleLine = singleLine,
121 | maxLines = maxLines,
122 | minLines = minLines,
123 | modifier = modifier
124 | .focusRequester(focusRequester)
125 | .widthIn(min = 2.dp) // width for the cursor blink
126 | .width(IntrinsicSize.Min) // override default width
127 | ,
128 | interactionSource = interactionSource,
129 | cursorBrush = cursorBrush,
130 | keyboardOptions = keyboardOptions
131 | ) {
132 | Box(Modifier.padding(contentPadding)) {
133 | it()
134 | if (value.isBlank()) {
135 | Text(placeholder, color = placeholderColor,style = style)
136 | }
137 | }
138 | }
139 | } else {
140 | val actualColor = if (value.isBlank() && placeholderColor.isSpecified) placeholderColor else color
141 |
142 | Text(
143 | text = value.ifBlank { placeholder },
144 | style = currentStyle,
145 | color = actualColor,
146 | modifier = modifier
147 | .widthIn(min = 2.dp) // width for the cursor blink
148 | .focusable(interactionSource = interactionSource)
149 | .padding(contentPadding),
150 | minLines = minLines,
151 | maxLines = maxLines,
152 | overflow = overflow,
153 | )
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.LocalIndication
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.CompositionLocalProvider
7 | import androidx.compose.runtime.compositionLocalOf
8 | import androidx.compose.runtime.staticCompositionLocalOf
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.text.TextStyle
11 | import androidx.compose.ui.unit.Dp
12 | import androidx.compose.ui.unit.dp
13 |
14 | val LocalBackgroundColor = compositionLocalOf { Color.Unspecified }
15 | val LocalOnBackgroundColor = compositionLocalOf { Color.Unspecified }
16 | val LocalPrimaryColor = compositionLocalOf { Color.Unspecified }
17 | val LocalOnPrimaryColor = compositionLocalOf { Color.Unspecified }
18 | val LocalSecondaryColor = compositionLocalOf { Color.Unspecified }
19 | val LocalOnSecondaryColor = compositionLocalOf { Color.Unspecified }
20 | val LocalSurfaceColor = compositionLocalOf { Color.Unspecified }
21 | val LocalOnSurfaceColor = compositionLocalOf { Color.Unspecified }
22 | val LocalDestructiveColor = compositionLocalOf { Color.Unspecified }
23 | val LocalOnDestructiveColor = compositionLocalOf { Color.Unspecified }
24 | val LocalOutlineColor = compositionLocalOf { Color.Unspecified }
25 | val LocalCornerRadius = compositionLocalOf { 0.dp }
26 |
27 | val LocalBodyLargeTextStyle = staticCompositionLocalOf { TextStyle.Default }
28 | val LocalBodyMediumTextStyle = staticCompositionLocalOf { TextStyle.Default }
29 | val LocalBodySmallTextStyle = staticCompositionLocalOf { TextStyle.Default }
30 | val LocalTitleLargeTextStyle = staticCompositionLocalOf { TextStyle.Default }
31 | val LocalTitleMediumTextStyle = staticCompositionLocalOf { TextStyle.Default }
32 | val LocalTitleSmallTextStyle = staticCompositionLocalOf { TextStyle.Default }
33 |
34 | val LocalTextStyle = staticCompositionLocalOf { TextStyle.Default }
35 | val LocalContentColor = compositionLocalOf { Color.Unspecified }
36 |
37 | @Composable
38 | fun Theme(
39 | onBackgroundColor: Color,
40 | backgroundColor: Color,
41 | primaryColor: Color,
42 | onPrimaryColor: Color,
43 | surfaceColor: Color,
44 | onSurfaceColor: Color,
45 | destructiveColor: Color,
46 | onDestructiveColor: Color,
47 | secondaryColor: Color,
48 | onSecondaryColor: Color,
49 | outlineColor: Color,
50 | cornerRadius: Dp,
51 | bodySmall: TextStyle,
52 | bodyMedium: TextStyle,
53 | bodyLarge: TextStyle,
54 | titleSmall: TextStyle,
55 | titleMedium: TextStyle,
56 | titleLarge: TextStyle,
57 | indication: Indication = LocalIndication.current,
58 | content: @Composable () -> Unit
59 | ) {
60 | CompositionLocalProvider(
61 | LocalTextStyle provides bodyLarge,
62 | LocalIndication provides indication,
63 |
64 | LocalOnBackgroundColor provides onBackgroundColor,
65 | LocalBackgroundColor provides backgroundColor,
66 | LocalPrimaryColor provides primaryColor,
67 | LocalOnPrimaryColor provides onPrimaryColor,
68 | LocalSurfaceColor provides surfaceColor,
69 | LocalOnSurfaceColor provides onSurfaceColor,
70 | LocalDestructiveColor provides destructiveColor,
71 | LocalOnDestructiveColor provides onDestructiveColor,
72 | LocalOutlineColor provides outlineColor,
73 | LocalSecondaryColor provides secondaryColor,
74 | LocalOnSecondaryColor provides onSecondaryColor,
75 | LocalCornerRadius provides cornerRadius,
76 |
77 | LocalBodySmallTextStyle provides bodySmall,
78 | LocalBodyMediumTextStyle provides bodyMedium,
79 | LocalBodyLargeTextStyle provides bodyLarge,
80 |
81 | LocalTitleLargeTextStyle provides titleLarge,
82 | LocalTitleMediumTextStyle provides titleMedium,
83 | LocalTitleSmallTextStyle provides titleSmall,
84 |
85 | LocalTextStyle provides bodyMedium,
86 | ) {
87 | content()
88 | }
89 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/Toggle.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.animation.animateColorAsState
4 | import androidx.compose.animation.core.animateDpAsState
5 | import androidx.compose.foundation.LocalIndication
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.interaction.MutableInteractionSource
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.PaddingValues
10 | import androidx.compose.foundation.layout.calculateEndPadding
11 | import androidx.compose.foundation.layout.calculateStartPadding
12 | import androidx.compose.foundation.layout.offset
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.foundation.layout.size
15 | import androidx.compose.foundation.layout.widthIn
16 | import androidx.compose.foundation.selection.toggleable
17 | import androidx.compose.foundation.shape.RoundedCornerShape
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.derivedStateOf
20 | import androidx.compose.runtime.getValue
21 | import androidx.compose.runtime.mutableStateOf
22 | import androidx.compose.runtime.remember
23 | import androidx.compose.runtime.setValue
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.draw.clip
26 | import androidx.compose.ui.draw.shadow
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.graphics.Shape
29 | import androidx.compose.ui.layout.onPlaced
30 | import androidx.compose.ui.platform.LocalDensity
31 | import androidx.compose.ui.platform.LocalLayoutDirection
32 | import androidx.compose.ui.semantics.Role
33 | import androidx.compose.ui.unit.dp
34 |
35 | @Composable
36 | fun Toggle(
37 | toggled: Boolean,
38 | modifier: Modifier = Modifier,
39 | onToggled: ((Boolean) -> Unit)? = null,
40 | enabled: Boolean = true,
41 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
42 | toggledColor: Color = LocalPrimaryColor.current,
43 | backgroundColor: Color = LocalContentColor.current.copy(0.44f),
44 | contentPadding: PaddingValues = PaddingValues(0.dp),
45 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
46 | thumb: @Composable () -> Unit,
47 | ) {
48 | var trackWidth by remember { mutableStateOf(0.dp) }
49 |
50 | val layoutDirection = LocalLayoutDirection.current
51 |
52 | val paddingEnd by derivedStateOf {
53 | contentPadding.calculateEndPadding(layoutDirection) + contentPadding.calculateStartPadding(layoutDirection)
54 | }
55 |
56 | val maxOffset by derivedStateOf { (trackWidth / 2) - paddingEnd }
57 | val offset by animateDpAsState(if (toggled) maxOffset else 0.dp)
58 |
59 | val backgroundColor by animateColorAsState(if (toggled) toggledColor else backgroundColor)
60 |
61 | val density = LocalDensity.current
62 |
63 | Box(
64 | modifier = modifier
65 | .widthIn(min = 48.dp)
66 | .clip(shape)
67 | .background(backgroundColor, shape)
68 | .onPlaced { trackWidth = with(density) { it.size.width.toDp() } }
69 | then (if (onToggled != null)
70 | Modifier.toggleable(
71 | value = toggled,
72 | enabled = enabled,
73 | interactionSource = interactionSource,
74 | indication = LocalIndication.current,
75 | role = Role.Switch
76 | ) { onToggled(toggled.not()) }
77 | else Modifier)
78 | .padding(contentPadding)
79 | ) {
80 | Box(Modifier.offset(x = offset)) {
81 | thumb()
82 | }
83 | }
84 | }
85 |
86 |
87 | @Composable
88 | fun Thumb(
89 | modifier: Modifier = Modifier,
90 | shape: Shape = RoundedCornerShape(LocalCornerRadius.current),
91 | color: Color = LocalSurfaceColor.current
92 | ) {
93 | Box(
94 | modifier
95 | .shadow(2.dp, shape)
96 | .clip(shape)
97 | .background(color)
98 | .size(24.dp)
99 | )
100 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/builtwithpaper/TopAppBar.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.PaddingValues
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.fillMaxHeight
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.layout.width
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.CompositionLocalProvider
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.shadow
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.unit.Dp
19 | import androidx.compose.ui.unit.dp
20 |
21 | @Composable
22 | fun TopAppBar(
23 | modifier: Modifier = Modifier,
24 | navigation: (@Composable () -> Unit)? = null,
25 | title: @Composable () -> Unit = {},
26 | actions: @Composable () -> Unit = {},
27 | elevation: Dp = 0.dp,
28 | contentColor: Color = LocalOnSurfaceColor.current,
29 | backgroundColor: Color = LocalSurfaceColor.current,
30 | contentPadding: PaddingValues = PaddingValues(0.dp),
31 | ) {
32 | Row(
33 | modifier = modifier
34 | .shadow(elevation)
35 | .background(backgroundColor)
36 | .padding(contentPadding),
37 | verticalAlignment = Alignment.CenterVertically,
38 | horizontalArrangement = Arrangement.spacedBy(8.dp),
39 | ) {
40 | CompositionLocalProvider(LocalContentColor provides contentColor) {
41 | if (navigation == null) {
42 | Spacer(Modifier.width(12.dp).fillMaxHeight())
43 | } else {
44 | Box(Modifier.fillMaxHeight(), contentAlignment = Alignment.CenterStart) {
45 | navigation()
46 | }
47 | }
48 | Box(
49 | modifier = Modifier.weight(1f).fillMaxHeight(),
50 | contentAlignment = Alignment.CenterStart
51 | ) {
52 | title()
53 | }
54 | Row(
55 | verticalAlignment = Alignment.CenterVertically,
56 | horizontalArrangement = Arrangement.spacedBy(8.dp),
57 | modifier = Modifier.fillMaxHeight()
58 | ) {
59 | actions()
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/AddContact.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.background
9 | import com.builtwithpaper.LocalSurfaceColor
10 | import androidx.compose.foundation.verticalScroll
11 | import androidx.compose.foundation.rememberScrollState
12 | import com.builtwithpaper.LocalOnSurfaceColor
13 | import androidx.compose.runtime.CompositionLocalProvider
14 | import com.builtwithpaper.LocalContentColor
15 | import com.builtwithpaper.TopAppBar
16 | import androidx.compose.foundation.layout.height
17 | import androidx.compose.ui.unit.dp
18 | import androidx.compose.foundation.layout.fillMaxWidth
19 | import com.builtwithpaper.GhostButton
20 | import androidx.compose.foundation.layout.PaddingValues
21 | import com.composables.icons.lucide.LucideIcon
22 | import com.builtwithpaper.Text
23 | import androidx.compose.ui.unit.sp
24 | import androidx.compose.foundation.layout.Arrangement
25 | import androidx.compose.foundation.layout.widthIn
26 | import androidx.compose.foundation.layout.fillMaxHeight
27 | import androidx.compose.foundation.layout.padding
28 | import com.builtwithpaper.SecondaryButton
29 | import com.builtwithpaper.LocalOnSecondaryColor
30 | import com.builtwithpaper.LocalSecondaryColor
31 | import androidx.compose.foundation.layout.Row
32 | import com.builtwithpaper.LocalPrimaryColor
33 | import com.builtwithpaper.TextField
34 | import androidx.compose.foundation.shape.RoundedCornerShape
35 | import com.builtwithpaper.LocalCornerRadius
36 | import androidx.compose.ui.text.TextStyle
37 | import androidx.compose.ui.draw.alpha
38 |
39 | @Composable
40 | fun AddContact() {
41 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize().background(LocalSurfaceColor.current).verticalScroll(rememberScrollState())) {
42 | CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColor.current) {
43 | TopAppBar(
44 | modifier = Modifier.height(56.dp).fillMaxWidth(),
45 | navigation = {
46 | GhostButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(8.dp)) {
47 | LucideIcon("arrow-left", contentDescription = "", tint = LocalContentColor.current)
48 | }
49 | },
50 | title = {
51 | Text("Add Contact", fontSize = 16.sp, maxLines = 99)
52 | },
53 | contentPadding = PaddingValues(4.dp)
54 | )
55 | Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight().verticalScroll(rememberScrollState()).padding(16.dp)) {
56 | SecondaryButton(onClick = { /* TODO Handle this */ }, contentColor = LocalOnSecondaryColor.current, backgroundColor = LocalSecondaryColor.current, contentPadding = PaddingValues(start = 8.dp, top = 12.dp, end = 8.dp, bottom = 12.dp), borderColor = LocalSecondaryColor.current, modifier = Modifier.height(200.dp).fillMaxWidth()) {
57 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize()) {
58 | LucideIcon("image-plus", contentDescription = "", tint = LocalContentColor.current)
59 | }
60 | }
61 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
62 | LucideIcon("contact-round", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
63 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "First name", contentPadding = PaddingValues(16.dp), borderWidth = 1.dp, shape = RoundedCornerShape(LocalCornerRadius.current), textStyle = TextStyle.Default, fontSize = 16.sp)
64 | }
65 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
66 | LucideIcon("contact-round", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.alpha(0f).padding(16.dp))
67 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "Last name", contentPadding = PaddingValues(16.dp), borderWidth = 1.dp, shape = RoundedCornerShape(LocalCornerRadius.current), textStyle = TextStyle.Default, fontSize = 16.sp)
68 | }
69 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
70 | LucideIcon("phone", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
71 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "Phone number", contentPadding = PaddingValues(16.dp), borderWidth = 1.dp, shape = RoundedCornerShape(LocalCornerRadius.current), textStyle = TextStyle.Default, fontSize = 16.sp)
72 | }
73 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
74 | LucideIcon("map-pin", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
75 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "Address", contentPadding = PaddingValues(16.dp), borderWidth = 1.dp, shape = RoundedCornerShape(LocalCornerRadius.current), textStyle = TextStyle.Default, fontSize = 16.sp)
76 | }
77 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
78 | LucideIcon("contact-round", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.alpha(0f).padding(16.dp))
79 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "City", contentPadding = PaddingValues(16.dp), borderWidth = 1.dp, shape = RoundedCornerShape(LocalCornerRadius.current), textStyle = TextStyle.Default, fontSize = 16.sp)
80 | }
81 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
82 | LucideIcon("contact-round", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.alpha(0f).padding(16.dp))
83 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "Post code", contentPadding = PaddingValues(16.dp), borderWidth = 1.dp, shape = RoundedCornerShape(LocalCornerRadius.current), textStyle = TextStyle.Default, fontSize = 16.sp)
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/App.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.navigation.compose.rememberNavController
5 | import com.builtwithpaper.Theme
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.text.TextStyle
8 | import androidx.compose.ui.text.font.FontWeight
9 | import androidx.compose.ui.unit.sp
10 | import androidx.compose.ui.unit.dp
11 | import androidx.navigation.compose.NavHost
12 | import androidx.navigation.compose.composable
13 |
14 | @Composable
15 | fun App() {
16 | val navController = rememberNavController()
17 | Theme(primaryColor = Color(0xFF2563EB), onPrimaryColor = Color(0xFFFFFFFF), secondaryColor = Color(0xFFE0F2FE), onSecondaryColor = Color(0xFF1A1A1A), backgroundColor = Color(0xFFFAFAFA), onBackgroundColor = Color(0xFF0F172A), surfaceColor = Color(0xFFFFFFFF), onSurfaceColor = Color(0xFF1A1A1A), destructiveColor = Color(0xFFDC2626), onDestructiveColor = Color(0xFFFFFFFF), outlineColor = Color(0xFFD1D5DB), titleLarge = TextStyle(fontWeight = FontWeight(600), fontSize = 32.sp, lineHeight = 27.sp), titleMedium = TextStyle(fontWeight = FontWeight(500), fontSize = 24.sp, lineHeight = 18.sp), titleSmall = TextStyle(fontWeight = FontWeight(500), fontSize = 18.sp, lineHeight = 15.sp), bodyLarge = TextStyle(fontWeight = FontWeight(400), fontSize = 16.sp, lineHeight = 21.sp), bodyMedium = TextStyle(fontWeight = FontWeight(400), fontSize = 16.sp, lineHeight = 18.sp), bodySmall = TextStyle(fontWeight = FontWeight(400), fontSize = 10.sp, lineHeight = 15.sp), cornerRadius = 24.dp) {
18 | NavHost(navController = navController, startDestination = "Conversations") {
19 | composable(route = "Conversations") {
20 | Conversations()
21 | }
22 | composable(route = "Conversation") {
23 | Conversation()
24 | }
25 | composable(route = "Profile") {
26 | Profile()
27 | }
28 | composable(route = "Search") {
29 | Search()
30 | }
31 | composable(route = "Contacts") {
32 | Contacts()
33 | }
34 | composable(route = "Settings") {
35 | Settings()
36 | }
37 | composable(route = "AddContact") {
38 | AddContact()
39 | }
40 | }
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/Contacts.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.background
9 | import com.builtwithpaper.LocalBackgroundColor
10 | import com.builtwithpaper.LocalOnBackgroundColor
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import com.builtwithpaper.LocalContentColor
13 | import androidx.compose.foundation.layout.widthIn
14 | import androidx.compose.ui.unit.dp
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.fillMaxHeight
17 | import androidx.compose.foundation.layout.padding
18 | import androidx.compose.foundation.layout.Row
19 | import androidx.compose.foundation.layout.Arrangement
20 | import androidx.compose.foundation.layout.height
21 | import androidx.compose.ui.draw.clip
22 | import androidx.compose.foundation.shape.RoundedCornerShape
23 | import androidx.compose.ui.draw.shadow
24 | import androidx.compose.ui.zIndex
25 | import com.builtwithpaper.LocalSurfaceColor
26 | import com.builtwithpaper.LocalOnSurfaceColor
27 | import com.composables.icons.lucide.LucideIcon
28 | import com.builtwithpaper.TextField
29 | import androidx.compose.foundation.layout.PaddingValues
30 | import androidx.compose.ui.text.TextStyle
31 | import androidx.compose.ui.unit.sp
32 | import com.builtwithpaper.GhostButton
33 | import androidx.compose.foundation.lazy.LazyColumn
34 | import com.builtwithpaper.Text
35 | import androidx.compose.ui.text.font.FontWeight
36 | import com.builtwithpaper.LocalPrimaryColor
37 | import androidx.compose.foundation.Image
38 | import coil3.compose.rememberAsyncImagePainter
39 | import androidx.compose.foundation.layout.width
40 | import androidx.compose.ui.layout.ContentScale
41 | import androidx.compose.ui.draw.alpha
42 | import com.builtwithpaper.BottomNavigation
43 | import androidx.compose.foundation.layout.heightIn
44 | import androidx.compose.ui.graphics.RectangleShape
45 | import androidx.compose.ui.graphics.Color
46 | import com.builtwithpaper.currentScreenWidthBreakpoint
47 | import com.builtwithpaper.isAtLeast
48 | import com.builtwithpaper.ScreenWidthBreakpoint
49 | import com.builtwithpaper.TabItem
50 |
51 | @Composable
52 | fun Contacts() {
53 | val breakpoint = currentScreenWidthBreakpoint()
54 |
55 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize().background(LocalBackgroundColor.current)) {
56 | val breakpoint = currentScreenWidthBreakpoint()
57 |
58 | CompositionLocalProvider(LocalContentColor provides LocalOnBackgroundColor.current) {
59 | Column(modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight().padding(top = 16.dp, end = 0.dp, bottom = 0.dp, start = 0.dp)) {
60 | Row(modifier = Modifier.padding(top = 0.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
61 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.height(56.dp).weight(1f, fill = false).fillMaxWidth().clip(RoundedCornerShape(100.dp)).shadow(4.dp, RoundedCornerShape(100.dp)).zIndex(4f).background(LocalSurfaceColor.current).padding(top = 0.dp, end = 8.dp, bottom = 0.dp, start = 16.dp)) {
62 | CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColor.current) {
63 | LucideIcon("search", contentDescription = "", tint = LocalContentColor.current)
64 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "Search", contentPadding = PaddingValues(start = 8.dp, top = 12.dp, end = 8.dp, bottom = 12.dp), textStyle = TextStyle.Default, fontSize = 16.sp)
65 | Row {
66 | GhostButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(start = 8.dp, top = 12.dp, end = 8.dp, bottom = 12.dp)) {
67 | LucideIcon("circle-plus", contentDescription = "", tint = LocalContentColor.current)
68 | }
69 | }
70 | }
71 | }
72 | }
73 | LazyColumn(contentPadding = PaddingValues(start = 0.dp, top = 16.dp, end = 0.dp, bottom = 16.dp), modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight()) {
74 | item {
75 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
76 | Text("A", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
77 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1607746882042-944635dfe10e?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
78 | Text("Alicia", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
79 | }
80 | }
81 | item {
82 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
83 | Text("A", modifier = Modifier.alpha(0f).padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
84 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1650630729397-810915b32ac9?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
85 | Text("Antony", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
86 | }
87 | }
88 | item {
89 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
90 | Text("B", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
91 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1500648767791-00dcc994a43e?q=80&w=3000&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
92 | Text("Ben", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
93 | }
94 | }
95 | item {
96 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
97 | Text("B", modifier = Modifier.alpha(0f).padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
98 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1508186225823-0963cf9ab0de?q=80&w=3166&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
99 | Text("Brianna", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
100 | }
101 | }
102 | item {
103 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
104 | Text("C", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
105 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
106 | Text("Cindy", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
107 | }
108 | }
109 | item {
110 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
111 | Text("D", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
112 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=3276&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
113 | Text("Daisy", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
114 | }
115 | }
116 | item {
117 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
118 | Text("N", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
119 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
120 | Text("Naomi", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
121 | }
122 | }
123 | item {
124 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
125 | Text("P", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
126 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
127 | Text("Paolo", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
128 | }
129 | }
130 | item {
131 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
132 | Text("P", modifier = Modifier.alpha(0f).padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
133 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1599566147214-ce487862ea4f?q=80&w=3347&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
134 | Text("Peter", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
135 | }
136 | }
137 | item {
138 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
139 | Text("P", modifier = Modifier.alpha(0f).padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 16.dp), fontSize = 16.sp, fontWeight = FontWeight(700), color = LocalPrimaryColor.current, maxLines = 99)
140 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=3388&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
141 | Text("Petra", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
142 | }
143 | }
144 | }
145 | }
146 | BottomNavigation(modifier = Modifier.heightIn(56.dp).fillMaxWidth().shadow(4.dp, RectangleShape).zIndex(4f).background(if (breakpoint isAtLeast ScreenWidthBreakpoint.Small) LocalSurfaceColor.current else Color.Unspecified), contentColor = LocalOnSurfaceColor.current, backgroundColor = LocalSurfaceColor.current) {
147 | TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
148 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
149 | LucideIcon("house", contentDescription = "", tint = LocalContentColor.current)
150 | Text("Chats", fontSize = 16.sp, maxLines = 99)
151 | }
152 | }
153 | TabItem(selected = true, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
154 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
155 | LucideIcon("contact", contentDescription = "", tint = LocalContentColor.current)
156 | Text("Contacts", fontSize = 16.sp, maxLines = 99)
157 | }
158 | }
159 | TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
160 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
161 | LucideIcon("settings", contentDescription = "", tint = LocalContentColor.current)
162 | Text("Settings", fontSize = 16.sp, maxLines = 99)
163 | }
164 | }
165 | }
166 | }
167 | }
168 | }
169 |
170 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/Conversation.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.background
9 | import com.builtwithpaper.LocalBackgroundColor
10 | import com.builtwithpaper.LocalOnBackgroundColor
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import com.builtwithpaper.LocalContentColor
13 | import com.builtwithpaper.TopAppBar
14 | import androidx.compose.foundation.layout.height
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.foundation.layout.fillMaxWidth
17 | import com.builtwithpaper.GhostButton
18 | import androidx.compose.foundation.layout.PaddingValues
19 | import com.composables.icons.lucide.LucideIcon
20 | import androidx.compose.foundation.layout.Arrangement
21 | import androidx.compose.ui.draw.clip
22 | import androidx.compose.foundation.shape.RoundedCornerShape
23 | import com.builtwithpaper.LocalCornerRadius
24 | import com.builtwithpaper.Text
25 | import androidx.compose.ui.unit.sp
26 | import androidx.compose.foundation.lazy.LazyColumn
27 | import androidx.compose.foundation.layout.widthIn
28 | import androidx.compose.foundation.layout.fillMaxHeight
29 | import androidx.compose.foundation.layout.Row
30 | import androidx.compose.foundation.layout.padding
31 | import androidx.compose.foundation.layout.Box
32 | import androidx.compose.foundation.layout.width
33 | import androidx.compose.foundation.Image
34 | import coil3.compose.rememberAsyncImagePainter
35 | import androidx.compose.ui.layout.ContentScale
36 | import com.builtwithpaper.LocalSurfaceColor
37 | import com.builtwithpaper.LocalOnSurfaceColor
38 | import com.builtwithpaper.LocalPrimaryColor
39 | import com.builtwithpaper.LocalOnPrimaryColor
40 | import androidx.compose.ui.draw.alpha
41 | import com.builtwithpaper.Card
42 | import com.builtwithpaper.TextField
43 | import androidx.compose.ui.text.TextStyle
44 |
45 | @Composable
46 | fun Conversation() {
47 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize().background(LocalBackgroundColor.current)) {
48 | CompositionLocalProvider(LocalContentColor provides LocalOnBackgroundColor.current) {
49 | TopAppBar(
50 | modifier = Modifier.height(56.dp).fillMaxWidth(),
51 | navigation = {
52 | GhostButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(8.dp)) {
53 | LucideIcon("arrow-left", contentDescription = "", tint = LocalContentColor.current)
54 | }
55 | },
56 | title = {
57 | Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize().clip(RoundedCornerShape(LocalCornerRadius.current))) {
58 | Text("Kim", fontSize = 16.sp, maxLines = 99)
59 | }
60 | },
61 | backgroundColor = LocalBackgroundColor.current,
62 | contentPadding = PaddingValues(4.dp)
63 | )
64 | LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(start = 0.dp, top = 16.dp, end = 0.dp, bottom = 16.dp), modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight()) {
65 | item {
66 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 0.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
67 | Box(contentAlignment = Alignment.Center, modifier = Modifier.width(44.dp).height(44.dp).clip(RoundedCornerShape(100.dp))) {
68 | Text("K", fontSize = 16.sp, maxLines = 99)
69 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "Kim's Photo", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
70 | }
71 | Column(modifier = Modifier.clip(RoundedCornerShape(LocalCornerRadius.current)).background(LocalSurfaceColor.current).padding(top = 14.dp, end = 16.dp, bottom = 14.dp, start = 16.dp)) {
72 | CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColor.current) {
73 | Text("Looking forward to the trip.", fontSize = 16.sp, maxLines = 99)
74 | }
75 | }
76 | }
77 | }
78 | item {
79 | Row(horizontalArrangement = Arrangement.spacedBy(space = 8.dp, alignment = Alignment.End), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 0.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
80 | Column(modifier = Modifier.clip(RoundedCornerShape(LocalCornerRadius.current)).background(LocalPrimaryColor.current).padding(top = 14.dp, end = 16.dp, bottom = 14.dp, start = 16.dp)) {
81 | CompositionLocalProvider(LocalContentColor provides LocalOnPrimaryColor.current) {
82 | Text("Same here! Can't wait", fontSize = 16.sp, maxLines = 99)
83 | }
84 | }
85 | }
86 | }
87 | item {
88 | Row(horizontalArrangement = Arrangement.spacedBy(space = 8.dp, alignment = Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 0.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
89 | Text("Today 17:30", modifier = Modifier.alpha(0.66f), fontSize = 12.sp, maxLines = 99)
90 | }
91 | }
92 | item {
93 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.Bottom, modifier = Modifier.fillMaxWidth().padding(top = 0.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
94 | Box(contentAlignment = Alignment.Center, modifier = Modifier.width(44.dp).height(44.dp).clip(RoundedCornerShape(100.dp))) {
95 | Text("K", fontSize = 16.sp, maxLines = 99)
96 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "Kim's Photo", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
97 | }
98 | Card(elevation = 0.dp, backgroundColor = LocalSurfaceColor.current, contentColor = LocalOnSurfaceColor.current, shape = RoundedCornerShape(LocalCornerRadius.current)) {
99 | Column {
100 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1506744038136-46273834b3fb?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "Hero Image", modifier = Modifier.height(200.dp).fillMaxWidth(), contentScale = ContentScale.Crop)
101 | Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.fillMaxWidth().padding(16.dp)) {
102 | Text("Fishing by the sea side", modifier = Modifier.fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
103 | Text("Find your next dream destination", modifier = Modifier.fillMaxWidth().alpha(0.6f), maxLines = 99)
104 | }
105 | }
106 | }
107 | }
108 | }
109 | item {
110 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.Bottom, modifier = Modifier.fillMaxWidth().padding(top = 0.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
111 | Box(contentAlignment = Alignment.Center, modifier = Modifier.width(44.dp).height(44.dp).clip(RoundedCornerShape(100.dp))) {
112 | Text("K", fontSize = 16.sp, maxLines = 99)
113 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "Kim's Photo", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
114 | }
115 | Column(modifier = Modifier.clip(RoundedCornerShape(LocalCornerRadius.current)).background(LocalSurfaceColor.current).padding(top = 14.dp, end = 16.dp, bottom = 14.dp, start = 16.dp)) {
116 | CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColor.current) {
117 | Text("What do you think?", fontSize = 16.sp, maxLines = 99)
118 | }
119 | }
120 | }
121 | }
122 | item {
123 | Row(horizontalArrangement = Arrangement.spacedBy(space = 8.dp, alignment = Alignment.End), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 0.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
124 | Column(modifier = Modifier.clip(RoundedCornerShape(LocalCornerRadius.current)).background(LocalPrimaryColor.current).padding(top = 14.dp, end = 16.dp, bottom = 14.dp, start = 16.dp)) {
125 | CompositionLocalProvider(LocalContentColor provides LocalOnPrimaryColor.current) {
126 | Text("It's perfect!", fontSize = 16.sp, maxLines = 99)
127 | }
128 | }
129 | }
130 | }
131 | }
132 | Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(16.dp)) {
133 | GhostButton(onClick = { /* TODO Handle this */ }, contentColor = LocalPrimaryColor.current, contentPadding = PaddingValues(12.dp)) {
134 | LucideIcon("camera", contentDescription = "", tint = LocalContentColor.current)
135 | }
136 | GhostButton(onClick = { /* TODO Handle this */ }, contentColor = LocalPrimaryColor.current, contentPadding = PaddingValues(12.dp)) {
137 | LucideIcon("image-play", contentDescription = "", tint = LocalContentColor.current)
138 | }
139 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 5, placeholderText = "Type your message", contentPadding = PaddingValues(16.dp), contentColor = LocalOnSurfaceColor.current, backgroundColor = LocalSurfaceColor.current, borderWidth = 1.dp, shape = RoundedCornerShape(LocalCornerRadius.current), textStyle = TextStyle.Default, fontSize = 16.sp)
140 | }
141 | }
142 | }
143 | }
144 |
145 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/Conversations.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.background
9 | import com.builtwithpaper.LocalBackgroundColor
10 | import com.builtwithpaper.LocalOnBackgroundColor
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import com.builtwithpaper.LocalContentColor
13 | import androidx.compose.foundation.layout.Arrangement
14 | import androidx.compose.foundation.layout.widthIn
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.foundation.layout.fillMaxWidth
17 | import androidx.compose.foundation.layout.fillMaxHeight
18 | import androidx.compose.foundation.layout.padding
19 | import androidx.compose.foundation.layout.Row
20 | import androidx.compose.foundation.layout.height
21 | import androidx.compose.ui.draw.clip
22 | import androidx.compose.foundation.shape.RoundedCornerShape
23 | import androidx.compose.ui.draw.shadow
24 | import androidx.compose.ui.zIndex
25 | import com.builtwithpaper.LocalSurfaceColor
26 | import com.builtwithpaper.LocalOnSurfaceColor
27 | import com.composables.icons.lucide.LucideIcon
28 | import com.builtwithpaper.Text
29 | import androidx.compose.ui.draw.alpha
30 | import androidx.compose.ui.unit.sp
31 | import com.builtwithpaper.GhostButton
32 | import androidx.compose.foundation.layout.PaddingValues
33 | import androidx.compose.foundation.lazy.LazyColumn
34 | import com.builtwithpaper.LocalCornerRadius
35 | import androidx.compose.foundation.Image
36 | import coil3.compose.rememberAsyncImagePainter
37 | import androidx.compose.foundation.layout.width
38 | import androidx.compose.ui.layout.ContentScale
39 | import com.builtwithpaper.LocalPrimaryColor
40 | import com.builtwithpaper.LocalOnPrimaryColor
41 | import androidx.compose.ui.text.style.TextAlign
42 | import androidx.compose.ui.text.font.FontWeight
43 | import com.builtwithpaper.BottomNavigation
44 | import androidx.compose.foundation.layout.heightIn
45 | import androidx.compose.ui.graphics.RectangleShape
46 | import androidx.compose.ui.graphics.Color
47 | import com.builtwithpaper.currentScreenWidthBreakpoint
48 | import com.builtwithpaper.isAtLeast
49 | import com.builtwithpaper.ScreenWidthBreakpoint
50 | import com.builtwithpaper.TabItem
51 |
52 | @Composable
53 | fun Conversations() {
54 | val breakpoint = currentScreenWidthBreakpoint()
55 |
56 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize().background(LocalBackgroundColor.current)) {
57 | val breakpoint = currentScreenWidthBreakpoint()
58 |
59 | CompositionLocalProvider(LocalContentColor provides LocalOnBackgroundColor.current) {
60 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Bottom, modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight().padding(top = 16.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
61 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.height(56.dp).fillMaxWidth().clip(RoundedCornerShape(100.dp)).shadow(4.dp, RoundedCornerShape(100.dp)).zIndex(4f).background(LocalSurfaceColor.current).padding(top = 0.dp, end = 8.dp, bottom = 0.dp, start = 16.dp)) {
62 | CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColor.current) {
63 | LucideIcon("search", contentDescription = "", tint = LocalContentColor.current)
64 | Text("Search", modifier = Modifier.weight(1f, fill = false).fillMaxWidth().alpha(0.4f).padding(top = 12.dp, end = 8.dp, bottom = 12.dp, start = 8.dp), fontSize = 16.sp, maxLines = 99)
65 | Row {
66 | GhostButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(8.dp)) {
67 | LucideIcon("message-circle-plus", contentDescription = "", tint = LocalContentColor.current)
68 | }
69 | }
70 | }
71 | }
72 | LazyColumn(contentPadding = PaddingValues(start = 0.dp, top = 16.dp, end = 0.dp, bottom = 16.dp), modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight()) {
73 | item {
74 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)).padding(8.dp)) {
75 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "Jenny's profile photo", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
76 | Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
77 | Row {
78 | Text("Kim", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
79 | Text("3min", modifier = Modifier.alpha(0.6f), fontSize = 16.sp, maxLines = 99)
80 | }
81 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
82 | Text("What do you think?", modifier = Modifier.weight(1f, fill = false).fillMaxWidth().alpha(0.6f), fontSize = 16.sp, maxLines = 99)
83 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.width(22.dp).height(22.dp).clip(RoundedCornerShape(100.dp)).background(LocalPrimaryColor.current)) {
84 | CompositionLocalProvider(LocalContentColor provides LocalOnPrimaryColor.current) {
85 | Text("1", modifier = Modifier.clip(RoundedCornerShape(100.dp)), textAlign = TextAlign.Center, fontSize = 12.sp, fontWeight = FontWeight(500), maxLines = 1)
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 | item {
93 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)).padding(8.dp)) {
94 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1599566147214-ce487862ea4f?q=80&w=3347&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
95 | Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
96 | Row {
97 | Text("Peter", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
98 | Text("15min", modifier = Modifier.alpha(0.6f), fontSize = 16.sp, maxLines = 99)
99 | }
100 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
101 | Text("Isn't that swell?", modifier = Modifier.weight(1f, fill = false).fillMaxWidth().alpha(0.6f), fontSize = 16.sp, maxLines = 99)
102 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.width(22.dp).height(22.dp).clip(RoundedCornerShape(100.dp)).background(LocalPrimaryColor.current)) {
103 | CompositionLocalProvider(LocalContentColor provides LocalOnPrimaryColor.current) {
104 | Text("2", modifier = Modifier.clip(RoundedCornerShape(100.dp)), textAlign = TextAlign.Center, fontSize = 12.sp, fontWeight = FontWeight(500), maxLines = 1)
105 | }
106 | }
107 | }
108 | }
109 | }
110 | }
111 | item {
112 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)).padding(8.dp)) {
113 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
114 | Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
115 | Row {
116 | Text("Naomi", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
117 | Text("Fri", modifier = Modifier.alpha(0.6f), fontSize = 16.sp, maxLines = 99)
118 | }
119 | Row {
120 | Text("Had a great time too! 😍", modifier = Modifier.weight(1f, fill = false).fillMaxWidth().alpha(0.6f), fontSize = 16.sp, maxLines = 99)
121 | }
122 | }
123 | }
124 | }
125 | item {
126 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)).padding(8.dp)) {
127 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=3388&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
128 | Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
129 | Row {
130 | Text("Petra", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
131 | Text("Wed", modifier = Modifier.alpha(0.6f), fontSize = 16.sp, maxLines = 99)
132 | }
133 | Row {
134 | Text("What do you think?", modifier = Modifier.weight(1f, fill = false).fillMaxWidth().alpha(0.6f), fontSize = 16.sp, maxLines = 99)
135 | }
136 | }
137 | }
138 | }
139 | item {
140 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)).padding(8.dp)) {
141 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
142 | Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
143 | Row {
144 | Text("Paolo", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
145 | Text("Wed", modifier = Modifier.alpha(0.6f), fontSize = 16.sp, maxLines = 99)
146 | }
147 | Row {
148 | Text("Paolo sent a photo.", modifier = Modifier.weight(1f, fill = false).fillMaxWidth().alpha(0.6f), fontSize = 16.sp, maxLines = 99)
149 | }
150 | }
151 | }
152 | }
153 | item {
154 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)).padding(8.dp)) {
155 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1607746882042-944635dfe10e?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "Jenny's profile photo", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
156 | Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
157 | Row {
158 | Text("Alicia", modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
159 | Text("Mon", modifier = Modifier.alpha(0.6f), fontSize = 16.sp, maxLines = 99)
160 | }
161 | Row {
162 | Text("Is that what really happened?", modifier = Modifier.weight(1f, fill = false).fillMaxWidth().alpha(0.6f), fontSize = 16.sp, maxLines = 1)
163 | }
164 | }
165 | }
166 | }
167 | }
168 | }
169 | BottomNavigation(modifier = Modifier.heightIn(56.dp).fillMaxWidth().shadow(4.dp, RectangleShape).zIndex(4f).background(if (breakpoint isAtLeast ScreenWidthBreakpoint.Small) LocalSurfaceColor.current else Color.Unspecified), contentColor = LocalOnSurfaceColor.current, backgroundColor = LocalSurfaceColor.current) {
170 | TabItem(selected = true, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
171 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
172 | LucideIcon("house", contentDescription = "", tint = LocalContentColor.current)
173 | Text("Chats", fontSize = 16.sp, maxLines = 99)
174 | }
175 | }
176 | TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
177 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
178 | LucideIcon("contact", contentDescription = "", tint = LocalContentColor.current)
179 | Text("Contacts", fontSize = 16.sp, maxLines = 99)
180 | }
181 | }
182 | TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
183 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
184 | LucideIcon("settings", contentDescription = "", tint = LocalContentColor.current)
185 | Text("Settings", fontSize = 16.sp, maxLines = 99)
186 | }
187 | }
188 | }
189 | }
190 | }
191 | }
192 |
193 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/Profile.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.background
9 | import com.builtwithpaper.LocalSurfaceColor
10 | import com.builtwithpaper.LocalOnSurfaceColor
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import com.builtwithpaper.LocalContentColor
13 | import com.builtwithpaper.TopAppBar
14 | import androidx.compose.foundation.layout.height
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.foundation.layout.fillMaxWidth
17 | import com.builtwithpaper.GhostButton
18 | import androidx.compose.foundation.layout.PaddingValues
19 | import com.composables.icons.lucide.LucideIcon
20 | import androidx.compose.ui.graphics.Color
21 | import androidx.compose.foundation.layout.widthIn
22 | import androidx.compose.foundation.layout.fillMaxHeight
23 | import androidx.compose.foundation.verticalScroll
24 | import androidx.compose.foundation.rememberScrollState
25 | import androidx.compose.foundation.layout.Arrangement
26 | import androidx.compose.foundation.layout.padding
27 | import androidx.compose.foundation.Image
28 | import coil3.compose.rememberAsyncImagePainter
29 | import androidx.compose.foundation.layout.width
30 | import androidx.compose.ui.draw.clip
31 | import androidx.compose.foundation.shape.RoundedCornerShape
32 | import androidx.compose.ui.layout.ContentScale
33 | import com.builtwithpaper.Text
34 | import androidx.compose.ui.unit.sp
35 | import androidx.compose.ui.text.font.FontWeight
36 | import com.builtwithpaper.LocalPrimaryColor
37 | import androidx.compose.ui.draw.alpha
38 | import androidx.compose.foundation.layout.Row
39 | import com.builtwithpaper.PrimaryButton
40 |
41 | @Composable
42 | fun Profile() {
43 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize().background(LocalSurfaceColor.current)) {
44 | CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColor.current) {
45 | TopAppBar(
46 | modifier = Modifier.height(56.dp).fillMaxWidth(),
47 | navigation = {
48 | GhostButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(8.dp)) {
49 | LucideIcon("arrow-left", contentDescription = "", tint = LocalContentColor.current)
50 | }
51 | },
52 | contentColor = LocalContentColor.current,
53 | backgroundColor = Color.Unspecified,
54 | contentPadding = PaddingValues(4.dp)
55 | )
56 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight().verticalScroll(rememberScrollState())) {
57 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().padding(top = 8.dp, end = 0.dp, bottom = 8.dp, start = 0.dp)) {
58 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "Kim's Photo", modifier = Modifier.width(120.dp).height(120.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
59 | Text("Kim", fontSize = 32.sp, fontWeight = FontWeight(500), color = LocalPrimaryColor.current, maxLines = 99)
60 | Text("+44 7519 654123", modifier = Modifier.alpha(0.66f).clip(RoundedCornerShape(4.dp)), fontSize = 16.sp, maxLines = 99)
61 | }
62 | Row {
63 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.weight(1f, fill = false).fillMaxWidth().padding(16.dp)) {
64 | PrimaryButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(8.dp)) {
65 | LucideIcon("send", contentDescription = "", tint = LocalContentColor.current)
66 | }
67 | Text("Message", fontSize = 16.sp, color = LocalPrimaryColor.current, maxLines = 99)
68 | }
69 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.weight(1f, fill = false).fillMaxWidth().padding(16.dp)) {
70 | PrimaryButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(8.dp)) {
71 | LucideIcon("phone", contentDescription = "", tint = LocalContentColor.current)
72 | }
73 | Text("Message", fontSize = 16.sp, color = LocalPrimaryColor.current, maxLines = 99)
74 | }
75 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.weight(1f, fill = false).fillMaxWidth().padding(16.dp)) {
76 | PrimaryButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(8.dp), borderWidth = 0.dp) {
77 | LucideIcon("video", contentDescription = "", tint = LocalContentColor.current)
78 | }
79 | Text("Video", fontSize = 16.sp, color = LocalPrimaryColor.current, maxLines = 99)
80 | }
81 | }
82 | Column(modifier = Modifier.fillMaxWidth()) {
83 | Text("More actions", modifier = Modifier.padding(16.dp), fontSize = 16.sp, fontWeight = FontWeight(500), color = LocalPrimaryColor.current, maxLines = 99)
84 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
85 | LucideIcon("file-video", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
86 | Text("View Media", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
87 | }
88 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
89 | LucideIcon("search", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
90 | Text("Search in conversation", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
91 | }
92 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
93 | LucideIcon("bell", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
94 | Text("Notifications", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
95 | }
96 | }
97 | }
98 | }
99 | }
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/Search.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.background
9 | import com.builtwithpaper.LocalBackgroundColor
10 | import com.builtwithpaper.LocalOnBackgroundColor
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import com.builtwithpaper.LocalContentColor
13 | import androidx.compose.foundation.layout.Arrangement
14 | import androidx.compose.ui.unit.dp
15 | import androidx.compose.foundation.layout.widthIn
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.foundation.layout.Row
18 | import androidx.compose.foundation.layout.height
19 | import androidx.compose.foundation.layout.fillMaxWidth
20 | import androidx.compose.ui.draw.clip
21 | import androidx.compose.foundation.shape.RoundedCornerShape
22 | import androidx.compose.ui.draw.shadow
23 | import androidx.compose.ui.zIndex
24 | import com.builtwithpaper.LocalSurfaceColor
25 | import com.builtwithpaper.LocalOnSurfaceColor
26 | import com.composables.icons.lucide.LucideIcon
27 | import com.builtwithpaper.TextField
28 | import androidx.compose.foundation.layout.PaddingValues
29 | import androidx.compose.ui.text.TextStyle
30 | import androidx.compose.ui.unit.sp
31 | import androidx.compose.foundation.horizontalScroll
32 | import androidx.compose.foundation.rememberScrollState
33 | import androidx.compose.foundation.layout.Box
34 | import androidx.compose.foundation.layout.width
35 | import com.builtwithpaper.Text
36 | import androidx.compose.foundation.Image
37 | import coil3.compose.rememberAsyncImagePainter
38 | import androidx.compose.ui.layout.ContentScale
39 | import com.builtwithpaper.LocalPrimaryColor
40 |
41 | @Composable
42 | fun Search() {
43 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize().background(LocalBackgroundColor.current)) {
44 | CompositionLocalProvider(LocalContentColor provides LocalOnBackgroundColor.current) {
45 | Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.widthIn(max = 600.dp).padding(16.dp)) {
46 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.height(56.dp).fillMaxWidth().clip(RoundedCornerShape(100.dp)).shadow(4.dp, RoundedCornerShape(100.dp)).zIndex(4f).background(LocalSurfaceColor.current).padding(top = 0.dp, end = 8.dp, bottom = 0.dp, start = 16.dp)) {
47 | CompositionLocalProvider(LocalContentColor provides LocalOnSurfaceColor.current) {
48 | LucideIcon("search", contentDescription = "", tint = LocalContentColor.current)
49 | TextField(value = "", onValueChange = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth(), maxLines = 1, placeholderText = "Search...", contentPadding = PaddingValues(start = 8.dp, top = 12.dp, end = 8.dp, bottom = 12.dp), textStyle = TextStyle.Default, fontSize = 16.sp)
50 | Row {
51 | }
52 | }
53 | }
54 | Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState())) {
55 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(16.dp)) {
56 | Box(contentAlignment = Alignment.Center, modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp))) {
57 | Text("K", fontSize = 16.sp, maxLines = 99)
58 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1544725176-7c40e5a71c5e?q=80&w=3534&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
59 | }
60 | Text("Jenny", fontSize = 16.sp, maxLines = 99)
61 | }
62 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(16.dp)) {
63 | Box(contentAlignment = Alignment.Center, modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp))) {
64 | Text("K", fontSize = 16.sp, maxLines = 99)
65 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1599566147214-ce487862ea4f?q=80&w=3347&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
66 | }
67 | Text("Peter", fontSize = 16.sp, maxLines = 99)
68 | }
69 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(16.dp)) {
70 | Box(contentAlignment = Alignment.Center, modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp))) {
71 | Text("K", fontSize = 16.sp, maxLines = 99)
72 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=256&q=80"), contentDescription = "", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
73 | }
74 | Text("Naomi", fontSize = 16.sp, maxLines = 99)
75 | }
76 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(16.dp)) {
77 | Box(contentAlignment = Alignment.Center, modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp))) {
78 | Text("K", fontSize = 16.sp, maxLines = 99)
79 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=3388&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
80 | }
81 | Text("Petra", fontSize = 16.sp, maxLines = 99)
82 | }
83 | }
84 | Column(modifier = Modifier.fillMaxWidth()) {
85 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
86 | LucideIcon("file-image", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
87 | Text("Photos", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
88 | }
89 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
90 | LucideIcon("file-video", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
91 | Text("Videos", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
92 | }
93 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
94 | LucideIcon("headphones", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
95 | Text("Music", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
96 | }
97 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
98 | LucideIcon("globe", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
99 | Text("Links", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
100 | }
101 | }
102 | }
103 | }
104 | }
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/example/Settings.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.background
9 | import com.builtwithpaper.LocalBackgroundColor
10 | import com.builtwithpaper.LocalOnBackgroundColor
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import com.builtwithpaper.LocalContentColor
13 | import com.builtwithpaper.TopAppBar
14 | import androidx.compose.foundation.layout.height
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.foundation.layout.fillMaxWidth
17 | import com.builtwithpaper.Text
18 | import androidx.compose.foundation.layout.padding
19 | import androidx.compose.ui.unit.sp
20 | import androidx.compose.ui.text.font.FontWeight
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.foundation.layout.PaddingValues
23 | import androidx.compose.foundation.layout.Arrangement
24 | import androidx.compose.foundation.layout.widthIn
25 | import androidx.compose.foundation.layout.fillMaxHeight
26 | import androidx.compose.foundation.verticalScroll
27 | import androidx.compose.foundation.rememberScrollState
28 | import androidx.compose.foundation.layout.Row
29 | import androidx.compose.ui.draw.clip
30 | import androidx.compose.foundation.shape.RoundedCornerShape
31 | import com.builtwithpaper.LocalCornerRadius
32 | import androidx.compose.foundation.Image
33 | import coil3.compose.rememberAsyncImagePainter
34 | import androidx.compose.foundation.layout.width
35 | import androidx.compose.ui.layout.ContentScale
36 | import androidx.compose.ui.draw.alpha
37 | import com.builtwithpaper.PrimaryButton
38 | import com.builtwithpaper.LocalPrimaryColor
39 | import com.composables.icons.lucide.LucideIcon
40 | import com.builtwithpaper.BottomNavigation
41 | import androidx.compose.foundation.layout.heightIn
42 | import androidx.compose.ui.draw.shadow
43 | import androidx.compose.ui.graphics.RectangleShape
44 | import androidx.compose.ui.zIndex
45 | import com.builtwithpaper.LocalSurfaceColor
46 | import com.builtwithpaper.currentScreenWidthBreakpoint
47 | import com.builtwithpaper.isAtLeast
48 | import com.builtwithpaper.ScreenWidthBreakpoint
49 | import com.builtwithpaper.LocalOnSurfaceColor
50 | import com.builtwithpaper.TabItem
51 |
52 | @Composable
53 | fun Settings() {
54 | val breakpoint = currentScreenWidthBreakpoint()
55 |
56 | Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize().background(LocalBackgroundColor.current)) {
57 | val breakpoint = currentScreenWidthBreakpoint()
58 |
59 | CompositionLocalProvider(LocalContentColor provides LocalOnBackgroundColor.current) {
60 | TopAppBar(
61 | modifier = Modifier.height(56.dp).fillMaxWidth(),
62 | title = {
63 | Text("Settings", modifier = Modifier.padding(top = 0.dp, end = 0.dp, bottom = 0.dp, start = 12.dp), fontSize = 24.sp, fontWeight = FontWeight(700), maxLines = 99)
64 | },
65 | contentColor = LocalContentColor.current,
66 | backgroundColor = Color.Unspecified,
67 | contentPadding = PaddingValues(start = 0.dp, top = 4.dp, end = 0.dp, bottom = 4.dp)
68 | )
69 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Bottom, modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight().padding(top = 16.dp, end = 16.dp, bottom = 0.dp, start = 16.dp)) {
70 | Column(modifier = Modifier.widthIn(max = 600.dp).fillMaxWidth().weight(weight = 1f, fill = false).fillMaxHeight().verticalScroll(rememberScrollState()).padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp)) {
71 | Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)).padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp)) {
72 | Image(rememberAsyncImagePainter("https://images.unsplash.com/photo-1499996860823-5214fcc65f8f?q=80&w=3466&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"), contentDescription = "", modifier = Modifier.width(58.dp).height(58.dp).clip(RoundedCornerShape(100.dp)), contentScale = ContentScale.Crop)
73 | Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.weight(1f, fill = false).fillMaxWidth()) {
74 | Text("Daniel", modifier = Modifier.fillMaxWidth(), fontSize = 16.sp, maxLines = 99)
75 | Text("+44 75 12 49384", modifier = Modifier.fillMaxWidth().alpha(0.6f), fontSize = 16.sp, maxLines = 99)
76 | }
77 | PrimaryButton(onClick = { /* TODO Handle this */ }, contentPadding = PaddingValues(start = 12.dp, top = 8.dp, end = 12.dp, bottom = 8.dp)) {
78 | Text("Edit", fontSize = 16.sp, maxLines = 99)
79 | }
80 | }
81 | Column(modifier = Modifier.fillMaxWidth()) {
82 | Text("General", modifier = Modifier.padding(16.dp), fontSize = 16.sp, fontWeight = FontWeight(500), color = LocalPrimaryColor.current, maxLines = 99)
83 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current))) {
84 | LucideIcon("file-video", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
85 | Text("Notifications", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
86 | }
87 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current))) {
88 | LucideIcon("moon", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
89 | Text("Appearance", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
90 | }
91 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current))) {
92 | LucideIcon("lock-keyhole", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
93 | Text("Privacy", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
94 | }
95 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current))) {
96 | LucideIcon("cloud-cog", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
97 | Text("Storage & Data", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
98 | }
99 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current))) {
100 | LucideIcon("info", contentDescription = "", tint = LocalPrimaryColor.current, modifier = Modifier.padding(16.dp))
101 | Text("About", modifier = Modifier.padding(top = 16.dp, end = 0.dp, bottom = 16.dp, start = 0.dp), fontSize = 16.sp, maxLines = 99)
102 | }
103 | }
104 | }
105 | }
106 | BottomNavigation(modifier = Modifier.heightIn(56.dp).fillMaxWidth().shadow(4.dp, RectangleShape).zIndex(4f).background(if (breakpoint isAtLeast ScreenWidthBreakpoint.Small) LocalSurfaceColor.current else Color.Unspecified), contentColor = LocalOnSurfaceColor.current, backgroundColor = LocalSurfaceColor.current) {
107 | TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
108 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
109 | LucideIcon("house", contentDescription = "", tint = LocalContentColor.current)
110 | Text("Chats", fontSize = 16.sp, maxLines = 99)
111 | }
112 | }
113 | TabItem(selected = false, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
114 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
115 | LucideIcon("contact", contentDescription = "", tint = LocalContentColor.current)
116 | Text("Contacts", fontSize = 16.sp, maxLines = 99)
117 | }
118 | }
119 | TabItem(selected = true, onSelected = { /* TODO Handle this */ }, modifier = Modifier.weight(1f, fill = false).fillMaxWidth().clip(RoundedCornerShape(LocalCornerRadius.current)), selectedColor = LocalPrimaryColor.current, contentColor = LocalContentColor.current) {
120 | Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), modifier = Modifier.fillMaxWidth()) {
121 | LucideIcon("settings", contentDescription = "", tint = LocalContentColor.current)
122 | Text("Settings", fontSize = 16.sp, maxLines = 99)
123 | }
124 | }
125 | }
126 | }
127 | }
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/com/builtwithpaper/ContainerSize.ios.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalComposeUiApi::class)
2 |
3 | package com.builtwithpaper
4 |
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.ExperimentalComposeUiApi
7 | import androidx.compose.ui.platform.LocalDensity
8 | import androidx.compose.ui.platform.LocalWindowInfo
9 | import androidx.compose.ui.unit.DpSize
10 | import androidx.compose.ui.unit.dp
11 |
12 | @Composable
13 | actual fun currentContainerSize(): DpSize {
14 | return with(LocalDensity.current) {
15 | val intSize = LocalWindowInfo.current.containerSize
16 | DpSize(intSize.width.dp, intSize.height.dp)
17 | }
18 | }
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/com/builtwithpaper/LightIndication.kt:
--------------------------------------------------------------------------------
1 | package com.builtwithpaper
2 |
3 | import androidx.compose.foundation.IndicationNodeFactory
4 | import androidx.compose.foundation.interaction.FocusInteraction
5 | import androidx.compose.foundation.interaction.HoverInteraction
6 | import androidx.compose.foundation.interaction.InteractionSource
7 | import androidx.compose.foundation.interaction.PressInteraction
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope
11 | import androidx.compose.ui.node.DelegatableNode
12 | import androidx.compose.ui.node.DrawModifierNode
13 | import androidx.compose.ui.node.invalidateDraw
14 | import kotlinx.coroutines.launch
15 |
16 | object LightIndication : IndicationNodeFactory {
17 |
18 | override fun create(interactionSource: InteractionSource): DelegatableNode =
19 | DefaultDebugIndicationInstance(interactionSource)
20 |
21 | override fun hashCode(): Int = -1
22 |
23 | override fun equals(other: Any?) = other === this
24 |
25 | private class DefaultDebugIndicationInstance(private val interactionSource: InteractionSource) : Modifier.Node(),
26 | DrawModifierNode {
27 | private var isPressed = false
28 | private var isHovered = false
29 | private var isFocused = false
30 | override fun onAttach() {
31 | coroutineScope.launch {
32 | var pressCount = 0
33 | var hoverCount = 0
34 | var focusCount = 0
35 | interactionSource.interactions.collect { interaction ->
36 | when (interaction) {
37 | is PressInteraction.Press -> pressCount++
38 | is PressInteraction.Release -> pressCount--
39 | is PressInteraction.Cancel -> pressCount--
40 | is HoverInteraction.Enter -> hoverCount++
41 | is HoverInteraction.Exit -> hoverCount--
42 | is FocusInteraction.Focus -> focusCount++
43 | is FocusInteraction.Unfocus -> focusCount--
44 | }
45 | val pressed = pressCount > 0
46 | val hovered = hoverCount > 0
47 | val focused = focusCount > 0
48 | var invalidateNeeded = false
49 | if (isPressed != pressed) {
50 | isPressed = pressed
51 | invalidateNeeded = true
52 | }
53 | if (isHovered != hovered) {
54 | isHovered = hovered
55 | invalidateNeeded = true
56 | }
57 | if (isFocused != focused) {
58 | isFocused = focused
59 | invalidateNeeded = true
60 | }
61 | if (invalidateNeeded) invalidateDraw()
62 | }
63 | }
64 | }
65 |
66 | override fun ContentDrawScope.draw() {
67 | drawContent()
68 | if (isPressed) {
69 | drawRect(color = Color.Companion.White.copy(alpha = 0.5f), size = size)
70 | } else if (isHovered || isFocused) {
71 | drawRect(color = Color.Companion.White.copy(alpha = 0.3f), size = size)
72 | }
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/com/example/MainViewController.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import androidx.compose.ui.window.ComposeUIViewController
4 |
5 | fun MainViewController() = ComposeUIViewController { App() }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
3 | #Gradle
4 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
5 |
6 |
7 | #Android
8 | android.nonTransitiveRClass=true
9 | android.useAndroidX=true
10 |
11 | #MPP
12 | kotlin.mpp.androidSourceSetLayoutVersion=2
13 | kotlin.mpp.enableCInteropCommonization=true
14 |
15 | #Development
16 | development=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | kotlin = "2.1.0"
3 | compose-multiplatform = "1.7.0"
4 | agp = "8.5.2"
5 | iconsLucide = "1.1.0"
6 | compose-unstyled = "1.20.0"
7 | materialRipple = "1.7.6"
8 | navigationCompose = "2.8.0-alpha10"
9 | addcoilmultiplatform = "3.0.0-rc01"
10 | android-compileSdk = "34"
11 | android-minSdk = "24"
12 | android-targetSdk = "34"
13 | androidx-activityCompose = "1.9.3"
14 | androidx-lifecycle = "2.8.3"
15 |
16 | [libraries]
17 | addcoilmultiplatform = { module = "com.alexstyl:addcoilmultiplatform", version.ref = "addcoilmultiplatform" }
18 | androidx-material-ripple = { module = "androidx.compose.material:material-ripple", version.ref = "materialRipple" }
19 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
20 | androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
21 | androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
22 | compose-unstyled = { group = "com.composables", name = "core", version.ref = "compose-unstyled" }
23 | icons-lucide = { module = "com.composables:icons-lucide", version.ref = "iconsLucide" }
24 | navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
25 |
26 | [plugins]
27 | androidApplication = { id = "com.android.application", version.ref = "agp" }
28 | composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
29 | composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
30 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------
/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 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/iosApp/Configuration/Config.xcconfig:
--------------------------------------------------------------------------------
1 | TEAM_ID=
2 | BUNDLE_ID=com.example
3 | APP_NAME=Bubbles
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
11 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
12 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
13 | 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
14 | /* End PBXBuildFile section */
15 |
16 | /* Begin PBXFileReference section */
17 | 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
18 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
19 | 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; };
20 | 7555FF7B242A565900829871 /* KotlinProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KotlinProject.app; sourceTree = BUILT_PRODUCTS_DIR; };
21 | 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
22 | 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23 | AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; };
24 | /* End PBXFileReference section */
25 |
26 | /* Begin PBXFrameworksBuildPhase section */
27 | B92378962B6B1156000C7307 /* Frameworks */ = {
28 | isa = PBXFrameworksBuildPhase;
29 | buildActionMask = 2147483647;
30 | files = (
31 | );
32 | runOnlyForDeploymentPostprocessing = 0;
33 | };
34 | /* End PBXFrameworksBuildPhase section */
35 |
36 | /* Begin PBXGroup section */
37 | 058557D7273AAEEB004C7B11 /* Preview Content */ = {
38 | isa = PBXGroup;
39 | children = (
40 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
41 | );
42 | path = "Preview Content";
43 | sourceTree = "";
44 | };
45 | 42799AB246E5F90AF97AA0EF /* Frameworks */ = {
46 | isa = PBXGroup;
47 | children = (
48 | );
49 | name = Frameworks;
50 | sourceTree = "";
51 | };
52 | 7555FF72242A565900829871 = {
53 | isa = PBXGroup;
54 | children = (
55 | AB1DB47929225F7C00F7AF9C /* Configuration */,
56 | 7555FF7D242A565900829871 /* iosApp */,
57 | 7555FF7C242A565900829871 /* Products */,
58 | 42799AB246E5F90AF97AA0EF /* Frameworks */,
59 | );
60 | sourceTree = "";
61 | };
62 | 7555FF7C242A565900829871 /* Products */ = {
63 | isa = PBXGroup;
64 | children = (
65 | 7555FF7B242A565900829871 /* KotlinProject.app */,
66 | );
67 | name = Products;
68 | sourceTree = "";
69 | };
70 | 7555FF7D242A565900829871 /* iosApp */ = {
71 | isa = PBXGroup;
72 | children = (
73 | 058557BA273AAA24004C7B11 /* Assets.xcassets */,
74 | 7555FF82242A565900829871 /* ContentView.swift */,
75 | 7555FF8C242A565B00829871 /* Info.plist */,
76 | 2152FB032600AC8F00CF470E /* iOSApp.swift */,
77 | 058557D7273AAEEB004C7B11 /* Preview Content */,
78 | );
79 | path = iosApp;
80 | sourceTree = "";
81 | };
82 | AB1DB47929225F7C00F7AF9C /* Configuration */ = {
83 | isa = PBXGroup;
84 | children = (
85 | AB3632DC29227652001CCB65 /* Config.xcconfig */,
86 | );
87 | path = Configuration;
88 | sourceTree = "";
89 | };
90 | /* End PBXGroup section */
91 |
92 | /* Begin PBXNativeTarget section */
93 | 7555FF7A242A565900829871 /* iosApp */ = {
94 | isa = PBXNativeTarget;
95 | buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
96 | buildPhases = (
97 | F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */,
98 | 7555FF77242A565900829871 /* Sources */,
99 | B92378962B6B1156000C7307 /* Frameworks */,
100 | 7555FF79242A565900829871 /* Resources */,
101 | );
102 | buildRules = (
103 | );
104 | dependencies = (
105 | );
106 | name = iosApp;
107 | packageProductDependencies = (
108 | );
109 | productName = iosApp;
110 | productReference = 7555FF7B242A565900829871 /* KotlinProject.app */;
111 | productType = "com.apple.product-type.application";
112 | };
113 | /* End PBXNativeTarget section */
114 |
115 | /* Begin PBXProject section */
116 | 7555FF73242A565900829871 /* Project object */ = {
117 | isa = PBXProject;
118 | attributes = {
119 | BuildIndependentTargetsInParallel = YES;
120 | LastSwiftUpdateCheck = 1130;
121 | LastUpgradeCheck = 1540;
122 | ORGANIZATIONNAME = orgName;
123 | TargetAttributes = {
124 | 7555FF7A242A565900829871 = {
125 | CreatedOnToolsVersion = 11.3.1;
126 | };
127 | };
128 | };
129 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
130 | compatibilityVersion = "Xcode 14.0";
131 | developmentRegion = en;
132 | hasScannedForEncodings = 0;
133 | knownRegions = (
134 | en,
135 | Base,
136 | );
137 | mainGroup = 7555FF72242A565900829871;
138 | packageReferences = (
139 | );
140 | productRefGroup = 7555FF7C242A565900829871 /* Products */;
141 | projectDirPath = "";
142 | projectRoot = "";
143 | targets = (
144 | 7555FF7A242A565900829871 /* iosApp */,
145 | );
146 | };
147 | /* End PBXProject section */
148 |
149 | /* Begin PBXResourcesBuildPhase section */
150 | 7555FF79242A565900829871 /* Resources */ = {
151 | isa = PBXResourcesBuildPhase;
152 | buildActionMask = 2147483647;
153 | files = (
154 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
155 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
156 | );
157 | runOnlyForDeploymentPostprocessing = 0;
158 | };
159 | /* End PBXResourcesBuildPhase section */
160 |
161 | /* Begin PBXShellScriptBuildPhase section */
162 | F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {
163 | isa = PBXShellScriptBuildPhase;
164 | buildActionMask = 2147483647;
165 | files = (
166 | );
167 | inputFileListPaths = (
168 | );
169 | inputPaths = (
170 | );
171 | name = "Compile Kotlin Framework";
172 | outputFileListPaths = (
173 | );
174 | outputPaths = (
175 | );
176 | runOnlyForDeploymentPostprocessing = 0;
177 | shellPath = /bin/sh;
178 | shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n";
179 | };
180 | /* End PBXShellScriptBuildPhase section */
181 |
182 | /* Begin PBXSourcesBuildPhase section */
183 | 7555FF77242A565900829871 /* Sources */ = {
184 | isa = PBXSourcesBuildPhase;
185 | buildActionMask = 2147483647;
186 | files = (
187 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
188 | 7555FF83242A565900829871 /* ContentView.swift in Sources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXSourcesBuildPhase section */
193 |
194 | /* Begin XCBuildConfiguration section */
195 | 7555FFA3242A565B00829871 /* Debug */ = {
196 | isa = XCBuildConfiguration;
197 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
198 | buildSettings = {
199 | ALWAYS_SEARCH_USER_PATHS = NO;
200 | CLANG_ANALYZER_NONNULL = YES;
201 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
202 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
203 | CLANG_CXX_LIBRARY = "libc++";
204 | CLANG_ENABLE_MODULES = YES;
205 | CLANG_ENABLE_OBJC_ARC = YES;
206 | CLANG_ENABLE_OBJC_WEAK = YES;
207 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
208 | CLANG_WARN_BOOL_CONVERSION = YES;
209 | CLANG_WARN_COMMA = YES;
210 | CLANG_WARN_CONSTANT_CONVERSION = YES;
211 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
212 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
213 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
214 | CLANG_WARN_EMPTY_BODY = YES;
215 | CLANG_WARN_ENUM_CONVERSION = YES;
216 | CLANG_WARN_INFINITE_RECURSION = YES;
217 | CLANG_WARN_INT_CONVERSION = YES;
218 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
219 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
220 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
221 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
222 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
223 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
224 | CLANG_WARN_STRICT_PROTOTYPES = YES;
225 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
226 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
227 | CLANG_WARN_UNREACHABLE_CODE = YES;
228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
229 | COPY_PHASE_STRIP = NO;
230 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
231 | ENABLE_STRICT_OBJC_MSGSEND = YES;
232 | ENABLE_TESTABILITY = YES;
233 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
234 | GCC_C_LANGUAGE_STANDARD = gnu11;
235 | GCC_DYNAMIC_NO_PIC = NO;
236 | GCC_NO_COMMON_BLOCKS = YES;
237 | GCC_OPTIMIZATION_LEVEL = 0;
238 | GCC_PREPROCESSOR_DEFINITIONS = (
239 | "DEBUG=1",
240 | "$(inherited)",
241 | );
242 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
243 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
244 | GCC_WARN_UNDECLARED_SELECTOR = YES;
245 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
246 | GCC_WARN_UNUSED_FUNCTION = YES;
247 | GCC_WARN_UNUSED_VARIABLE = YES;
248 | IPHONEOS_DEPLOYMENT_TARGET = 15.3;
249 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
250 | MTL_FAST_MATH = YES;
251 | ONLY_ACTIVE_ARCH = YES;
252 | SDKROOT = iphoneos;
253 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
254 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
255 | };
256 | name = Debug;
257 | };
258 | 7555FFA4242A565B00829871 /* Release */ = {
259 | isa = XCBuildConfiguration;
260 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
261 | buildSettings = {
262 | ALWAYS_SEARCH_USER_PATHS = NO;
263 | CLANG_ANALYZER_NONNULL = YES;
264 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
266 | CLANG_CXX_LIBRARY = "libc++";
267 | CLANG_ENABLE_MODULES = YES;
268 | CLANG_ENABLE_OBJC_ARC = YES;
269 | CLANG_ENABLE_OBJC_WEAK = YES;
270 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
271 | CLANG_WARN_BOOL_CONVERSION = YES;
272 | CLANG_WARN_COMMA = YES;
273 | CLANG_WARN_CONSTANT_CONVERSION = YES;
274 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
276 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
277 | CLANG_WARN_EMPTY_BODY = YES;
278 | CLANG_WARN_ENUM_CONVERSION = YES;
279 | CLANG_WARN_INFINITE_RECURSION = YES;
280 | CLANG_WARN_INT_CONVERSION = YES;
281 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
282 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
283 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
284 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
285 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
286 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
287 | CLANG_WARN_STRICT_PROTOTYPES = YES;
288 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
289 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
290 | CLANG_WARN_UNREACHABLE_CODE = YES;
291 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
292 | COPY_PHASE_STRIP = NO;
293 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
294 | ENABLE_NS_ASSERTIONS = NO;
295 | ENABLE_STRICT_OBJC_MSGSEND = YES;
296 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
297 | GCC_C_LANGUAGE_STANDARD = gnu11;
298 | GCC_NO_COMMON_BLOCKS = YES;
299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
301 | GCC_WARN_UNDECLARED_SELECTOR = YES;
302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
303 | GCC_WARN_UNUSED_FUNCTION = YES;
304 | GCC_WARN_UNUSED_VARIABLE = YES;
305 | IPHONEOS_DEPLOYMENT_TARGET = 15.3;
306 | MTL_ENABLE_DEBUG_INFO = NO;
307 | MTL_FAST_MATH = YES;
308 | SDKROOT = iphoneos;
309 | SWIFT_COMPILATION_MODE = wholemodule;
310 | SWIFT_OPTIMIZATION_LEVEL = "-O";
311 | VALIDATE_PRODUCT = YES;
312 | };
313 | name = Release;
314 | };
315 | 7555FFA6242A565B00829871 /* Debug */ = {
316 | isa = XCBuildConfiguration;
317 | buildSettings = {
318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
319 | CODE_SIGN_IDENTITY = "Apple Development";
320 | CODE_SIGN_STYLE = Automatic;
321 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
322 | DEVELOPMENT_TEAM = "${TEAM_ID}";
323 | ENABLE_PREVIEWS = YES;
324 | FRAMEWORK_SEARCH_PATHS = (
325 | "$(inherited)",
326 | "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
327 | );
328 | INFOPLIST_FILE = iosApp/Info.plist;
329 | IPHONEOS_DEPLOYMENT_TARGET = 15.3;
330 | LD_RUNPATH_SEARCH_PATHS = (
331 | "$(inherited)",
332 | "@executable_path/Frameworks",
333 | );
334 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
335 | PRODUCT_NAME = "${APP_NAME}";
336 | PROVISIONING_PROFILE_SPECIFIER = "";
337 | SWIFT_VERSION = 5.0;
338 | TARGETED_DEVICE_FAMILY = "1,2";
339 | };
340 | name = Debug;
341 | };
342 | 7555FFA7242A565B00829871 /* Release */ = {
343 | isa = XCBuildConfiguration;
344 | buildSettings = {
345 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
346 | CODE_SIGN_IDENTITY = "Apple Development";
347 | CODE_SIGN_STYLE = Automatic;
348 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
349 | DEVELOPMENT_TEAM = "${TEAM_ID}";
350 | ENABLE_PREVIEWS = YES;
351 | FRAMEWORK_SEARCH_PATHS = (
352 | "$(inherited)",
353 | "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
354 | );
355 | INFOPLIST_FILE = iosApp/Info.plist;
356 | IPHONEOS_DEPLOYMENT_TARGET = 15.3;
357 | LD_RUNPATH_SEARCH_PATHS = (
358 | "$(inherited)",
359 | "@executable_path/Frameworks",
360 | );
361 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
362 | PRODUCT_NAME = "${APP_NAME}";
363 | PROVISIONING_PROFILE_SPECIFIER = "";
364 | SWIFT_VERSION = 5.0;
365 | TARGETED_DEVICE_FAMILY = "1,2";
366 | };
367 | name = Release;
368 | };
369 | /* End XCBuildConfiguration section */
370 |
371 | /* Begin XCConfigurationList section */
372 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
373 | isa = XCConfigurationList;
374 | buildConfigurations = (
375 | 7555FFA3242A565B00829871 /* Debug */,
376 | 7555FFA4242A565B00829871 /* Release */,
377 | );
378 | defaultConfigurationIsVisible = 0;
379 | defaultConfigurationName = Release;
380 | };
381 | 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
382 | isa = XCConfigurationList;
383 | buildConfigurations = (
384 | 7555FFA6242A565B00829871 /* Debug */,
385 | 7555FFA7242A565B00829871 /* Release */,
386 | );
387 | defaultConfigurationIsVisible = 0;
388 | defaultConfigurationName = Release;
389 | };
390 | /* End XCConfigurationList section */
391 | };
392 | rootObject = 7555FF73242A565900829871 /* Project object */;
393 | }
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "app-icon-1024.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/composablehorizons/bubbles/3ddce98d80963af3484423cf9a23c38760ec84b8/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import ComposeApp
4 |
5 | struct ComposeView: UIViewControllerRepresentable {
6 | func makeUIViewController(context: Context) -> UIViewController {
7 | MainViewControllerKt.MainViewController()
8 | }
9 |
10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
11 | }
12 |
13 | struct ContentView: View {
14 | var body: some View {
15 | ComposeView()
16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
17 | }
18 | }
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @main
4 | struct iOSApp: App {
5 | var body: some Scene {
6 | WindowGroup {
7 | ContentView()
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "Bubbles"
2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
3 |
4 | pluginManagement {
5 | repositories {
6 | google {
7 | mavenContent {
8 | includeGroupAndSubgroups("androidx")
9 | includeGroupAndSubgroups("com.android")
10 | includeGroupAndSubgroups("com.google")
11 | }
12 | }
13 | mavenCentral()
14 | gradlePluginPortal()
15 | }
16 | }
17 |
18 | dependencyResolutionManagement {
19 | repositories {
20 | google {
21 | mavenContent {
22 | includeGroupAndSubgroups("androidx")
23 | includeGroupAndSubgroups("com.android")
24 | includeGroupAndSubgroups("com.google")
25 | }
26 | }
27 | mavenCentral()
28 | }
29 | }
30 |
31 | include(":composeApp")
--------------------------------------------------------------------------------