├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── br │ │ └── com │ │ └── programadorthi │ │ └── compose │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── br │ │ │ └── com │ │ │ └── programadorthi │ │ │ └── compose │ │ │ ├── MainActivity.kt │ │ │ ├── composables │ │ │ ├── animated_title.kt │ │ │ ├── app_bar.kt │ │ │ ├── background.kt │ │ │ ├── drawer.kt │ │ │ ├── forecast_content.kt │ │ │ └── rain_particles.kt │ │ │ ├── controllers │ │ │ └── DrawerController.kt │ │ │ ├── helpers │ │ │ ├── animations.kt │ │ │ └── modifiers.kt │ │ │ └── models │ │ │ ├── app_title_models.kt │ │ │ ├── drawer_models.kt │ │ │ ├── forecast_models.kt │ │ │ └── particle_models.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── background.jpg │ │ ├── background_blur.jpg │ │ ├── ic_baseline_arrow_forward_ios_24.xml │ │ ├── ic_baseline_refresh_24.xml │ │ ├── ic_baseline_wb_cloudy_24.xml │ │ ├── ic_baseline_wb_rainy_24.xml │ │ ├── ic_baseline_wb_sunny_24.xml │ │ ├── ic_launcher_background.xml │ │ └── rain.png │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── br │ └── com │ └── programadorthi │ └── compose │ └── ExampleUnitTest.kt ├── build.gradle ├── demo.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Compose Started -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weather Compose 2 | Weather forecast layout built with [Jetpack Compose](https://developer.android.com/jetpack/compose). 3 | 4 | To try out this example, you need to use Android Studio 4.1 Canary 3 or later, and import the 5 | project. 6 | 7 | Screenshots 8 | ----------- 9 | Screenshot 10 | 11 | ## Important 12 | 13 | - This project was built with 0.1.0-dev06. Some behaviors implemented here maybe exists in new versions like LayoutOffset modifier. 14 | - LayoutFractionalOffset is a feature that doesn't exists in the compose at now. 15 | - There is no HitTestBehavior to Clickable 16 | - Icon doesn't have a property to resize it 17 | 18 | ## Next steps 19 | 20 | - [ ] Upgrade to new Compose version that is breaking build for now 21 | - [X] Add particles system to create rain effect 22 | 23 | ## Credits 24 | - [Chris Slowik](https://dribbble.com/chrisslowik) that created this awesome [Weather design](https://dribbble.com/shots/1212896-Weather-Rebound) 25 | - [Matt Carroll](https://github.com/matthew-carroll) for the Flutter challenge before works at Flutter team 26 | - [Android Team](https://twitter.com/androiddev) for Android SO and Jetpack Compose 27 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.3" 9 | 10 | defaultConfig { 11 | applicationId "br.com.programadorthi.compose" 12 | minSdkVersion 23 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | signingConfig signingConfigs.debug 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | buildFeatures { 35 | compose true 36 | } 37 | composeOptions { 38 | kotlinCompilerExtensionVersion "$compose_version" 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 44 | implementation 'androidx.core:core-ktx:1.2.0' 45 | implementation 'androidx.appcompat:appcompat:1.1.0' 46 | implementation "androidx.ui:ui-core:$compose_version" 47 | implementation "androidx.ui:ui-animation:$compose_version" 48 | implementation "androidx.ui:ui-foundation:$compose_version" 49 | implementation "androidx.ui:ui-framework:$compose_version" 50 | implementation "androidx.ui:ui-graphics:$compose_version" 51 | implementation "androidx.ui:ui-layout:$compose_version" 52 | implementation "androidx.ui:ui-material:$compose_version" 53 | implementation "androidx.ui:ui-tooling:$compose_version" 54 | testImplementation 'junit:junit:4.13' 55 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 57 | } -------------------------------------------------------------------------------- /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/androidTest/java/br/com/programadorthi/compose/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("br.com.programadorthi.compose", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.compose.remember 6 | import androidx.compose.state 7 | import androidx.ui.animation.animatedFloat 8 | import androidx.ui.core.setContent 9 | import androidx.ui.foundation.Box 10 | import androidx.ui.layout.LayoutGravity 11 | import androidx.ui.layout.LayoutPadding 12 | import androidx.ui.layout.Stack 13 | import androidx.ui.material.MaterialTheme 14 | import androidx.ui.unit.dp 15 | import br.com.programadorthi.compose.composables.AppBar 16 | import br.com.programadorthi.compose.composables.Drawer 17 | import br.com.programadorthi.compose.composables.ForecastContent 18 | import br.com.programadorthi.compose.controllers.DrawerController 19 | import br.com.programadorthi.compose.helpers.LayoutFractionalOffset 20 | import br.com.programadorthi.compose.models.drawerItems 21 | import br.com.programadorthi.compose.models.forecasts 22 | 23 | class MainActivity : AppCompatActivity() { 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | 27 | val drawerItems = drawerItems() 28 | val forecasts = forecasts() 29 | 30 | setContent { 31 | val drawerController = remember { 32 | DrawerController( 33 | animation = animatedFloat(initVal = 1f).apply { 34 | setBounds(min = 0f, max = 1f) 35 | } 36 | ) 37 | } 38 | 39 | val state = state { drawerItems.first() } 40 | 41 | MaterialTheme { 42 | Stack(children = { 43 | ForecastContent(state.value, forecasts) 44 | 45 | Box(modifier = LayoutGravity.TopStart + LayoutPadding(top = 24.dp)) { 46 | AppBar(drawerController, state) 47 | } 48 | 49 | // TODO: should have a clickable in out area to hide drawer 50 | // TODO: clickable doesn't support HitTestBehavior 51 | Box( 52 | modifier = LayoutGravity.CenterEnd + LayoutFractionalOffset( 53 | dx = drawerController.animationProgress, 54 | dy = 0f 55 | ) 56 | ) { 57 | Drawer(drawerController, drawerItems) { item -> 58 | state.value = item 59 | } 60 | } 61 | }) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/composables/animated_title.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.composables 2 | 3 | import androidx.animation.FastOutSlowInEasing 4 | import androidx.animation.TweenBuilder 5 | import androidx.compose.Composable 6 | import androidx.compose.onCommit 7 | import androidx.compose.remember 8 | import androidx.ui.animation.animatedFloat 9 | import androidx.ui.core.Text 10 | import androidx.ui.graphics.Color 11 | import androidx.ui.layout.Stack 12 | import androidx.ui.text.TextStyle 13 | import androidx.ui.unit.TextUnit 14 | import br.com.programadorthi.compose.helpers.LayoutFractionalOffset 15 | import br.com.programadorthi.compose.models.AppTitle 16 | 17 | private val TweenBuilder = TweenBuilder().apply { 18 | duration = 750 19 | easing = FastOutSlowInEasing 20 | } 21 | 22 | @Composable 23 | fun AnimatedTitle(title: String) { 24 | val animation = animatedFloat(initVal = 0f).apply { 25 | setBounds(min = 0f, max = 1f) 26 | } 27 | 28 | val appTitle = remember { AppTitle() } 29 | 30 | onCommit(title) { 31 | if (appTitle.bottomText != title) { 32 | appTitle.topText = title 33 | 34 | animation.animateTo(targetValue = 1f, anim = TweenBuilder, onEnd = { _, _ -> 35 | appTitle.apply { 36 | bottomText = title 37 | topText = "" 38 | } 39 | animation.snapTo(0f) 40 | }) 41 | } 42 | } 43 | 44 | Stack { 45 | CustomText( 46 | modifier = LayoutFractionalOffset(0f, animation.value - 1f), 47 | text = appTitle.topText 48 | ) 49 | CustomText( 50 | modifier = LayoutFractionalOffset(0f, animation.value), 51 | text = appTitle.bottomText 52 | ) 53 | } 54 | } 55 | 56 | @Composable 57 | private fun CustomText( 58 | modifier: LayoutFractionalOffset = LayoutFractionalOffset.Origin, 59 | text: String 60 | ) { 61 | Text( 62 | modifier = modifier, 63 | text = text, 64 | style = TextStyle( 65 | color = Color.White, 66 | fontSize = TextUnit.Sp(16) 67 | ) 68 | ) 69 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/composables/app_bar.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.composables 2 | 3 | import androidx.compose.Composable 4 | import androidx.compose.State 5 | import androidx.ui.core.Clip 6 | import androidx.ui.core.Text 7 | import androidx.ui.foundation.Box 8 | import androidx.ui.foundation.Clickable 9 | import androidx.ui.foundation.Icon 10 | import androidx.ui.foundation.shape.RectangleShape 11 | import androidx.ui.graphics.Color 12 | import androidx.ui.layout.* 13 | import androidx.ui.material.contentColorFor 14 | import androidx.ui.material.surface.Surface 15 | import androidx.ui.res.vectorResource 16 | import androidx.ui.text.TextStyle 17 | import androidx.ui.unit.TextUnit 18 | import androidx.ui.unit.dp 19 | import br.com.programadorthi.compose.R 20 | import br.com.programadorthi.compose.controllers.DrawerController 21 | import br.com.programadorthi.compose.models.DrawerItem 22 | 23 | 24 | @Composable 25 | fun AppBar( 26 | drawerController: DrawerController, 27 | state: State 28 | ) { 29 | val surfaceColor = Color.Transparent 30 | val icDrawer = vectorResource(id = R.drawable.ic_baseline_arrow_forward_ios_24) 31 | val appBarHeight = 60.dp 32 | 33 | Surface( 34 | color = surfaceColor, 35 | contentColor = contentColorFor(color = surfaceColor), 36 | elevation = 0.dp, 37 | shape = RectangleShape 38 | ) { 39 | Row( 40 | LayoutWidth.Fill + LayoutPadding( 41 | start = 16.dp, 42 | end = 16.dp 43 | ) + LayoutHeight(appBarHeight), 44 | arrangement = Arrangement.SpaceBetween, 45 | children = { 46 | Box { 47 | Column { 48 | Box { 49 | Clip(shape = RectangleShape) { 50 | AnimatedTitle(title = state.value.withComma()) 51 | } 52 | } 53 | Text( 54 | text = "Sacramento", 55 | style = TextStyle( 56 | color = Color.White, 57 | fontSize = TextUnit.Sp(30) 58 | ) 59 | ) 60 | } 61 | } 62 | Box( 63 | modifier = LayoutSize( 64 | icDrawer.defaultWidth, 65 | appBarHeight 66 | ) + LayoutAlign.Center 67 | ) { 68 | Clickable(onClick = { 69 | drawerController.open() 70 | }) { 71 | Icon(icon = icDrawer, tint = Color.White) 72 | } 73 | } 74 | } 75 | ) 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/composables/background.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.composables 2 | 3 | import android.content.res.Resources 4 | import androidx.compose.Composable 5 | import androidx.ui.core.Clip 6 | import androidx.ui.core.DensityAmbient 7 | import androidx.ui.core.DrawModifier 8 | import androidx.ui.core.toModifier 9 | import androidx.ui.foundation.Box 10 | import androidx.ui.foundation.shape.GenericShape 11 | import androidx.ui.geometry.Offset 12 | import androidx.ui.geometry.Rect 13 | import androidx.ui.graphics.* 14 | import androidx.ui.graphics.painter.ImagePainter 15 | import androidx.ui.layout.LayoutSize 16 | import androidx.ui.layout.Stack 17 | import androidx.ui.res.imageResource 18 | import androidx.ui.unit.Density 19 | import androidx.ui.unit.PxSize 20 | import androidx.ui.unit.toRect 21 | import br.com.programadorthi.compose.R 22 | import androidx.ui.foundation.Canvas as Board 23 | 24 | val overlayColor = Color(0xFFAA88AA) 25 | val overlayPaint = Paint() 26 | val overlayBorderPaint = Paint().apply { 27 | color = Color(0x14FFFFFF) 28 | style = PaintingStyle.stroke 29 | strokeWidth = 5f 30 | } 31 | 32 | data class Circle( 33 | val radius: Float, 34 | val alpha: Float 35 | ) 36 | 37 | fun Canvas.maskCircle(size: PxSize, radius: Float, centerOffset: Offset) { 38 | val path = Path().apply { 39 | setFillType(PathFillType.evenOdd) 40 | addRect(Rect.fromLTWH(0f, 0f, size.width.value, size.height.value)) 41 | addOval( 42 | Rect.fromCircle( 43 | center = Offset(0f, (size.height / 2).value) + centerOffset, 44 | radius = radius 45 | ) 46 | ) 47 | } 48 | clipPath(path) 49 | } 50 | 51 | @Composable 52 | fun Background() { 53 | val displayMetrics = Resources.getSystem().displayMetrics 54 | val screenHeight = displayMetrics.heightPixels 55 | val clipRadius = displayMetrics.widthPixels / 2.5f 56 | val marginLeftOffset = Offset(40f, 0f) 57 | val center = Offset(0f, screenHeight / 2f) + marginLeftOffset 58 | 59 | Stack { 60 | Image( 61 | image = imageResource( 62 | R.drawable.background_blur 63 | ) 64 | ) 65 | Clip(shape = GenericShape { 66 | addOval(Rect.fromCircle(center = center, radius = clipRadius)) 67 | }) { 68 | Image( 69 | image = imageResource( 70 | R.drawable.background 71 | ) 72 | ) 73 | } 74 | WhiteCircleDraw( 75 | centerOffset = marginLeftOffset, 76 | circles = listOf( 77 | Circle( 78 | radius = clipRadius, 79 | alpha = 0.10f 80 | ), 81 | Circle( 82 | radius = clipRadius + 30f, 83 | alpha = 0.20f 84 | ), 85 | Circle( 86 | radius = clipRadius + 80f, 87 | alpha = 0.30f 88 | ), 89 | Circle( 90 | radius = clipRadius + 200f, 91 | alpha = 0.35f 92 | ) 93 | ) 94 | ) 95 | } 96 | } 97 | 98 | @Composable 99 | fun WhiteCircleDraw(centerOffset: Offset = Offset.zero, circles: List) { 100 | Board(modifier = LayoutSize.Fill, onCanvas = { 101 | save() 102 | 103 | val parentSize = size 104 | 105 | val circleOffset = Offset(0f, parentSize.height.value / 2) + centerOffset 106 | 107 | for (index in 1 until circles.size) { 108 | val previousCircle = circles[index - 1] 109 | 110 | maskCircle( 111 | parentSize, 112 | previousCircle.radius, 113 | centerOffset 114 | ) 115 | 116 | overlayPaint.color = overlayColor.copy( 117 | alpha = previousCircle.alpha 118 | ) 119 | 120 | // Fill circle 121 | drawCircle( 122 | center = circleOffset, 123 | radius = circles[index].radius, 124 | paint = overlayPaint 125 | ) 126 | 127 | // Draw circle border 128 | drawCircle( 129 | center = circleOffset, 130 | radius = previousCircle.radius, 131 | paint = overlayBorderPaint 132 | ) 133 | } 134 | 135 | val lastCircle = circles.last() 136 | 137 | // Mask the area of the final circle 138 | maskCircle( 139 | parentSize, 140 | lastCircle.radius, 141 | centerOffset 142 | ) 143 | 144 | // Draw an overlay that fills the rest of the screen 145 | overlayPaint.color = overlayColor.copy( 146 | alpha = lastCircle.alpha 147 | ) 148 | drawRect( 149 | rect = Rect.fromLTWH(0f, 0f, parentSize.width.value, parentSize.height.value), 150 | paint = overlayPaint 151 | ) 152 | 153 | // Draw circle border 154 | drawCircle( 155 | center = circleOffset, 156 | radius = lastCircle.radius, 157 | paint = overlayBorderPaint 158 | ) 159 | 160 | restore() 161 | }) 162 | } 163 | 164 | // TODO: when update to dev07, remove this composable 165 | @Composable 166 | fun Image( 167 | image: Image, 168 | tint: Color? = null 169 | ) { 170 | with(DensityAmbient.current) { 171 | val imageModifier = ImagePainter(image).toModifier( 172 | scaleFit = ScaleFit.Fit, 173 | colorFilter = tint?.let { ColorFilter(it, BlendMode.srcIn) } 174 | ) 175 | Box(LayoutSize(image.width.toDp(), image.height.toDp()) + ClipModifier + imageModifier) 176 | } 177 | } 178 | 179 | private object ClipModifier : DrawModifier { 180 | override fun draw(density: Density, drawContent: () -> Unit, canvas: Canvas, size: PxSize) { 181 | canvas.save() 182 | canvas.clipRect(size.toRect()) 183 | drawContent() 184 | canvas.restore() 185 | } 186 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/composables/drawer.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.composables 2 | 3 | import androidx.compose.Composable 4 | import androidx.ui.core.Text 5 | import androidx.ui.foundation.Box 6 | import androidx.ui.foundation.Clickable 7 | import androidx.ui.foundation.ContentGravity 8 | import androidx.ui.foundation.Icon 9 | import androidx.ui.foundation.shape.RectangleShape 10 | import androidx.ui.graphics.Color 11 | import androidx.ui.layout.* 12 | import androidx.ui.res.vectorResource 13 | import androidx.ui.text.TextStyle 14 | import androidx.ui.text.style.TextAlign 15 | import androidx.ui.unit.dp 16 | import androidx.ui.unit.sp 17 | import br.com.programadorthi.compose.R 18 | import br.com.programadorthi.compose.controllers.DrawerController 19 | import br.com.programadorthi.compose.models.DrawerItem 20 | 21 | private val drawerWidth = 125.dp 22 | 23 | @Composable 24 | fun Drawer( 25 | controller: DrawerController, 26 | drawerItems: List, 27 | onItemClick: (DrawerItem) -> Unit 28 | ) { 29 | Box( 30 | modifier = LayoutWidth(drawerWidth) + LayoutHeight.Fill, 31 | backgroundColor = Color(0xAA234060), 32 | gravity = ContentGravity.Center, 33 | shape = RectangleShape 34 | ) { 35 | FlowColumn( 36 | crossAxisAlignment = FlowCrossAxisAlignment.Center, 37 | crossAxisSpacing = drawerWidth, 38 | mainAxisAlignment = FlowMainAxisAlignment.SpaceEvenly, 39 | mainAxisSize = SizeMode.Expand 40 | ) { 41 | // TODO: icon doesn't have properties to resize the icon 42 | Icon(icon = vectorResource(id = R.drawable.ic_baseline_refresh_24), tint = Color.White) 43 | for (item in drawerItems) { 44 | Clickable(onClick = { 45 | onItemClick(item) 46 | controller.close() 47 | }) { 48 | Text( 49 | text = item.withLineBreak(), 50 | style = TextStyle( 51 | color = Color.White, 52 | fontSize = 14.sp, 53 | textAlign = TextAlign.Center 54 | ) 55 | ) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/composables/forecast_content.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.composables 2 | 3 | import android.content.res.Resources 4 | import androidx.animation.TweenBuilder 5 | import androidx.compose.Composable 6 | import androidx.compose.onCommit 7 | import androidx.compose.remember 8 | import androidx.compose.state 9 | import androidx.ui.animation.animatedFloat 10 | import androidx.ui.core.Alignment 11 | import androidx.ui.core.Opacity 12 | import androidx.ui.core.Text 13 | import androidx.ui.foundation.Border 14 | import androidx.ui.foundation.Box 15 | import androidx.ui.foundation.ContentGravity 16 | import androidx.ui.foundation.Icon 17 | import androidx.ui.foundation.shape.corner.CircleShape 18 | import androidx.ui.graphics.Color 19 | import androidx.ui.layout.* 20 | import androidx.ui.res.vectorResource 21 | import androidx.ui.text.TextStyle 22 | import androidx.ui.unit.dp 23 | import androidx.ui.unit.sp 24 | import br.com.programadorthi.compose.helpers.* 25 | import br.com.programadorthi.compose.models.DrawerItem 26 | import br.com.programadorthi.compose.models.RadialItem 27 | import kotlin.math.PI 28 | import kotlin.math.cos 29 | import kotlin.math.sin 30 | 31 | 32 | private const val MAX_BOUND = 1f 33 | private const val MIN_BOUND = 0f 34 | 35 | private const val FIRST_ITEM_ANGLE = -PI / 3 36 | private const val LAST_ITEM_ANGLE = PI / 3 37 | private const val START_SLIDING_ANGLE = 3 * PI / 4 38 | 39 | private const val DELAY_INTERVAL = 0.1f 40 | private const val SLIDE_INTERVAL = 0.5f 41 | 42 | private const val REMEMBER_ID = "remember" 43 | 44 | private val fadeDuration = TweenBuilder().apply { 45 | duration = 150 46 | } 47 | 48 | private val slideDuration = TweenBuilder().apply { 49 | duration = 1500 50 | } 51 | 52 | private enum class RadialState { 53 | CLOSED, FADING_OUT, OPEN, SLIDING_OPEN 54 | } 55 | 56 | private val displayMetrics = Resources.getSystem().displayMetrics 57 | private val halfScreenWidth = displayMetrics.widthPixels / 4.6 58 | 59 | 60 | @Composable 61 | fun ForecastContent(item: DrawerItem, forecasts: List) { 62 | Stack { 63 | Background() 64 | RainParticles() 65 | Temperature() 66 | RadialForecastList(item, forecasts) 67 | } 68 | } 69 | 70 | @Composable 71 | fun RadialForecastList( 72 | item: DrawerItem, 73 | forecasts: List 74 | ) { 75 | val slideAnimation = animatedFloat(initVal = MIN_BOUND).apply { 76 | setBounds(min = MIN_BOUND, max = MAX_BOUND) 77 | } 78 | 79 | val fadeAnimation = animatedFloat(initVal = MAX_BOUND).apply { 80 | setBounds(min = MIN_BOUND, max = MAX_BOUND) 81 | } 82 | 83 | val radialState = state { RadialState.CLOSED } 84 | 85 | val angleDiffPerItem = 86 | (LAST_ITEM_ANGLE - FIRST_ITEM_ANGLE) / if (forecasts.size <= 1) 1 else forecasts.lastIndex 87 | 88 | val animations = remember(v1 = REMEMBER_ID, calculation = { 89 | forecasts.mapIndexed { index, _ -> 90 | val itemDelay = DELAY_INTERVAL * index 91 | val itemDuration = itemDelay + SLIDE_INTERVAL 92 | val endSlidingAngle = FIRST_ITEM_ANGLE + (angleDiffPerItem * index) 93 | 94 | return@mapIndexed TweenEasing( 95 | begin = START_SLIDING_ANGLE.toFloat(), 96 | end = endSlidingAngle.toFloat(), 97 | easing = IntervalEasing( 98 | begin = itemDelay, 99 | end = itemDuration, 100 | easing = easeInOut 101 | ) 102 | ) 103 | } 104 | }) 105 | 106 | onCommit(item) { 107 | radialState.value = RadialState.FADING_OUT 108 | fadeAnimation.animateTo(targetValue = MAX_BOUND, anim = fadeDuration, onEnd = { _, _ -> 109 | radialState.value = RadialState.CLOSED 110 | slideAnimation.snapTo(MIN_BOUND) 111 | fadeAnimation.snapTo(MIN_BOUND) 112 | radialState.value = RadialState.SLIDING_OPEN 113 | slideAnimation.animateTo( 114 | targetValue = MAX_BOUND, 115 | anim = slideDuration, 116 | onEnd = { _, _ -> 117 | radialState.value = RadialState.OPEN 118 | }) 119 | }) 120 | } 121 | 122 | Stack { 123 | forecasts.forEachIndexed { index, forecast -> 124 | Align(alignment = Alignment.CenterStart) { 125 | RadialPositioned(angle = animations[index].invoke(slideAnimation.value)) { 126 | Opacity( 127 | opacity = when (radialState.value) { 128 | RadialState.CLOSED -> MIN_BOUND 129 | RadialState.FADING_OUT -> MAX_BOUND - fadeAnimation.value 130 | else -> MAX_BOUND 131 | } 132 | ) { 133 | RadialForecast(forecast) 134 | } 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | @Composable 142 | fun RadialPositioned(angle: Float, children: @Composable() () -> Unit) { 143 | val dx = cos(angle) * halfScreenWidth 144 | val dy = sin(angle) * halfScreenWidth 145 | 146 | Box(modifier = LayoutOffset(dx.dp, dy.dp), children = children) 147 | } 148 | 149 | @Composable 150 | fun RadialForecast(radialItem: RadialItem) { 151 | Row { 152 | Box( 153 | modifier = LayoutSize(60.dp), 154 | backgroundColor = if (radialItem.selected) Color.White else Color.Transparent, 155 | border = if (radialItem.selected) null else Border(size = 2.dp, color = Color.White), 156 | gravity = ContentGravity.Center, 157 | shape = CircleShape 158 | ) { 159 | Icon( 160 | icon = vectorResource(id = radialItem.icon), 161 | tint = if (radialItem.selected) Color(0xFF6688CC) else Color.White 162 | ) 163 | } 164 | Column(modifier = LayoutPadding(start = 8.dp)) { 165 | Text( 166 | text = radialItem.title, 167 | style = TextStyle(color = Color.White, fontSize = 18.sp) 168 | ) 169 | Text( 170 | text = radialItem.subtitle, 171 | style = TextStyle(color = Color.White, fontSize = 16.sp) 172 | ) 173 | } 174 | } 175 | } 176 | 177 | @Composable 178 | fun Temperature() { 179 | Align(alignment = Alignment.CenterStart) { 180 | Text( 181 | modifier = LayoutFractionalOffset(0.05f, 1f), 182 | text = "68º", 183 | style = TextStyle(color = Color.White, fontSize = 80.sp) 184 | ) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/composables/rain_particles.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.composables 2 | 3 | import android.content.res.Resources 4 | import androidx.compose.Composable 5 | import androidx.ui.animation.Transition 6 | import androidx.ui.foundation.Canvas 7 | import androidx.ui.layout.LayoutSize 8 | import androidx.ui.res.imageResource 9 | import br.com.programadorthi.compose.R 10 | import br.com.programadorthi.compose.helpers.ParticleStates 11 | import br.com.programadorthi.compose.helpers.ParticlesAnimation 12 | import br.com.programadorthi.compose.helpers.ParticlesProgress 13 | import br.com.programadorthi.compose.models.ParticleSystem 14 | 15 | @Composable 16 | fun RainParticles() { 17 | val displayMetrics = Resources.getSystem().displayMetrics 18 | val screenHeight = displayMetrics.heightPixels 19 | val screenWidth = displayMetrics.widthPixels 20 | val particleSystem = ParticleSystem( 21 | startXPos = -screenWidth * 0.1f, 22 | startYPos = -screenHeight * 0.6f, 23 | endXPos = 0f, 24 | endYPos = screenHeight.toFloat(), 25 | xPosRange = screenWidth.toFloat(), 26 | yPosRange = screenWidth * 0.1f, 27 | minSpeed = 100f, 28 | speedRange = 100f, 29 | image = imageResource(id = R.drawable.rain), 30 | numParticles = 6 31 | ) 32 | Transition( 33 | definition = ParticlesAnimation, 34 | initState = ParticleStates.HIDE, 35 | toState = ParticleStates.SHOW 36 | ) { state -> 37 | Canvas(modifier = LayoutSize.Fill, onCanvas = { 38 | save() 39 | rotate(degrees = 8f) 40 | particleSystem.doDraw(this) 41 | particleSystem.updatePhysics(1f - state[ParticlesProgress]) 42 | restore() 43 | }) 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/controllers/DrawerController.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.controllers 2 | 3 | import androidx.animation.AnimatedFloat 4 | 5 | data class DrawerController( 6 | val animation: AnimatedFloat 7 | ) { 8 | val animationProgress: Float 9 | get() = animation.value 10 | 11 | fun close() { 12 | animation.animateTo(targetValue = 1f) 13 | } 14 | 15 | fun open() { 16 | animation.animateTo(targetValue = 0f) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/helpers/animations.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.helpers 2 | 3 | import androidx.animation.* 4 | 5 | enum class ParticleStates { 6 | HIDE, SHOW 7 | } 8 | 9 | val easeInOut = CubicBezierEasing(0.42f, 0f, 0.58f, 1f) 10 | 11 | val ParticlesProgress = FloatPropKey() 12 | 13 | val ParticlesAnimation = transitionDefinition { 14 | state(ParticleStates.HIDE) { 15 | this[ParticlesProgress] = 0f 16 | } 17 | 18 | state(ParticleStates.SHOW) { 19 | this[ParticlesProgress] = 1f 20 | } 21 | 22 | transition(fromState = ParticleStates.HIDE, toState = ParticleStates.SHOW) { 23 | ParticlesProgress using repeatable { 24 | iterations = Infinite 25 | 26 | animation = tween { 27 | easing = FastOutLinearInEasing 28 | duration = 300 29 | } 30 | } 31 | } 32 | } 33 | 34 | data class TweenEasing( 35 | private val begin: Float, 36 | private val end: Float, 37 | private val easing: Easing = LinearEasing 38 | ) : Easing { 39 | override fun invoke(fraction: Float): Float = 40 | begin + (end - begin) * easing.invoke(fraction) 41 | } 42 | 43 | data class IntervalEasing( 44 | private val begin: Float, 45 | private val end: Float, 46 | private val easing: Easing = LinearEasing 47 | ) : Easing { 48 | override fun invoke(fraction: Float): Float { 49 | val localFraction = ((fraction - begin) / (end - begin)).coerceIn(0f, 1f) 50 | return easing.invoke(localFraction) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/helpers/modifiers.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.helpers 2 | 3 | import androidx.ui.core.LayoutDirection 4 | import androidx.ui.core.LayoutModifier 5 | import androidx.ui.core.ModifierScope 6 | import androidx.ui.unit.Dp 7 | import androidx.ui.unit.IntPxPosition 8 | import androidx.ui.unit.IntPxSize 9 | 10 | // TODO: LayoutOffset already exists in dev07 11 | data class LayoutOffset(val dx: Dp, val dy: Dp) : LayoutModifier { 12 | override fun ModifierScope.modifyPosition( 13 | childSize: IntPxSize, 14 | containerSize: IntPxSize 15 | ): IntPxPosition = IntPxPosition( 16 | x = (if (layoutDirection == LayoutDirection.Ltr) dx else -dx).toIntPx(), 17 | y = dy.toIntPx() 18 | ) 19 | } 20 | 21 | data class LayoutFractionalOffset(val dx: Float, val dy: Float) : LayoutModifier { 22 | override fun ModifierScope.modifyPosition( 23 | childSize: IntPxSize, 24 | containerSize: IntPxSize 25 | ): IntPxPosition = IntPxPosition( 26 | x = childSize.width * dx * if (layoutDirection == LayoutDirection.Ltr) 1f else -1f, 27 | y = childSize.height * dy 28 | ) 29 | 30 | companion object { 31 | val Origin = LayoutFractionalOffset(0f, 0f) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/models/app_title_models.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.models 2 | 3 | import androidx.compose.Model 4 | 5 | @Model 6 | data class AppTitle( 7 | var topText: String = "", 8 | var bottomText: String = "" 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/models/drawer_models.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.models 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.* 5 | import kotlin.math.pow 6 | 7 | 8 | private val dayFormat = SimpleDateFormat("EEEE", Locale.US) 9 | private val monthFormat = SimpleDateFormat("MMMM", Locale.US) 10 | 11 | data class DrawerItem( 12 | val day: String, 13 | val dayName: String, 14 | val monthName: String 15 | ) { 16 | fun withComma() = "$dayName, $monthName $day" 17 | fun withLineBreak() = "$dayName\n$monthName $day" 18 | } 19 | 20 | fun drawerItems(): List { 21 | val items = mutableListOf() 22 | val calendar = Calendar.getInstance() 23 | var count = 1.0 24 | do { 25 | val dayNumber = calendar.get(Calendar.DAY_OF_MONTH) 26 | val item = DrawerItem( 27 | day = if (dayNumber < 10) "0$dayNumber" else "$dayNumber", 28 | dayName = dayFormat.format( 29 | calendar.time 30 | ), 31 | monthName = monthFormat.format( 32 | calendar.time 33 | ) 34 | ) 35 | calendar.add(Calendar.DAY_OF_MONTH, count.pow(0).toInt()) 36 | items.add(item) 37 | count++ 38 | } while (count <= 7) 39 | return items 40 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/models/forecast_models.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.models 2 | 3 | import androidx.annotation.DrawableRes 4 | import br.com.programadorthi.compose.R 5 | import java.text.SimpleDateFormat 6 | import java.util.* 7 | 8 | private val formatter = SimpleDateFormat("HH:mm", Locale.US) 9 | 10 | private fun time(increaseHourIn: Int = 0): String { 11 | val calendar = Calendar.getInstance().apply { 12 | add(Calendar.HOUR, increaseHourIn) 13 | } 14 | return formatter.format(calendar.time) 15 | } 16 | 17 | data class RadialItem( 18 | @DrawableRes val icon: Int, 19 | val title: String = "", 20 | val subtitle: String = "", 21 | val selected: Boolean = false 22 | ) 23 | 24 | fun forecasts() = listOf( 25 | RadialItem( 26 | icon = R.drawable.ic_baseline_wb_rainy_24, 27 | title = time(), 28 | subtitle = "Light Rain", 29 | selected = true 30 | ), 31 | RadialItem( 32 | icon = R.drawable.ic_baseline_wb_rainy_24, 33 | title = time(increaseHourIn = 1), 34 | subtitle = "Light Rain" 35 | ), 36 | RadialItem( 37 | icon = R.drawable.ic_baseline_wb_cloudy_24, 38 | title = time(increaseHourIn = 2), 39 | subtitle = "Cloudy" 40 | ), 41 | RadialItem( 42 | icon = R.drawable.ic_baseline_wb_sunny_24, 43 | title = time(increaseHourIn = 3), 44 | subtitle = "Sunny" 45 | ), 46 | RadialItem( 47 | icon = R.drawable.ic_baseline_wb_sunny_24, 48 | title = time(increaseHourIn = 4), 49 | subtitle = "Sunny" 50 | ) 51 | ) -------------------------------------------------------------------------------- /app/src/main/java/br/com/programadorthi/compose/models/particle_models.kt: -------------------------------------------------------------------------------- 1 | package br.com.programadorthi.compose.models 2 | 3 | import androidx.ui.geometry.Offset 4 | import androidx.ui.graphics.Canvas 5 | import androidx.ui.graphics.Image 6 | import androidx.ui.graphics.Paint 7 | import java.lang.Math.random 8 | 9 | 10 | private data class Particle( 11 | private val startXPos: Float, 12 | private val startYPos: Float, 13 | private val xPosRange: Float, 14 | private val yPosRange: Float, 15 | private val minSpeed: Float, 16 | private val speedRange: Float, 17 | private val image: Image 18 | ) { 19 | private var speed = minSpeed + random().toFloat() * speedRange 20 | var xPos: Float = startXPos + random().toFloat() * xPosRange 21 | private set 22 | 23 | var yPos: Float = startYPos + random().toFloat() * yPosRange 24 | private set 25 | 26 | fun updatePhysics(change: Float) { 27 | yPos += change * speed 28 | } 29 | 30 | fun onDraw(canvas: Canvas) { 31 | canvas.drawImage(image, Offset(xPos, yPos), Paint().apply { 32 | alpha = 0.3f 33 | }) 34 | } 35 | } 36 | 37 | class ParticleSystem( 38 | private val endYPos: Float, 39 | private val endXPos: Float, 40 | private val startYPos: Float, 41 | private val startXPos: Float, 42 | private val xPosRange: Float, 43 | private val yPosRange: Float, 44 | private val minSpeed: Float, 45 | private val speedRange: Float, 46 | private val image: Image, 47 | numParticles: Int 48 | ) { 49 | private val particles = mutableListOf() 50 | 51 | init { 52 | for (i in 0 until numParticles) { 53 | val particle = Particle( 54 | startXPos = startXPos, 55 | startYPos = startYPos, 56 | xPosRange = xPosRange, 57 | yPosRange = yPosRange, 58 | minSpeed = minSpeed, 59 | speedRange = speedRange, 60 | image = image 61 | ) 62 | particles.add(particle) 63 | } 64 | } 65 | 66 | fun doDraw(canvas: Canvas) { 67 | for (particle in particles) { 68 | particle.onDraw(canvas) 69 | } 70 | } 71 | 72 | fun updatePhysics(altDelta: Float) { 73 | particles.forEachIndexed { index, particle -> 74 | particle.updatePhysics(altDelta) 75 | 76 | if (particle.xPos !in startXPos..endXPos || particle.yPos !in startYPos..endYPos) { 77 | particles[index] = Particle( 78 | startXPos = startXPos, 79 | startYPos = startYPos, 80 | xPosRange = xPosRange, 81 | yPosRange = yPosRange, 82 | minSpeed = minSpeed, 83 | speedRange = speedRange, 84 | image = image 85 | ) 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /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/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/drawable/background.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_blur.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/drawable/background_blur.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_forward_ios_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_refresh_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_wb_cloudy_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_wb_rainy_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_wb_sunny_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /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/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/drawable/rain.png -------------------------------------------------------------------------------- /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.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorthi/compose-weather-forecast/02884a698153a7cd0b81df7472bee26ca2e92829/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Compose Started 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 15 | 16 |