├── .gitignore
├── .idea
├── .gitignore
├── .name
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── ru
│ │ │ └── ozh
│ │ │ └── compose
│ │ │ └── challenges
│ │ │ ├── MainActivity.kt
│ │ │ └── ui
│ │ │ ├── Screen.kt
│ │ │ ├── clock
│ │ │ ├── Clock.kt
│ │ │ ├── Dots.kt
│ │ │ ├── Preview.kt
│ │ │ └── Time.kt
│ │ │ ├── grid
│ │ │ ├── Cell.kt
│ │ │ ├── Elements.kt
│ │ │ ├── Icons.kt
│ │ │ ├── WatchGridConfig.kt
│ │ │ ├── WatchGridLayout.kt
│ │ │ ├── WatchGridState.kt
│ │ │ └── WatchGridStateImpl.kt
│ │ │ ├── ktx
│ │ │ ├── ModifierKtx.kt
│ │ │ └── OffsetState.kt
│ │ │ ├── magnet
│ │ │ └── MagnetButtun.kt
│ │ │ ├── switch
│ │ │ ├── DayAndNightSwitch.kt
│ │ │ ├── MoonIcon.kt
│ │ │ ├── SunIcon.kt
│ │ │ ├── SwitchConsts.kt
│ │ │ └── SwitchMoon.kt
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_app_store.xml
│ │ ├── ic_apple_music.xml
│ │ ├── ic_apple_tv.xml
│ │ ├── ic_books.xml
│ │ ├── ic_calculator.xml
│ │ ├── ic_calendar.xml
│ │ ├── ic_camera.xml
│ │ ├── ic_clock.xml
│ │ ├── ic_contacts.xml
│ │ ├── ic_files.xml
│ │ ├── ic_find_my.xml
│ │ ├── ic_health.xml
│ │ ├── ic_home.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_mail.xml
│ │ ├── ic_maps.xml
│ │ ├── ic_messages.xml
│ │ ├── ic_notes.xml
│ │ ├── ic_photos.xml
│ │ ├── ic_podcasts.xml
│ │ ├── ic_safari.xml
│ │ ├── ic_settings.xml
│ │ ├── ic_stocks.xml
│ │ ├── ic_translate.xml
│ │ ├── ic_wallet.xml
│ │ └── ic_weather.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-v23
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── ru
│ └── ozh
│ └── compose
│ └── challenges
│ └── ExampleUnitTest.kt
├── art
├── apple_grid_art.gif
├── clock.gif
└── day_and_night_switch.gif
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── stability_config.conf
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Compose-Challenges
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Compose-Challenges
2 |
3 | Challenge | Art |
4 | ------------ |-----------------------------------|
5 | [Apple watch grid](/app/src/main/java/ru/ozh/compose/challenges/ui/grid/) |  |
6 | [Day&Night Switch](/app/src/main/java/ru/ozh/compose/challenges/ui/switch/) |  |
7 | [Clock](/app/src/main/java/ru/ozh/compose/challenges/ui/clock/) |  |
8 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | }
6 |
7 | android {
8 | namespace = "ru.ozh.compose.challenges"
9 | compileSdk = 35
10 |
11 | defaultConfig {
12 | applicationId = "ru.ozh.compose.challenges"
13 | minSdk = 22
14 | targetSdk = 35
15 | versionCode = 1
16 | versionName = "1.0"
17 |
18 | vectorDrawables {
19 | useSupportLibrary = true
20 | }
21 | }
22 |
23 | buildTypes {
24 | debug {
25 | isMinifyEnabled = false
26 | //proguardFiles getDefaultProguardFile ("proguard-android-optimize.txt"), "proguard-rules.pro"
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility = JavaVersion.VERSION_17
31 | targetCompatibility = JavaVersion.VERSION_17
32 | }
33 | kotlinOptions {
34 | jvmTarget = "17"
35 | }
36 | buildFeatures {
37 | compose = true
38 | }
39 | }
40 |
41 | composeCompiler {
42 | reportsDestination = layout.buildDirectory.dir("compose_compiler")
43 | metricsDestination = layout.buildDirectory.dir("compose_compiler")
44 | stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
45 | }
46 |
47 | dependencies {
48 |
49 | implementation (libs.androidx.material3)
50 | implementation (libs.androidx.material3.windows.size)
51 |
52 | implementation (libs.androidx.core.ktx)
53 |
54 | implementation(platform(libs.androidx.compose.bom))
55 | implementation (libs.androidx.ui)
56 | implementation (libs.androidx.ui.tooling.preview)
57 |
58 | implementation (libs.androidx.lifecycle.runtime.ktx)
59 | implementation (libs.androidx.activity.compose)
60 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
20 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.scale
13 | import kotlinx.coroutines.delay
14 | import ru.ozh.compose.challenges.ui.GridScreen
15 | import ru.ozh.compose.challenges.ui.clock.WaveClock
16 | import ru.ozh.compose.challenges.ui.clock.currentTime
17 | import ru.ozh.compose.challenges.ui.magnet.MagnetButtonScreen
18 | import ru.ozh.compose.challenges.ui.switch.SwitchDaN
19 | import ru.ozh.compose.challenges.ui.theme.ComposeChallengesTheme
20 |
21 | class MainActivity : ComponentActivity() {
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContent {
25 | ComposeChallengesTheme {
26 | // GridScreen()
27 | // SwitchScreen()
28 | // WaveClockScreen()
29 | MagnetButtonScreen()
30 | }
31 | }
32 | }
33 | }
34 |
35 |
36 | @Composable
37 | fun SwitchScreen() {
38 | Box(
39 | modifier = Modifier.fillMaxSize(),
40 | contentAlignment = Alignment.Center
41 | ) {
42 | var checked by remember {
43 | mutableStateOf(true)
44 | }
45 | SwitchDaN(
46 | checked = checked,
47 | onCheckedChange = {
48 | checked = it
49 | }
50 | )
51 | }
52 | }
53 |
54 | @Composable
55 | fun WaveClockScreen() {
56 | var time by remember { mutableStateOf(currentTime()) }
57 | LaunchedEffect(Unit) {
58 | while (true) {
59 | time = currentTime()
60 | delay(1000)
61 | Log.d("WaveClockScreen", "$time ${time.hashCode()}")
62 | }
63 | }
64 |
65 | Box(
66 | modifier = Modifier.fillMaxSize().scale(2.5f),
67 | contentAlignment = Alignment.Center
68 | ) {
69 | WaveClock(time)
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/Screen.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.compose.foundation.BorderStroke
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.clickable
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.size
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
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.platform.LocalContext
19 | import androidx.compose.ui.unit.dp
20 | import ru.ozh.compose.challenges.ui.grid.IconRounded
21 | import ru.ozh.compose.challenges.ui.grid.Icons
22 | import ru.ozh.compose.challenges.ui.grid.WatchGridLayout
23 | import ru.ozh.compose.challenges.ui.theme.ComposeChallengesTheme
24 |
25 | @Composable
26 | fun GridScreen() {
27 |
28 | var toast: Toast? = null
29 | val context: Context = LocalContext.current
30 |
31 | ComposeChallengesTheme {
32 | Box(
33 | modifier = Modifier
34 | .fillMaxSize(),
35 | contentAlignment = Alignment.Center
36 | ) {
37 | WatchGridLayout(
38 | modifier = Modifier
39 | .size(300.dp)
40 | .background(
41 | color = Color.Black,
42 | shape = RoundedCornerShape(size = 64.dp)
43 | )
44 | .border(
45 | border = BorderStroke(width = 12.dp, color = Color.LightGray),
46 | shape = RoundedCornerShape(size = 64.dp)
47 | )
48 | .clip(
49 | shape = RoundedCornerShape(size = 64.dp)
50 | ),
51 | rowItemsCount = 5,
52 | itemSize = 80.dp
53 | ) {
54 | Icons.appleIcons.forEach { (res, name) ->
55 | IconRounded(res = res,
56 | modifier = Modifier.clickable {
57 | toast?.cancel()
58 | toast = Toast.makeText(
59 | context,
60 | "You've clicked on $name",
61 | Toast.LENGTH_SHORT
62 | )
63 |
64 | toast?.show()
65 | }
66 | )
67 | }
68 | }
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/clock/Clock.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalAnimationApi::class)
2 |
3 | package ru.ozh.compose.challenges.ui.clock
4 |
5 | import androidx.compose.animation.*
6 | import androidx.compose.animation.core.*
7 | import androidx.compose.foundation.background
8 | import androidx.compose.foundation.layout.*
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.*
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.draw.clip
15 | import androidx.compose.ui.draw.shadow
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.unit.Dp
18 | import androidx.compose.ui.unit.TextUnit
19 | import androidx.compose.ui.unit.dp
20 | import androidx.compose.ui.unit.sp
21 | import kotlinx.coroutines.delay
22 | import kotlinx.coroutines.launch
23 |
24 | @Composable
25 | fun WaveClock(
26 | time: Time,
27 | plateSize: Dp = 40.dp,
28 | plateColor: Color = Color.White,
29 | plateElevation: Dp = 8.dp,
30 | plateRoundCorner: Dp = 3.dp,
31 | digitSize: TextUnit = 20.sp,
32 | digitColor: Color = Color.Black.copy(alpha = 0.6f)
33 | ) {
34 | Row(
35 | modifier = Modifier.wrapContentSize(),
36 | horizontalArrangement = Arrangement.Center,
37 | verticalAlignment = Alignment.CenterVertically
38 | ) {
39 | DigitPlate(
40 | value = time.hours,
41 | plateSize = plateSize,
42 | backgroundColor = plateColor,
43 | elevation = plateElevation,
44 | roundCorner = plateRoundCorner,
45 | textColor = digitColor,
46 | textSize = digitSize,
47 | animationDelay = 100
48 | )
49 | DotsColumn(digitColor)
50 | DigitPlate(
51 | value = time.minutes,
52 | plateSize = plateSize,
53 | backgroundColor = plateColor,
54 | elevation = plateElevation,
55 | roundCorner = plateRoundCorner,
56 | textColor = digitColor,
57 | textSize = digitSize,
58 | animationDelay = 50
59 | )
60 | DotsColumn(digitColor)
61 | DigitPlate(
62 | value = time.seconds,
63 | plateSize = plateSize,
64 | backgroundColor = plateColor,
65 | elevation = plateElevation,
66 | roundCorner = plateRoundCorner,
67 | textColor = digitColor,
68 | textSize = digitSize
69 | )
70 | }
71 | }
72 |
73 | @Composable
74 | fun DigitPlate(
75 | value: Int,
76 | plateSize: Dp,
77 | roundCorner: Dp,
78 | backgroundColor: Color,
79 | textColor: Color,
80 | textSize: TextUnit,
81 | elevation: Dp = 0.dp,
82 | animationDelay: Long = 0,
83 | ) {
84 | val backGroundShape = RoundedCornerShape(size = roundCorner)
85 | WiggleBox(
86 | modifier = Modifier
87 | .shadow(elevation = elevation, shape = backGroundShape)
88 | .size(plateSize)
89 | .clip(shape = backGroundShape)
90 | .background(color = backgroundColor),
91 | key = value,
92 | shakeOffset = plateSize / 10,
93 | shakeDuration = 300,
94 | shakeDelay = animationDelay
95 | ) {
96 | DropDigitText(
97 | value = value,
98 | textColor = textColor,
99 | textSize = textSize,
100 | animationDelay = animationDelay
101 | )
102 | }
103 | }
104 |
105 | @Composable
106 | fun DropDigitText(
107 | value: Int,
108 | textColor: Color,
109 | textSize: TextUnit,
110 | animationDelay: Long
111 | ) {
112 | SlideContentAnimation(targetState = value, delay = animationDelay) { targetCount ->
113 | Text(
114 | text = "%02d".format(targetCount),
115 | fontSize = textSize,
116 | color = textColor,
117 | )
118 | }
119 | }
120 |
121 | @Composable
122 | fun WiggleBox(
123 | modifier: Modifier,
124 | key: Any,
125 | shakeOffset: Dp = 0.dp,
126 | shakeDuration: Int = 0,
127 | shakeDelay: Long = 0,
128 | content: @Composable BoxScope.() -> Unit
129 | ) {
130 | var ignoreFirstComposition by remember { mutableStateOf(true) }
131 | var targetOffset by remember { mutableStateOf(0.dp) }
132 |
133 | val animateOffset by animateDpAsState(
134 | targetValue = targetOffset,
135 | animationSpec = repeatable(
136 | iterations = 1,
137 | animation = tween(
138 | durationMillis = shakeDuration / 2,
139 | delayMillis = shakeDelay.toInt(),
140 | easing = FastOutSlowInEasing
141 | ),
142 | repeatMode = RepeatMode.Reverse
143 | ),
144 | finishedListener = {
145 | targetOffset = 0.dp
146 | }
147 | )
148 |
149 | LaunchedEffect(key) {
150 | if (!ignoreFirstComposition) {
151 | targetOffset = shakeOffset
152 | }
153 | ignoreFirstComposition = false
154 | }
155 |
156 | Box(
157 | contentAlignment = Alignment.Center,
158 | modifier = Modifier
159 | .offset(y = animateOffset)
160 | .then(modifier),
161 | content = content
162 | )
163 | }
164 |
165 | @Composable
166 | fun SlideContentAnimation(
167 | targetState: Int,
168 | delay: Long = 0,
169 | content: @Composable AnimatedVisibilityScope.(targetState: Int) -> Unit,
170 | ) {
171 | val scope = rememberCoroutineScope()
172 |
173 | AnimatedContent(
174 | targetState = targetState,
175 | transitionSpec = {
176 | scope.launch { delay(delay) }
177 | // Compare the incoming number with the previous number.
178 | if (targetState > initialState) {
179 | // If the target number is larger, it slides up and fades in
180 | // while the initial (smaller) number slides up and fades out.
181 | slideInVertically { height -> -height } with
182 | slideOutVertically { height -> height }
183 | } else {
184 | // If the target number is smaller, it slides down and fades in
185 | // while the initial number slides down and fades out.
186 | slideInVertically { height -> -height } with
187 | slideOutVertically { height -> height }
188 | }.using(
189 | // Disable clipping since the faded slide-in/out should
190 | // be displayed out of bounds.
191 | SizeTransform(clip = false)
192 | )
193 | },
194 | content = content
195 | )
196 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/clock/Dots.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.clock
2 |
3 | import androidx.compose.animation.core.*
4 | import androidx.compose.foundation.Canvas
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.runtime.*
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.draw.alpha
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.unit.dp
12 |
13 | @Composable
14 | fun Dot(
15 | color: Color
16 | ) {
17 | Canvas(modifier = Modifier.size(2.dp)) {
18 | drawCircle(
19 | color = color,
20 | )
21 | }
22 | }
23 |
24 | @Composable
25 | fun DotsColumn(
26 | dotColor: Color
27 | ) {
28 |
29 | val infiniteTransition = rememberInfiniteTransition()
30 |
31 | val alpha by infiniteTransition.animateFloat(
32 | initialValue = 1.0f,
33 | targetValue = 0.3f,
34 | animationSpec = infiniteRepeatable(
35 | animation = tween(durationMillis = 450),
36 | repeatMode = RepeatMode.Reverse
37 | )
38 | )
39 |
40 | Column(
41 | modifier = Modifier
42 | .width(8.dp)
43 | .alpha(alpha),
44 | verticalArrangement = Arrangement.Center,
45 | horizontalAlignment = Alignment.CenterHorizontally
46 | ) {
47 | Dot(dotColor)
48 | Spacer(modifier = Modifier.height(4.dp))
49 | Dot(dotColor)
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/clock/Preview.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.clock
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.runtime.*
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.tooling.preview.Preview
10 | import kotlinx.coroutines.delay
11 |
12 | @Preview
13 | @Composable
14 | fun WaveClockPreview() {
15 | var time by remember { mutableStateOf(currentTime()) }
16 | LaunchedEffect(Unit) {
17 | while (true) {
18 | time = currentTime()
19 | delay(1000)
20 | }
21 | }
22 |
23 | Box(
24 | modifier = Modifier.fillMaxSize(),
25 | contentAlignment = Alignment.Center
26 | ) {
27 | WaveClock(time)
28 | }
29 | }
30 |
31 | @Preview
32 | @Composable
33 | fun DotsColumnPreview() {
34 | DotsColumn(Color.Black)
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/clock/Time.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.clock
2 |
3 | import java.util.*
4 |
5 | data class Time(
6 | val hours: Int,
7 | val minutes: Int,
8 | val seconds: Int
9 | )
10 |
11 | fun currentTime(): Time {
12 | val calendar = Calendar.getInstance()
13 | return Time(
14 | hours = calendar.get(Calendar.HOUR_OF_DAY),
15 | minutes = calendar.get(Calendar.MINUTE),
16 | seconds = calendar.get(Calendar.SECOND),
17 | )
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/grid/Cell.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.grid
2 |
3 | data class Cell(
4 | val x: Int,
5 | val y: Int,
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/grid/Elements.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.grid
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.Canvas
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.shape.CircleShape
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.clip
13 | import androidx.compose.ui.geometry.Offset
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.res.painterResource
16 |
17 | @Composable
18 | fun IconRounded(
19 | modifier: Modifier = Modifier,
20 | @DrawableRes res: Int
21 | ) {
22 | Box(
23 | modifier = Modifier.clip(CircleShape).then(modifier),
24 | contentAlignment = Alignment.Center
25 | ) {
26 | Image(
27 | modifier = Modifier.fillMaxSize(),
28 | painter = painterResource(id = res),
29 | contentDescription = ""
30 | )
31 | }
32 | }
33 |
34 | @Composable
35 | fun CrossLine(
36 | color: Color = Color.White
37 | ) {
38 | Canvas(
39 | modifier = Modifier
40 | .fillMaxSize(),
41 | onDraw = {
42 | drawLine(
43 | start = Offset(this.size.width / 2, 0f),
44 | end = Offset(this.size.width / 2, this.size.height),
45 | color = color,
46 | strokeWidth = 4f
47 | )
48 |
49 | drawLine(
50 | start = Offset(0f, this.size.height / 2),
51 | end = Offset(this.size.width, this.size.height / 2),
52 | color = color,
53 | strokeWidth = 4f
54 | )
55 | }
56 | )
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/grid/Icons.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.grid
2 |
3 | import ru.ozh.compose.challenges.R
4 |
5 |
6 | object Icons {
7 | val appleIcons = listOf(
8 | R.drawable.ic_app_store to "App store",
9 | R.drawable.ic_apple_music to "Apple music",
10 | R.drawable.ic_apple_tv to "Apple TV",
11 | R.drawable.ic_calculator to "Calculator",
12 | R.drawable.ic_books to "Books",
13 | R.drawable.ic_calendar to "Calendar",
14 | R.drawable.ic_camera to "Camera",
15 | R.drawable.ic_clock to "Clock",
16 | R.drawable.ic_contacts to "Contacts",
17 | R.drawable.ic_settings to "Settings",
18 | R.drawable.ic_files to "Files",
19 | R.drawable.ic_find_my to "Find_my",
20 | R.drawable.ic_health to "Health",
21 | R.drawable.ic_home to "Home",
22 | R.drawable.ic_mail to "Mail",
23 | R.drawable.ic_maps to "Maps",
24 | R.drawable.ic_messages to "Messages",
25 | R.drawable.ic_notes to "Notes",
26 | R.drawable.ic_photos to "Photos",
27 | R.drawable.ic_podcasts to "Podcasts",
28 | R.drawable.ic_safari to "Safari",
29 | R.drawable.ic_stocks to "Stocks",
30 | R.drawable.ic_translate to "Translate",
31 | R.drawable.ic_wallet to "Wallet",
32 | R.drawable.ic_weather to "Weather",
33 | )
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/grid/WatchGridConfig.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.grid
2 |
3 | import androidx.compose.ui.unit.IntOffset
4 |
5 | class WatchGridConfig(
6 | val itemSizePx: Int = 0,
7 | val layoutHeightPx: Int = 0,
8 | val layoutWidthPx: Int = 0,
9 | val cells: List = emptyList()
10 | ) {
11 |
12 | val a = 3f * layoutWidthPx
13 | val b = 3f * layoutHeightPx
14 | val c = 20.0f
15 |
16 | val layoutCenter = IntOffset(
17 | x = layoutWidthPx / 2,
18 | y = layoutHeightPx / 2
19 | )
20 |
21 | val halfItemSizePx = itemSizePx / 2
22 |
23 | val contentHeight = ((cells.maxByOrNull { it.y }?.y?.let { y -> y + 1 }) ?: 0).times(itemSizePx)
24 | val contentWidth = ((cells.maxByOrNull { it.x }?.x?.let { x -> x + 1 }) ?: 0).times(itemSizePx)
25 |
26 | val maxOffsetHorizontal = contentWidth - layoutWidthPx
27 | val maxOffsetVertical = contentHeight - layoutHeightPx
28 |
29 | val overScrollDragDistanceHorizontal = layoutWidthPx - itemSizePx
30 | val overScrollDragDistanceVertical = layoutHeightPx - itemSizePx
31 |
32 | val overScrollDistanceHorizontal = layoutWidthPx / 2 - halfItemSizePx
33 | val overScrollDistanceVertical = layoutHeightPx / 2 - halfItemSizePx
34 |
35 | val overScrollDragRangeVertical =
36 | (-maxOffsetVertical.toFloat() - overScrollDragDistanceVertical)
37 | .rangeTo(overScrollDragDistanceVertical.toFloat())
38 | val overScrollDragRangeHorizontal =
39 | (-maxOffsetHorizontal.toFloat() - overScrollDragDistanceHorizontal)
40 | .rangeTo(overScrollDragDistanceHorizontal.toFloat())
41 |
42 | val overScrollRangeVertical =
43 | (-maxOffsetVertical.toFloat() - overScrollDistanceVertical)
44 | .rangeTo(overScrollDistanceVertical.toFloat())
45 | val overScrollRangeHorizontal =
46 | (-maxOffsetHorizontal.toFloat() - overScrollDistanceHorizontal)
47 | .rangeTo(overScrollDistanceHorizontal.toFloat())
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/grid/WatchGridLayout.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.grid
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.draw.clipToBounds
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.layout.Layout
10 | import androidx.compose.ui.platform.LocalDensity
11 | import androidx.compose.ui.tooling.preview.Preview
12 | import androidx.compose.ui.unit.Constraints
13 | import androidx.compose.ui.unit.Dp
14 | import androidx.compose.ui.unit.dp
15 | import ru.ozh.compose.challenges.ui.grid.Icons.appleIcons
16 | import ru.ozh.compose.challenges.ui.ktx.drag
17 | import ru.ozh.compose.challenges.ui.theme.ComposeChallengesTheme
18 |
19 | @Composable
20 | fun WatchGridLayout(
21 | modifier: Modifier = Modifier,
22 | rowItemsCount: Int,
23 | itemSize: Dp,
24 | state: WatchGridState = rememberWatchGridState(),
25 | content: @Composable () -> Unit,
26 | ) {
27 |
28 | check(rowItemsCount > 0) { "rowItemsCount must be positive" }
29 | check(itemSize > 0.dp) { "itemSize must be positive" }
30 |
31 | val itemSizePx = with(LocalDensity.current) { itemSize.roundToPx() }
32 | val itemConstraints = Constraints.fixed(width = itemSizePx, height = itemSizePx)
33 |
34 | Layout(
35 | modifier = modifier
36 | .drag(state)
37 | .clipToBounds(),
38 | content = content
39 | ) { measurables, layoutConstraints ->
40 |
41 | val placeables = measurables.map { measurable -> measurable.measure(itemConstraints) }
42 | val cells = List(placeables.size) { index ->
43 | val x = index % rowItemsCount
44 | val y = (index - x) / rowItemsCount
45 | Cell(x, y)
46 | }
47 |
48 | state.config = WatchGridConfig(
49 | layoutWidthPx = layoutConstraints.maxWidth,
50 | layoutHeightPx = layoutConstraints.maxHeight,
51 | itemSizePx = itemSizePx,
52 | cells = cells
53 | )
54 |
55 | layout(layoutConstraints.maxWidth, layoutConstraints.maxHeight) {
56 | placeables.forEachIndexed { index, placeable ->
57 | val position = state.getPositionFor(index)
58 | val scale = state.getScaleFor(position)
59 | placeable.placeWithLayer(
60 | position = position,
61 | layerBlock = {
62 | this.scaleX = scale
63 | this.scaleY = scale
64 | }
65 | )
66 | }
67 | }
68 | }
69 | }
70 |
71 | @Preview(showBackground = true)
72 | @Composable
73 | fun WatchGridLayoutPreview() {
74 | ComposeChallengesTheme {
75 | WatchGridLayout(
76 | modifier = Modifier
77 | .size(300.dp)
78 | .background(Color.Black),
79 | rowItemsCount = 5,
80 | itemSize = 80.dp
81 | ) {
82 | appleIcons.forEach { (res, name) ->
83 | IconRounded(
84 | res = res
85 | )
86 | }
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/grid/WatchGridState.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.grid
2 |
3 | import androidx.compose.ui.unit.IntOffset
4 | import ru.ozh.compose.challenges.ui.ktx.OffsetState
5 |
6 | interface WatchGridState : OffsetState {
7 |
8 | var config: WatchGridConfig
9 |
10 | fun getPositionFor(index: Int): IntOffset
11 | fun getScaleFor(position: IntOffset): Float
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/grid/WatchGridStateImpl.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.grid
2 |
3 | import androidx.compose.animation.core.*
4 | import androidx.compose.runtime.*
5 | import androidx.compose.runtime.saveable.Saver
6 | import androidx.compose.runtime.saveable.rememberSaveable
7 | import androidx.compose.ui.geometry.Offset
8 | import androidx.compose.ui.unit.IntOffset
9 |
10 | data class WatchGridStateImpl(
11 | private val initialOffset: Offset = Offset.Zero
12 | ) : WatchGridState {
13 |
14 | companion object {
15 | val Saver = Saver>(
16 | save = {
17 | val (x, y) = it.currentOffset
18 | listOf(x, y)
19 | },
20 | restore = {
21 | WatchGridStateImpl(initialOffset = Offset(it[0], it[1]))
22 | }
23 | )
24 |
25 | private const val CELL_MIN_SCALE = 0.5f
26 | private const val CELL_MAX_SCALE = 1.0f
27 | }
28 |
29 | private val decayAnimationSpec = SpringSpec(
30 | dampingRatio = Spring.DampingRatioLowBouncy,
31 | stiffness = Spring.StiffnessLow,
32 | )
33 |
34 | override val animatable = Animatable(
35 | initialValue = initialOffset,
36 | typeConverter = Offset.VectorConverter
37 | )
38 |
39 | override var config: WatchGridConfig = WatchGridConfig()
40 |
41 | override suspend fun snapTo(offset: Offset) {
42 | val x = offset.x.coerceIn(config.overScrollDragRangeHorizontal)
43 | val y = offset.y.coerceIn(config.overScrollDragRangeVertical)
44 | animatable.snapTo(Offset(x, y))
45 | }
46 |
47 | override suspend fun animateTo(offset: Offset, velocity: Offset) {
48 | val x = offset.x.coerceIn(config.overScrollRangeHorizontal)
49 | val y = offset.y.coerceIn(config.overScrollRangeVertical)
50 | animatable.animateTo(
51 | initialVelocity = velocity,
52 | animationSpec = decayAnimationSpec,
53 | targetValue = Offset(x, y)
54 | )
55 | }
56 |
57 | override suspend fun stop() {
58 | animatable.stop()
59 | }
60 |
61 | override fun getPositionFor(index: Int): IntOffset {
62 | val (offsetX, offsetY) = currentOffset
63 | val (cellX, cellY) = config.cells[index]
64 | val rowOffset = if (cellY % 2 != 0) {
65 | config.halfItemSizePx
66 | } else {
67 | 0
68 | }
69 | val x = (cellX * config.itemSizePx) + offsetX.toInt() + rowOffset
70 | val y = (cellY * config.itemSizePx) + offsetY.toInt()
71 |
72 | return IntOffset(x, y)
73 | }
74 |
75 | override fun getScaleFor(position: IntOffset): Float {
76 | val (centerX, centerY) = position.plus(
77 | IntOffset(
78 | config.halfItemSizePx,
79 | config.halfItemSizePx
80 | )
81 | )
82 | val offsetX = centerX - config.layoutCenter.x
83 | val offsetY = centerY - config.layoutCenter.y
84 | val x = (offsetX * offsetX) / (config.a * config.a)
85 | val y = (offsetY * offsetY) / (config.b * config.b)
86 | val z = (-config.c * (x + y) + 1.1f)
87 | .coerceIn(
88 | minimumValue = CELL_MIN_SCALE,
89 | maximumValue = CELL_MAX_SCALE
90 | )
91 | return z
92 | }
93 | }
94 |
95 | @Composable
96 | fun rememberWatchGridState(): WatchGridState {
97 | return rememberSaveable(saver = WatchGridStateImpl.Saver) {
98 | WatchGridStateImpl()
99 | }
100 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/ktx/ModifierKtx.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.ktx
2 |
3 | import androidx.compose.animation.core.VectorConverter
4 | import androidx.compose.animation.core.calculateTargetValue
5 | import androidx.compose.animation.splineBasedDecay
6 | import androidx.compose.foundation.gestures.*
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.geometry.Offset
9 | import androidx.compose.ui.input.pointer.*
10 | import androidx.compose.ui.input.pointer.util.VelocityTracker
11 | import androidx.compose.ui.input.pointer.util.addPointerInputChange
12 | import kotlinx.coroutines.coroutineScope
13 | import kotlinx.coroutines.launch
14 |
15 | fun Modifier.drag(state: OffsetState) = pointerInput(Unit) {
16 | val decay = splineBasedDecay(this)
17 | val tracker = VelocityTracker()
18 | coroutineScope {
19 | awaitEachGesture {
20 | val pointerId = awaitFirstDown(requireUnconsumed = false).id
21 | launch {
22 | state.stop()
23 | }
24 | tracker.resetTracking()
25 | var dragPointerInput: PointerInputChange?
26 | var overSlop = Offset.Zero
27 | do {
28 | dragPointerInput = awaitTouchSlopOrCancellation(
29 | pointerId
30 | ) { change, over ->
31 | if (change.positionChange() != Offset.Zero) change.consume()
32 | overSlop = over
33 | }
34 | } while (dragPointerInput != null && !dragPointerInput.isConsumed)
35 |
36 | dragPointerInput?.let {
37 |
38 | launch {
39 | state.snapTo(state.currentOffset.plus(overSlop))
40 | }
41 |
42 | drag(dragPointerInput.id) { change ->
43 | val dragAmount = change.positionChange()
44 | launch {
45 | state.snapTo(state.currentOffset.plus(dragAmount))
46 | }
47 | if (change.positionChange() != Offset.Zero) change.consume()
48 | tracker.addPointerInputChange(change)
49 | }
50 | }
51 |
52 | val (velX, velY) = tracker.calculateVelocity()
53 | val velocity = Offset(velX, velY)
54 | val targetOffset = decay.calculateTargetValue(
55 | typeConverter = Offset.VectorConverter,
56 | initialValue = state.currentOffset,
57 | initialVelocity = velocity
58 | )
59 | launch {
60 | state.animateTo(
61 | offset = targetOffset,
62 | velocity = velocity,
63 | )
64 | }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/ktx/OffsetState.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.ktx
2 |
3 | import androidx.compose.animation.core.Animatable
4 | import androidx.compose.animation.core.AnimationVector2D
5 | import androidx.compose.ui.geometry.Offset
6 |
7 | /**
8 | * @see [drag]
9 | */
10 | interface OffsetState {
11 |
12 | val currentOffset: Offset
13 | get() = animatable.value
14 |
15 | val animatable: Animatable
16 |
17 | suspend fun snapTo(offset: Offset)
18 | suspend fun animateTo(offset: Offset, velocity: Offset)
19 | suspend fun stop()
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/magnet/MagnetButtun.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.magnet
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.gestures.detectDragGestures
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.BoxWithConstraints
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.offset
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.material3.Surface
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.geometry.Offset
19 | import androidx.compose.ui.geometry.Rect
20 | import androidx.compose.ui.geometry.RoundRect
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.input.pointer.pointerInput
23 | import androidx.compose.ui.platform.LocalDensity
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import androidx.compose.ui.unit.Dp
26 | import androidx.compose.ui.unit.IntOffset
27 | import androidx.compose.ui.unit.dp
28 |
29 | @Composable
30 | fun MagnetButtonScreen() {
31 | Surface {
32 | MagnetButtonBox(
33 | modifier = Modifier
34 | .fillMaxSize()
35 | .background(color = Color.LightGray)
36 | )
37 | }
38 | }
39 |
40 | @Preview
41 | @Composable
42 | fun MagnetButtonScreenPreview() {
43 | MagnetButtonScreen()
44 | }
45 |
46 | @Composable
47 | fun MagnetButtonBox(
48 | modifier: Modifier = Modifier,
49 | buttonSize: Dp = 72.dp,
50 | ) {
51 | BoxWithConstraints(modifier) {
52 |
53 | val boxWidth = with(LocalDensity.current) { maxWidth.toPx() }
54 | val boxHeight = with(LocalDensity.current) { maxHeight.toPx() }
55 | val buttonHalfSizePx = with(LocalDensity.current) { buttonSize.toPx() }
56 |
57 | val bounds = Rect(
58 | topLeft = Offset.Zero,
59 | bottomRight = Offset(boxWidth - buttonHalfSizePx, boxHeight - buttonHalfSizePx)
60 | )
61 |
62 | var offsetX by remember { mutableStateOf(0) }
63 | var offsetY by remember { mutableStateOf(0) }
64 | Box(
65 | modifier = Modifier
66 | .offset { IntOffset(offsetX, offsetY) }
67 | .size(buttonSize)
68 | .background(color = Color.Blue, shape = RoundedCornerShape(20.dp))
69 | .pointerInput(Unit) {
70 | detectDragGestures { change, dragAmount ->
71 | change.consume()
72 | val x = offsetX + dragAmount.x.toInt()
73 | val y = offsetY + dragAmount.y.toInt()
74 | if(bounds.contains(Offset(x.toFloat(), y.toFloat()))) {
75 | offsetX = x
76 | offsetY = y
77 | }
78 | }
79 | }
80 | )
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/switch/DayAndNightSwitch.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.switch
2 |
3 | import android.view.animation.OvershootInterpolator
4 | import androidx.compose.animation.animateColorAsState
5 | import androidx.compose.animation.core.Animatable
6 | import androidx.compose.animation.core.tween
7 | import androidx.compose.foundation.background
8 | import androidx.compose.foundation.indication
9 | import androidx.compose.foundation.interaction.InteractionSource
10 | import androidx.compose.foundation.interaction.MutableInteractionSource
11 | import androidx.compose.foundation.layout.*
12 | import androidx.compose.foundation.selection.toggleable
13 | import androidx.compose.foundation.shape.RoundedCornerShape
14 | import androidx.compose.material.ripple.rememberRipple
15 | import androidx.compose.runtime.*
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.draw.scale
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.graphics.Shape
21 | import androidx.compose.ui.graphics.TransformOrigin
22 | import androidx.compose.ui.graphics.graphicsLayer
23 | import androidx.compose.ui.platform.LocalDensity
24 | import androidx.compose.ui.semantics.Role
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import androidx.compose.ui.unit.IntOffset
27 | import androidx.compose.ui.unit.dp
28 | import kotlinx.coroutines.coroutineScope
29 | import kotlinx.coroutines.launch
30 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.AnimationDuration
31 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.Black
32 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.Orange
33 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.RippleRadius
34 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.SwitchHeight
35 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.SwitchWidth
36 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.ThumbDiameter
37 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.ThumbPathLength
38 | import ru.ozh.compose.challenges.ui.switch.SwitchConsts.ThumbStartOffset
39 | import kotlin.math.roundToInt
40 |
41 |
42 | @Composable
43 | fun SwitchDaN(
44 | checked: Boolean,
45 | onCheckedChange: ((Boolean) -> Unit)?,
46 | modifier: Modifier = Modifier,
47 | enabled: Boolean = true,
48 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
49 | ) {
50 |
51 | val minBound = with(LocalDensity.current) { ThumbStartOffset.toPx() }
52 | val maxBound = with(LocalDensity.current) { ThumbPathLength.toPx() }
53 | val valueToOffset = remember<(Boolean) -> Float>(minBound, maxBound) {
54 | { value -> if (value) maxBound else minBound }
55 | }
56 |
57 | val targetValue = valueToOffset(checked)
58 | val offset = remember { Animatable(targetValue) }
59 | val scaleX = remember { Animatable(1f) }
60 | val scaleY = remember { Animatable(1f) }
61 |
62 | val scope = rememberCoroutineScope()
63 |
64 | DisposableEffect(checked) {
65 | if (offset.targetValue != targetValue) {
66 | scope.launch {
67 | launch {
68 | offset.animateTo(
69 | targetValue = targetValue,
70 | animationSpec = tween(
71 | durationMillis = AnimationDuration,
72 | easing = OvershootInterpolator(1.5f).toEasing()
73 | )
74 | )
75 | }
76 |
77 | launch {
78 | scaleY.animateTo(
79 | targetValue = 0.8f,
80 | animationSpec = tween(
81 | durationMillis = AnimationDuration / 2,
82 | )
83 | )
84 |
85 | coroutineScope {
86 | launch {
87 | scaleX.animateTo(
88 | targetValue = 0.9f,
89 | animationSpec = tween(
90 | durationMillis = AnimationDuration / 4,
91 | )
92 | )
93 |
94 | scaleX.animateTo(
95 | targetValue = 1f,
96 | animationSpec = tween(
97 | durationMillis = AnimationDuration / 2,
98 | )
99 | )
100 | }
101 | launch {
102 | scaleY.animateTo(
103 | targetValue = 1.1f,
104 | animationSpec = tween(
105 | durationMillis = AnimationDuration / 4,
106 | )
107 | )
108 |
109 | scaleY.animateTo(
110 | targetValue = 1f,
111 | animationSpec = tween(
112 | durationMillis = AnimationDuration / 2,
113 | )
114 | )
115 | }
116 | }
117 | }
118 | }
119 | }
120 | onDispose { }
121 | }
122 |
123 | val toggleableModifier = if (onCheckedChange != null) {
124 | Modifier.toggleable(
125 | value = checked,
126 | onValueChange = onCheckedChange,
127 | enabled = enabled,
128 | role = Role.Switch,
129 | interactionSource = interactionSource,
130 | indication = null
131 | )
132 | } else {
133 | Modifier
134 | }
135 |
136 | Box(
137 | modifier
138 | .then(toggleableModifier)
139 | .wrapContentSize(Alignment.Center)
140 | .requiredSize(SwitchWidth, SwitchHeight)
141 | ) {
142 | SwitchImpl(
143 | scaleX = scaleX.asState(),
144 | scaleY = scaleY.asState(),
145 | checked = checked,
146 | thumbValue = offset.asState(),
147 | interactionSource = interactionSource,
148 | thumbShape = RoundedCornerShape(50),
149 | )
150 | }
151 | }
152 |
153 | @Composable
154 | private fun BoxScope.SwitchImpl(
155 | checked: Boolean,
156 | thumbValue: State,
157 | scaleX: State,
158 | scaleY: State,
159 | interactionSource: InteractionSource,
160 | thumbShape: Shape,
161 | ) {
162 | val trackColor by animateColorAsState(if (checked) Orange else Black)
163 |
164 | val modifier = Modifier
165 | .align(Alignment.Center)
166 | .width(SwitchWidth)
167 | .height(SwitchHeight)
168 | .background(trackColor, RoundedCornerShape(50))
169 |
170 | Box(modifier) {
171 |
172 | if (checked) {
173 | SunIcon(
174 | modifier = Modifier
175 | .offset(x = 2.dp)
176 | .requiredSize(24.dp)
177 | .align(Alignment.CenterStart)
178 | .scale(0.8f),
179 | color = Color.White
180 | )
181 | } else {
182 | MoonIcon(
183 | modifier = Modifier
184 | .offset(x = (-2).dp)
185 | .requiredSize(24.dp)
186 | .align(Alignment.CenterEnd)
187 | .scale(0.8f),
188 | color = Color.White
189 | )
190 | }
191 |
192 | Box(
193 | modifier = Modifier
194 | .align(Alignment.CenterStart)
195 | .graphicsLayer {
196 | this.scaleX = scaleX.value
197 | this.scaleY = scaleY.value
198 | this.transformOrigin = if (checked) {
199 | TransformOrigin(3f, 0.5f)
200 | } else {
201 | TransformOrigin(0f, 0.5f)
202 | }
203 | }
204 | .offset { IntOffset(thumbValue.value.roundToInt(), 0) }
205 | // .indication(
206 | // interactionSource = interactionSource,
207 | //indication = rememberRipple(bounded = false, RippleRadius)
208 | // )
209 | .requiredSize(ThumbDiameter)
210 | .background(Color.White, thumbShape),
211 | contentAlignment = Alignment.Center
212 | ) {
213 |
214 | }
215 | }
216 | }
217 |
218 | @Preview(
219 | widthDp = 300,
220 | heightDp = 300
221 | )
222 | @Composable
223 | fun SwitchDaNPreview() {
224 | Box(
225 | modifier = Modifier.fillMaxSize(),
226 | contentAlignment = Alignment.Center
227 | ) {
228 | var checked by remember {
229 | mutableStateOf(true)
230 | }
231 | SwitchDaN(
232 | checked = checked,
233 | onCheckedChange = {
234 | checked = it
235 | }
236 | )
237 | }
238 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/switch/MoonIcon.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.switch
2 |
3 | import androidx.compose.animation.core.Animatable
4 | import androidx.compose.animation.core.TweenSpec
5 | import androidx.compose.foundation.Canvas
6 | import androidx.compose.foundation.layout.BoxWithConstraints
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.LaunchedEffect
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.graphics.Path
13 | import androidx.compose.ui.graphics.drawscope.Stroke
14 | import androidx.compose.ui.graphics.drawscope.rotate
15 | import androidx.compose.ui.graphics.drawscope.scale
16 | import androidx.compose.ui.platform.LocalDensity
17 | import androidx.compose.ui.unit.dp
18 | import kotlinx.coroutines.launch
19 |
20 | @Composable
21 | fun MoonIcon(
22 | modifier: Modifier,
23 | color: Color
24 | ) {
25 |
26 | BoxWithConstraints(modifier) {
27 | val width = this.maxWidth
28 | val height = this.maxHeight
29 | val sizePx = with(LocalDensity.current) { width.toPx() }
30 |
31 | require(width == height) {
32 | error("width and height must be equals, but width = $width & height = $height")
33 | }
34 |
35 | val scaleAnimation = remember { Animatable(0.8f) }
36 | val rotateAnimation = remember { Animatable(180f) }
37 | val moonStrokeWidth = with(LocalDensity.current) { 1.dp.toPx() }
38 | val path = Path()
39 |
40 | LaunchedEffect(Unit) {
41 | launch {
42 | rotateAnimation.animateTo(
43 | 0f,
44 | animationSpec = TweenSpec(
45 | durationMillis = 250
46 | )
47 | )
48 | }
49 |
50 | launch {
51 | scaleAnimation.animateTo(
52 | 1f,
53 | animationSpec = TweenSpec(
54 | durationMillis = 250
55 | )
56 | )
57 | }
58 | }
59 |
60 | Canvas(modifier = modifier) {
61 | path.reset()
62 | path.moveTo(x = sizePx * 0.7f, y = 0f)
63 | path.cubicTo(
64 | x1 = 0f,
65 | y1 = 0f,
66 | x2 = 0f,
67 | y2 = sizePx,
68 | x3 = sizePx * 0.7f,
69 | y3 = sizePx,
70 | )
71 |
72 | path.moveTo(x = sizePx * 0.7f, y = 0f)
73 | path.cubicTo(
74 | x1 = sizePx * 0.3f,
75 | y1 = sizePx * 0.2f,
76 | x2 = sizePx * 0.3f,
77 | y2 = sizePx * 0.8f,
78 | x3 = sizePx * 0.7f,
79 | y3 = sizePx,
80 | )
81 |
82 | scale(scale = scaleAnimation.value) {
83 | rotate(rotateAnimation.value - 15f) {
84 | drawPath(path, color, style = Stroke(width = moonStrokeWidth))
85 | }
86 | }
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/switch/SunIcon.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.switch
2 |
3 | import androidx.compose.animation.core.Animatable
4 | import androidx.compose.animation.core.TweenSpec
5 | import androidx.compose.foundation.Canvas
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.BoxWithConstraints
9 | import androidx.compose.foundation.layout.requiredSize
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.LaunchedEffect
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.geometry.Offset
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.StrokeCap
18 | import androidx.compose.ui.graphics.drawscope.Stroke
19 | import androidx.compose.ui.graphics.drawscope.rotate
20 | import androidx.compose.ui.platform.LocalDensity
21 | import androidx.compose.ui.tooling.preview.Preview
22 | import androidx.compose.ui.unit.dp
23 | import kotlinx.coroutines.delay
24 | import kotlinx.coroutines.launch
25 |
26 | @Composable
27 | fun SunIcon(
28 | modifier: Modifier,
29 | color: Color
30 | ) {
31 |
32 | BoxWithConstraints(modifier) {
33 | val width = this.maxWidth
34 | val height = this.maxHeight
35 |
36 | require(width == height) {
37 | error("width and height must be equals, but width = $width & height = $height")
38 | }
39 |
40 | val LightCount = 8
41 | val LightRotateAngle = 45f
42 | val sizePx = with(LocalDensity.current) { width.toPx() }
43 | val circleRadius = with(LocalDensity.current) { 4.dp.toPx() }
44 | val strokeWidth = with(LocalDensity.current) { 2.dp.toPx() }
45 |
46 | val circleAnimation = remember { Animatable(0f) }
47 | val lightsAlphaAnimation = remember { Array(LightCount) { Animatable(0f) } }
48 | val lightsOffsetAnimation = remember { Array(LightCount) { Animatable(0.75f) } }
49 |
50 | LaunchedEffect(Unit) {
51 | circleAnimation.animateTo(
52 | 1f,
53 | animationSpec = TweenSpec(
54 | durationMillis = 75
55 | )
56 | )
57 |
58 | lightsAlphaAnimation.zip(lightsOffsetAnimation) { alpha, offset -> alpha to offset }
59 | .withIndex()
60 | .forEach { (index, value) ->
61 | val (alpha, offset) = value
62 | delay(index * 5L)
63 | launch {
64 | alpha.animateTo(
65 | 1f,
66 | animationSpec = TweenSpec(
67 | durationMillis = 10
68 | )
69 | )
70 | }
71 | launch {
72 | offset.animateTo(
73 | 1f,
74 | animationSpec = TweenSpec(
75 | durationMillis = 10
76 | )
77 | )
78 | }
79 | }
80 | }
81 |
82 | Canvas(modifier = modifier) {
83 |
84 | drawCircle(
85 | color = color,
86 | radius = circleRadius * circleAnimation.value,
87 | style = Stroke(width = strokeWidth)
88 | )
89 |
90 | repeat(LightCount) { index ->
91 | rotate(degrees = -90 + (index * LightRotateAngle)) {
92 | val lightStartX = sizePx * 0.8f
93 | val lightEndX = sizePx * if (index % 2 == 0) 1f else 0.95f
94 |
95 | val lightY = sizePx / 2
96 |
97 | val lightStart = Offset(
98 | x = lightStartX * lightsOffsetAnimation[index].value,
99 | y = lightY
100 | )
101 | val lightEnd = Offset(
102 | x = lightEndX * lightsOffsetAnimation[index].value,
103 | y = lightY
104 | )
105 |
106 | drawLine(
107 | color = color.copy(alpha = lightsAlphaAnimation[index].value),
108 | start = lightStart,
109 | end = lightEnd,
110 | strokeWidth = strokeWidth * lightsOffsetAnimation[index].value,
111 | cap = StrokeCap.Round
112 | )
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
119 | @Preview(widthDp = 32, heightDp = 32, showBackground = false)
120 | @Composable
121 | fun SunIconPreview() {
122 | Box(
123 | modifier = Modifier
124 | .requiredSize(24.dp)
125 | .background(color = Color.Blue)
126 | ) {
127 | SunIcon(
128 | modifier = Modifier
129 | .requiredSize(24.dp)
130 | .align(Alignment.CenterStart),
131 | color = Color.White,
132 | )
133 | }
134 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/switch/SwitchConsts.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.switch
2 |
3 | import android.animation.TimeInterpolator
4 | import androidx.compose.animation.core.*
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.unit.dp
7 |
8 | internal object SwitchConsts {
9 |
10 | val AnimationDuration = 250
11 | val RippleRadius = 20.0.dp
12 | val SwitchWidth = 60.0.dp
13 | val SwitchHeight = 32.0.dp
14 | val ThumbDiameter = 24.0.dp
15 | val ThumbStartOffset = 4.dp
16 | val ThumbPadding = (SwitchHeight - ThumbDiameter) / 2
17 | val ThumbPathLength = (SwitchWidth - ThumbDiameter) - ThumbPadding
18 |
19 | val Black = Color(red = 53, green = 53, blue = 53)
20 | val Orange = Color(red = 243, green = 109, blue = 24)
21 | }
22 |
23 |
24 | fun TimeInterpolator.toEasing() = Easing { x -> getInterpolation(x) }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/switch/SwitchMoon.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.switch
2 |
3 | import androidx.compose.animation.core.animateFloatAsState
4 | import androidx.compose.foundation.Canvas
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material3.Switch
8 | import androidx.compose.material3.SwitchDefaults
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.draw.clip
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.drawscope.translate
18 | import androidx.compose.ui.semantics.contentDescription
19 | import androidx.compose.ui.semantics.semantics
20 | import androidx.compose.ui.tooling.preview.Preview
21 | import androidx.compose.ui.unit.dp
22 |
23 | @Composable
24 | fun SwitchMoon() {
25 | var isChecked by remember { mutableStateOf(true) }
26 | val colors = SwitchDefaults.colors().copy(
27 | uncheckedBorderColor = Color.Transparent,
28 | checkedThumbColor = Color.Transparent,
29 | checkedTrackColor = Color(0XFF261439),
30 | uncheckedTrackColor = Color(0xFFFFECB3),
31 | )
32 | Switch(
33 | modifier = Modifier.semantics {
34 | contentDescription = "SwitchMoon"
35 | },
36 | checked = isChecked,
37 | onCheckedChange = { isChecked = it },
38 | colors = colors,
39 | thumbContent = { MoonThumb(isChecked) }
40 | )
41 | }
42 |
43 | @Composable
44 | @Preview
45 | fun SwitchMoonPreview() {
46 | SwitchMoon()
47 | }
48 |
49 | @Composable
50 | fun MoonThumb(isChecked: Boolean) {
51 | val translateFactor by animateFloatAsState(
52 | targetValue = if (isChecked) 0.45f else 1f,
53 | label = "translateFactor",
54 | )
55 | Canvas(
56 | modifier = Modifier
57 | .size(24.dp)
58 | .clip(RoundedCornerShape(40)),
59 | ) {
60 | val diameter = size.maxDimension
61 | val radius = diameter / 2
62 | drawCircle(
63 | color = Color(0XFFFCC008),
64 | radius = radius,
65 | )
66 |
67 | translate(left = -diameter * translateFactor) {
68 | drawCircle(
69 | color = Color(0XFF261439),
70 | radius = radius
71 | )
72 | }
73 | }
74 | }
75 |
76 | @Composable
77 | @Preview
78 | fun MoonThumbPreview() {
79 | val isChecked by remember { mutableStateOf(false) }
80 | MoonThumb(isChecked)
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material3.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.*
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.SideEffect
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.graphics.toArgb
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.platform.LocalView
13 | import androidx.core.view.ViewCompat
14 |
15 | private val DarkColorScheme = darkColorScheme(
16 | primary = Purple80,
17 | secondary = PurpleGrey80,
18 | tertiary = Pink80
19 | )
20 |
21 | private val LightColorScheme = lightColorScheme(
22 | primary = Purple40,
23 | secondary = PurpleGrey40,
24 | tertiary = Pink40,
25 | background = Color(0xFFFFFBFE),
26 | surface = Color(0xFFFFFBFE),
27 | onPrimary = Color.White,
28 | onSecondary = Color.White,
29 | onTertiary = Color.White,
30 | onBackground = Color(0xFF1C1B1F),
31 | onSurface = Color(0xFF1C1B1F),
32 | )
33 |
34 | @Composable
35 | fun ComposeChallengesTheme(
36 | darkTheme: Boolean = isSystemInDarkTheme(),
37 | // Dynamic color is available on Android 12+
38 | dynamicColor: Boolean = true,
39 | content: @Composable () -> Unit
40 | ) {
41 | val colorScheme = when {
42 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
43 | val context = LocalContext.current
44 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
45 | }
46 | darkTheme -> DarkColorScheme
47 | else -> LightColorScheme
48 | }
49 | val view = LocalView.current
50 | if (!view.isInEditMode) {
51 | SideEffect {
52 | (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
53 | ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
54 | }
55 | }
56 |
57 | MaterialTheme(
58 | colorScheme = colorScheme,
59 | typography = Typography,
60 | content = content
61 | )
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/ozh/compose/challenges/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_app_store.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
17 |
20 |
23 |
24 |
25 |
26 |
30 |
34 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_apple_music.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
17 |
20 |
23 |
24 |
25 |
26 |
30 |
33 |
34 |
39 |
42 |
45 |
46 |
47 |
48 |
51 |
52 |
57 |
60 |
63 |
66 |
67 |
68 |
69 |
72 |
73 |
79 |
82 |
85 |
86 |
87 |
88 |
92 |
93 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_apple_tv.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
28 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_books.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_calculator.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
27 |
30 |
33 |
36 |
39 |
42 |
45 |
48 |
51 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_calendar.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_camera.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
28 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_clock.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
22 |
26 |
30 |
34 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_contacts.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
15 |
18 |
21 |
22 |
23 |
29 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
46 |
49 |
52 |
53 |
54 |
55 |
58 |
62 |
65 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_files.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
15 |
19 |
23 |
26 |
27 |
33 |
36 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_find_my.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
27 |
30 |
33 |
36 |
39 |
40 |
41 |
47 |
50 |
53 |
56 |
59 |
62 |
65 |
66 |
67 |
68 |
71 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_health.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
20 |
23 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/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 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mail.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
17 |
20 |
23 |
24 |
25 |
26 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_maps.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
12 |
16 |
20 |
24 |
30 |
34 |
40 |
44 |
48 |
52 |
56 |
60 |
68 |
72 |
76 |
81 |
87 |
93 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_messages.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
17 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_notes.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
15 |
16 |
17 |
23 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
48 |
49 |
52 |
55 |
58 |
61 |
64 |
67 |
70 |
73 |
76 |
79 |
82 |
85 |
88 |
91 |
94 |
97 |
100 |
103 |
104 |
105 |
111 |
114 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_photos.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
22 |
26 |
30 |
34 |
38 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_podcasts.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
17 |
20 |
23 |
24 |
25 |
26 |
29 |
32 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_safari.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
40 |
46 |
52 |
58 |
64 |
70 |
76 |
82 |
88 |
94 |
100 |
106 |
112 |
118 |
124 |
130 |
136 |
142 |
148 |
154 |
160 |
166 |
172 |
178 |
184 |
190 |
196 |
202 |
208 |
214 |
220 |
226 |
232 |
238 |
244 |
250 |
254 |
258 |
259 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stocks.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
15 |
18 |
19 |
25 |
28 |
31 |
32 |
33 |
34 |
37 |
42 |
47 |
52 |
55 |
60 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_translate.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
19 |
22 |
25 |
28 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_wallet.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
17 |
21 |
25 |
29 |
33 |
37 |
41 |
45 |
49 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_weather.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-v23/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Compose-Challenges
3 | MainActivity3
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/ru/ozh/compose/challenges/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package ru.ozh.compose.challenges
2 |
3 |
--------------------------------------------------------------------------------
/art/apple_grid_art.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/art/apple_grid_art.gif
--------------------------------------------------------------------------------
/art/clock.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/art/clock.gif
--------------------------------------------------------------------------------
/art/day_and_night_switch.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/art/day_and_night_switch.gif
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 |
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 |
9 | plugins {
10 | alias(libs.plugins.android.application) apply false
11 | alias(libs.plugins.kotlin.android) apply false
12 | alias(libs.plugins.kotlin.compose) apply false
13 | }
14 |
15 | //task clean(type: Delete) {
16 | // delete rootProject.layout.buildDirectory
17 | //}
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | activityCompose = "1.9.3"
3 | agp = "8.6.1"
4 | composeBom = "2024.10.00"
5 | coreKtx = "1.13.1"
6 | kotlin = "2.0.20"
7 | lifecycleRuntimeKtx = "2.8.6"
8 | material3 = "1.3.0"
9 |
10 | [libraries]
11 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
12 | androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
13 | androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
14 | androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
15 | androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
16 | androidx-material3-windows-size = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "material3" }
17 | androidx-ui = { module = "androidx.compose.ui:ui" }
18 | androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
19 |
20 | [plugins]
21 | android-application = { id = "com.android.application", version.ref = "agp" }
22 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
23 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 01 12:18:59 MSK 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Compose-Challenges"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/stability_config.conf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ozh-dev/Compose-Challenges/64575bcbc58b9c721ac8aac0cb929ed4358c24c7/stability_config.conf
--------------------------------------------------------------------------------
|