This commit is contained in:
coco
2026-07-15 16:57:36 +08:00
parent 723ce1af5c
commit 95aa018b8b
314 changed files with 8910 additions and 0 deletions
@@ -0,0 +1,20 @@
<!---
This is the API of version 1.2.0
-->
# BackHandler
You can use BackHandler to detect the presses on the device back button inside of Compose
## Example
```kotlin
--8<-- "activity/backhandler/BackHandlerExample.kt:func"
```
<p align="left">
<img src ="{{ site.images }}/activity/backhandler/backhandler.png" height=100 width=200 style="border: 1px solid black;" />
</p>
## See also:
* [Official Docs](https://developer.android.com/reference/kotlin/androidx/activity/compose/package-summary#backhandler)
* [Full Example Code]({{ site.samplefolder }}/activity/backhandler/BackHandlerExample.kt)
@@ -0,0 +1,63 @@
<!---
This is the API of version 1.2.0
-->
# Crossfade
Crossfade can be used to switch between Composables with an crossfade animation.
<div>
<video height="500" align="center" controls>
<source src="{{ site.images }}/animation/crossfade/crossfadedemo.webm" type="video/webm" align="center">
</video>
</div>
Example video of the code below. On every button click, the content of the screen will change with an animation duration of 3 seconds.
```kotlin
enum class MyColors(val color: Color) {
Red(Color.Red), Green(Color.Green), Blue(Color.Blue)
}
@Composable
fun CrossfadeDemo() {
var currentColor by remember { mutableStateOf(MyColors.Red) }
Column {
Row {
MyColors.values().forEach { myColors ->
Button(
onClick = { currentColor = myColors },
Modifier.weight(1f, true)
.height(48.dp)
.background(myColors.color),
colors = ButtonDefaults.buttonColors(backgroundColor = myColors.color)
) {
Text(myColors.name)
}
}
}
Crossfade(targetState = currentColor, animationSpec = tween(3000)) { selectedColor ->
Box(modifier = Modifier.fillMaxSize().background(selectedColor.color))
}
}
}
```
## How does it work?
As you can see in the video above, this demo consists of a screen with 3 buttons at the top. On a button click, the screen below will change to the selected color.
```kotlin
Crossfade(targetState = currentColor, animationSpec = tween(3000)) { selectedColor ->
Box(modifier = Modifier.fillMaxSize().background(selectedColor.color))
}
```
Crossfade expects some kind of state to detect when it should recompose. In this example, this is **currentColor**, which is a state with the selected color enum. This state will be set to the **current** parameter.
With the **animationSpec** parameter, you can set which animation should be used to switch between the Composables. The default parameter here is the **tween** animation. You can choose between any class that implements **AnimationSpec**.
The **3000** in this example is the duration that the tween animation will have.
The last parameter is the body of the Crossfade Composable. Here you have to create the UI that you want to display. **selectedColor** is the current value of **currentColor**. In this example i'm using a Box to display the colors.
Everytime one of the 3 buttons is clicked, the value of currentColor will change and the Crossfade will recompose with an animation between the old and new UI.
## See also:
* [Full Example Code]({{ site.samplefolder }}/animation/crossfade/CrossfadeDemo.kt)
@@ -0,0 +1,70 @@
# Transistion
WORK IN PROGRESS
# Working with Transistion
<video width="320" height="240" controls>
<source src="{{ site.images }}/animation/transistion/rotation.mp4" type="video/webm">
Your browser does not support the video tag.
</video>
## What will we build?
This is might not be a spectacutlar use case for Transistion, but it should give an idea, how to use Transistion.
This is example consist of a **Text** which will print the active value of the Transition state and a padding which will change depending on this state.
The value of the Transistion state will change from 0 to 200 over and over again.
## Create a transistion definition
Transistion requires a TransitionDefinition, so let's build that first.
```kotlin
private val paddingTransitionDefinition = transitionDefinition {
...
}
```
Inside the TransitionDefinition, we have to define all possible transition states and which transition should be used between two transition states.
In this example we want to create transition between 0 and 200, so let's create two states for that.
```kotlin
private val paddingPropKey = FloatPropKey()
private val paddingTransitionDefinition = transitionDefinition {
state("A"){ this[paddingPropKey] = 0f }
state("B") { this[paddingPropKey] = 200f }
...
}
```
Inside **transitionDefinition** you have to use the **state()** to define a transition state. It requires a name and a function to initialize a state.
**paddingPropKey** is a **FloatPropKey**, it's like a state with a float value. It will be constantly updated with the latest value inside the transition.
Now we have two states. One with the Name **A** and a paddingPropKey value of **0f** and one with the Name **B** and a paddingPropKey value of **200f**. Next we have to define a transistion between **A** and **B**.
```kotlin
private val paddingPropKey = FloatPropKey()
private val paddingTransitionDefinition = transitionDefinition {
state("A"){ this[paddingPropKey] = 0f }
state("B") { this[paddingPropKey] = 200f }
transition("A" to "B") {
paddingPropKey using repeatable {
animation = tween {
duration = 1000
easing = FastOutLinearInEasing
}
iterations = Infinite
}
}
}
```
With
```kotlin
transition("A" to "B")
```
a transistion between state **A** and state **B** is definied. Inside the transition you have to define **how**, **how long** and **how often** the transition should happen.
paddingPropKey using repeatable
@@ -0,0 +1,8 @@
# Community
Slack: join the Kotlin Slack and the #compose channel
Bugtracker
https://issuetracker.google.com/issues/new?component=612128
Stackoverflow
@@ -0,0 +1,97 @@
# Compose for Android Developers
This page is inspired by https://flutter.dev/docs/get-started/flutter-for/android-devs.
The goal is to show how common use cases with the classic Android View system, can be done with Compose.
| Android View | Compose |
| ----------------------------------------- | ------------------------------------ |
| Button | [Button](../material/button.md) |
| TextView | [Text](../foundation/text.md) |
| EditText | [TextField](../material/textfield.md) |
| ImageView | [Image](../foundation/image.md) |
| LinearLayout(horizontally) | [Row](../layout/row.md) |
| LinearLayout(vertically) | [Column](../layout/column.md) |
| FrameLayout | [Box](../layout/box.md) |
| RecyclerView | [LazyColumn](../foundation/lazycolumn.md) |
| RecyclerView(horizontally) | [LazyRow](../foundation/lazyrow.md) |
| Snackbar | [Snackbar](../material/snackbar.md) |
## Layouts
### What is the equivalent of a LinearLayout?
In Android, a LinearLayout is used to lay your widgets out linearly—either horizontally or vertically. With Compose, use the [Row](https://foso.github.io/Jetpack-Compose-Playground/layout/row/) or [Column](https://foso.github.io/Jetpack-Compose-Playground/layout/column/) composable to achieve the same result.
If you notice the two code samples are identical with the exception of the “Row” and “Column” composable. The children are the same and this feature can be exploited to develop rich layouts that can change overtime with the same children.
```kotlin
@Composable
fun Example() {
Row {
Text("Hello World!")
Text("Hello World!2")
}
}
```
```kotlin
@Composable
fun Example() {
Column {
Text("Hello World!")
Text("Hello World!2")
}
}
```
### What is the equivalent of a RelativeLayout?
A RelativeLayout lays your widgets out relative to each other. In Compose, there are a few ways to achieve the same result.
You can achieve the result of a RelativeLayout by using a combination of Column, Row, and Stack widgets
### What is the equivalent of a ScrollView?
In Android, use a ScrollView to lay out your widgetsif the users device has a smaller screen than your content, it scrolls.
In Compose, you can use a **[LazyColumn](../foundation/lazycolumn.md)**
### What is the equivalent of a RecyclerView?
In Compose, you can use a **[LazyColumn](../foundation/lazycolumn/)** or **[LazyRow](../foundation/lazyrow/)**.
### What is the equivalent of wrap_content?
In the classic Android View system you use **wrap_content** to set the height/width of a View to the minimun needed value.
In Compose, you can set a Modifier:
#### Modifier.wrapContentWidth()
Android View equivalent -> android:layout_width="wrap_content"
#### Modifier.wrapContentHeight()
Android View equivalent ->android:layout_height="wrap_content"
#### Modifier.wrapContentSize()
Android View equivalent ->android:layout_height="wrap_content"
Android View equivalent -> android:layout_width="wrap_content"
```kotlin
@Composable
fun Example() {
Row {
Text("Text1",modifier = Modifier.wrapContentWidth())
Text("Text2",modifier = Modifier.wrapContentHeight())
}
}
```
## Working with Text
### What is the equivalent of a TextView?
In Compose you can use a **[Text](https://foso.github.io/Jetpack-Compose-Playground/foundation/text/)** to display text
### What is the equivalent of a EditText?
The EditText is the standard text entry view in the Android View system. If the user needs to enter text into an app, this is the primary way for them to do that.
In Compose you can use **[TextField](https://foso.github.io/Jetpack-Compose-Playground/material/textfield/)**
@@ -0,0 +1,66 @@
# Compose for SwiftUI Developers
| SwiftUI | Compose |
| ----------------------------------------- | ------------------------------------ |
| Button | [Button](../material/button.md) |
| Text | [Text](../foundation/text.md) |
| TextEditor | [TextField](../material/textfield.md) |
| Image | [Image](../foundation/image.md) |
| HStack | [Row](../layout/row.md) |
| VStack | [Column](../layout/column.md) |
| ZStack | [Box](../layout/box.md) |
| LazyVStack | [LazyColumn](../foundation/lazycolumn.md) |
| LazyHStack | [LazyRow](../foundation/lazyrow.md) |
| ScrollView | [LazyColumn](../foundation/lazycolumn.md) |
## What is the equivalent of a View?
In "Jetpack Compose" Views are called **Composable**. They are Kotlin functions that are annotated with @Composable.
```kotlin
@Composable fun ComposableDemo(){
Text("Hello, World")
}
```
## How to define a state?
In SwiftUI you can use the **@State** property wrapper to create a state.
```swift
//SwiftUI
@State var isActiveState = false
```
In Compose you use **mutableStateOf()**
```kotlin
//Compose
var isActiveState = mutableStateOf(false)
```
But this will only create the state. When you want to remember the value of your state in every recomposition, you have to put your state inside **remember**
```kotlin
//Compose
val isActiveState = remember { mutableStateOf(false) }
```
## What is the equivalent of a ViewModifier?
```swift
//SwiftUI
struct ContentView: View {
var body : some View {
Text("Hello, World!").background(Color.Red).padding(100)
}
}
```
In Compose a ViewModifier is called [Modifier](../general/modifier/). You can't directly append it to a View, you need to apply your modifier as a parameter to a Composable that expects a modifier.
```kotlin
//Compose
Text("Hello, World", modifier = Modifier.background(Color.Red).padding(100.dp))
```
## What is the equivalent of EnvironmentObject?
For data that should be shared with all views in your entire app, in SwiftUI you can use EnvironmentObject. In Compose this can be done with [CompositionLocal](/general/compositionlocal/)
@@ -0,0 +1,56 @@
# Compose Projects
A list of projects that are related to Jetpack Compose. If you want to add an entry, please edit the table and send PR.
# Example Apps
| Name | Description |
| ----------------------------------------- | ------------------------------------ |
| [Jetpack Compose Playground](https://github.com/Foso/Jetpack-Compose-Playground) | Collection of Jetpack Compose example code :rocket: . |
| [Compose Pokedex](https://github.com/zsoltk/compose-pokedex) | Pokedex on Jetpack Compose. |
| [Compose Samples Repository](https://github.com/android/compose-samples) | This repository contains a set of individual Android Studio projects to help you learn about Compose in Android. |
| [PeopleInSpace](https://github.com/joreilly/PeopleInSpace) | Minimal Kotlin Multiplatform project using Jetpack Compose and SwiftUI|
| [ComposeClock](https://github.com/adibfara/composeclock) | Particle clock created with Jetpack Compose framework |
| [JetDelivery](https://github.com/vipulasri/JetDelivery) | JetDelivery is a sample food delivery app, built with Jetpack Compose.|
| [Learn Jetpack Compose By Example](https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example) | This project contains various examples that show how you would do things the "Jetpack Compose" way|
| [Full Compose Cookbook with Demo UIs](https://github.com/Gurupreet/ComposeCookBook) | This project showcase all UI, Widgets, Animations and Demo UI samples.
| [JetInstagram](https://github.com/vipulasri/JetInstagram) | An Instagram UI clone app built with "Jetpack Compose" with Like Button Animation and instagram reels feature with exoplayer.
| [DisneyCompose](https://github.com/skydoves/DisneyCompose) | A demo Disney app using compose and Hilt based on modern Android tech-stacks and MVVM architecture. Fetching data from the network and integrating persisted data in the database via repository pattern. Declarative UI version of the DisneyMotions using compose.
| [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform) | Compose Kotlin UI framework port for desktop platforms (macOS, Linux, Windows), components outside of the core.
| [Compose Slack Desktop](https://github.com/vipulasri/ComposeSlackDesktop) | A Slack demo app for desktop using Jetpack Compose UI toolkit
| [FlappyBird](https://github.com/Nthily/FlappyBird) | FlappyBird made with Jetpack Compose.
| [JetSpotify](https://github.com/sunny52525/JetSpotify) | This is a Spotify App made in Jetpack Compose having Spotify Android UI and functionality using Spotify SDK and web API.
| [JetPic Express](https://github.com/la-colinares/JetPicExpress) | A simple photo editing app that allows you to apply stunning filters and share it to the world. This app was built using the latest Jetpack Compose UI for Modern Native Android UI development.
| [CoolWheatherApp](https://github.com/markoeltiger/CoolWheatherApp) | A cool modern weather application build with (JetpackCompose UI , coroutine,retrofit , MVVM Architecture , LiveData)
# Libraries
| Name | Description |
| ----------------------------------------- | ------------------------------------ |
| [accompanist](https://github.com/chrisbanes/accompanist/) | A collection of extension libraries for Jetpack Compose |
| [Showkase](https://github.com/airbnb/Showkase) | Showkase is an annotation-processor based Android library that helps you organize, discover, search and visualize Jetpack Compose UI elements |
| [Decompose](https://github.com/arkivanov/Decompose) | Lifecycle-aware components for Jetpack Compose with routing functionality |
| [Compose Router](https://github.com/zsoltk/compose-router) | Routing functionality for Jetpack Compose with back stack |
| [Compose Glide Image](https://github.com/mvarnagiris/compose-glide-image) | Simple Glide library adaptation for Jetpack Compose. |
| [Compose Navigation](https://github.com/mvarnagiris/compose-navigation) | |
| [Compose Backstack](https://github.com/zach-klippenstein/compose-backstack) | Simple composable for rendering transitions between backstacks.|
| [SwipeReveal-Compose](https://github.com/kacmacuna/SwipeReveal-Compose) | A layout that you can swipe to show action buttons.|
| [Exploding Composable](https://github.com/omkar-tenkale/ExplodingComposable) | Explosive dust effect animation for your composables!|
| [Landscapist](https://github.com/skydoves/landscapist) | 🍂 Jetpack Compose image loading library which can fetch and display network images using Glide, Coil, and Fresco.|
# Websites
| Name | Description |
| ----------------------------------------- | ------------------------------------ |
| [Compose.Academy](https://compose.academy/) | |
| [Jetpackcompose.app](https://Jetpackcompose.app) | |
| [Jetpack Compose 中文文档](https://docs.compose.net.cn/) |
| [JetpackComposeVersion.com](https://www.jetpackcomposeversion.com/) | A page to quickly check what the latest version of Jetpack Compose is and its dependencies. |
| [Composables.co](https://www.composables.co) | Articles & resources for Jetpack Compose |
# Compose for Web
| Name | Description |
| ----------------------------------------- | ------------------------------------ |
| [HtmlToComposeWebConverter](https://github.com/Foso/HtmlToComposeWebConverter) | Intellij Idea Plugin that can convert HTML to Compose for Web code. https://plugins.jetbrains.com/plugin/18261-html-to-compose-web-converter
| [Compose-Snake-Web](https://github.com/Foso/compose-snake-web) | This is a Compose for Web port of CompoSnake.
@@ -0,0 +1,36 @@
# Contributing
## to Jetpack Compose
If you want to improve Compose, join the Kotlin Slack and the **#compose** channel or file a bug at https://issuetracker.google.com/issues?q=componentid:612128
## to this project
This project is using [MkDocs](https://www.mkdocs.org/) and the [MkDocs-Material Theme](https://squidfunk.github.io/mkdocs-material/) to generate the pages for Github.
The markdown files are located in [/docs](https://github.com/Foso/Jetpack-Compose-Playground/tree/master/docs).
The generated files for the GitHub page are in [/site](https://github.com/Foso/Jetpack-Compose-Playground/tree/master/.github/workflows/gh-pages.yml#L32). Do not make changes in this folder, they will be overridden.
* Install plugins
```
pip3 install mkdocs-minify-plugin
pip3 install mkdocs-git-revision-date-localized-plugin
pip3 install mkdocs-minify-plugin
pip3 install mkdocs-macros-plugin
```
* Run docs locally
To start the mkdocs server locally, run **mkdocs serve** in a terminal in the project folder.
* Add/Change docs
The docs are written in markdown files which are all in [/docs](https://github.com/Foso/Jetpack-Compose-Playground/tree/master/docs). To change the navigation sidebar, you need to edit the **mkdocs.yml**.
* Build the docs
When you run **mkdocs build** in a terminal in the project folder, the html files be generated to [/site](https://github.com/Foso/Jetpack-Compose-Playground/tree/master/docs).
When deployed, these files are built for GitHub Pages using a [workflow](https://github.com/Foso/Jetpack-Compose-Playground/actions).
Feel free to change/add files and send a pull request.
@@ -0,0 +1,14 @@
# How to detect dark mode
Inside your Composable you can use **isSystemInDarkTheme** to detect if the device is running in dark mode.
```kotlin
val dark = isSystemInDarkTheme()
```
-------------
## See also:
* [Official Docs](https://developer.android.com/reference/kotlin/androidx/compose/foundation/package-summary#issystemindarktheme)
* [Learn-Jetpack-Compose-By-Example/DarkModeActivity](https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example/blob/master/app/src/main/java/com/example/jetpackcompose/theme/DarkModeActivity.kt)
@@ -0,0 +1,16 @@
<!---
This is the API of version 1.2.0
-->
You can use LocalContext.current to receive the context of your Android App inside a Compose Function
```kotlin
@Composable
fun AndroidContextComposeDemo() {
val context = LocalContext.current
Text(text = "Read this string from Context: "+context.getString(R.string.app_name))
}
```
## See also:
* [Full Example Code]({{ site.samplefolder }}/other/AndroidContextComposeDemo.kt)
@@ -0,0 +1,84 @@
# How to show hint with underline in a TextField
!!! info
This is the API of version dev08. Newer versions may have a different one
We have all used EditText in classic Android development. It has lots of features like hint showing or default underline background. In Jetpack compose it's name is TextField
and we set up it like that.
## Create TextField
```kotlin
val state = state { "" }
TextField(
value = state.value,
modifier = modifier,
onValueChange = { state.value = it },
textStyle = yourTextStyle
)
```
With textStyle parameter you can change your TextField's font, font size, color and many other things, but there are something that its missing, you cant quite set its PlaceHolder
text(Hint). So how can we do that? If you think about that hint's only purpose is to be shown when TextField is empty and be hidden when we are starting to type something in TextField.
So we need two views one Text for hint showing and one TextField. They must have same location on the screen so it will look like same component. So without further ado let's start
implementing it.
## Create HintTextField
```kotlin
@Composable
fun HintEditText(
hintText: String = "",
modifier: Modifier = Modifier.None,
textStyle: TextStyle = currentTextStyle()
) {
val state = state { "" }
val inputField = @Composable {
TextField(
value = state.value,
modifier = modifier,
onValueChange = { state.value = it },
textStyle = textStyle.merge(TextStyle(textDecoration = TextDecoration.None))
)
}
Layout(
children = @Composable {
inputField()
Text(
text = hintText,
modifier = modifier,
style = textStyle.merge(TextStyle(color = Color.Gray))
)
Divider(color = Color.Black, height = 2.dp)
},
measureBlock = { measurables: List<Measurable>, constraints: Constraints, _ ->
val inputFieldPlace = measurables[0].measure(constraints)
val hintEditPlace = measurables[1].measure(constraints)
val dividerEditPlace = measurables[2].measure(
Constraints(constraints.minWidth, constraints.maxWidth, 2.ipx, 2.ipx)
)
layout(
inputFieldPlace.width,
inputFieldPlace.height + dividerEditPlace.height
) {
inputFieldPlace.place(0.ipx, 0.ipx)
if (state.value.isEmpty())
hintEditPlace.place(0.ipx, 0.ipx)
dividerEditPlace.place(0.ipx, inputFieldPlace.height)
}
})
}
```
Seems like lots of code for simple functional, doesnt it? Lets describe whats happening from the function declaration. It has three function parameters and they are pretty
self-explanatory. As second part we are creating TextField lambda and passing it in Layout Composable function, with our HintText and Divider, which as you might have guessed
will create underline background. Now it's time to decide where we are going to put our HintTextField.
First of all we need to measure our views and **measureBlock** will help us do that.
Measurables list will contain three items(Our TextField, HintText and Divider) as Measurable type, we need to call measure on all of list items and pass constraints in it. Constraints
is a class which only has four properties(minWidth, maxWidth, minHeight, maxHeight). We can pass constraints which is given to us by measuring @Composable children lambda, which works
with our TextField and HintText because we want them to have same size as it's parent, but when it comes to underline background we need it to stay as thin as possible.
Now we are measuring our HintTextField one more time and placing it's children on some (x, y).
One last thing to realize is that if our state value is not empty we dont place
our HintText at all!
@@ -0,0 +1,83 @@
## How to draw a custom shape?
!!! info
This is the API of version 1.2.0. Newer versions may have a different one
<p align="center">
<img src ="../../images/foundation/shape/triangleshape.png" />
</p>
### GenericShape
You can create custom shapes. One way to do it, is to use **GenericShape**. Let's see how the triangle is drawn.
```kotlin
private val TriangleShape = GenericShape { size, _ ->
// 1)
moveTo(size.width / 2f, 0f)
// 2)
lineTo(size.width, size.height)
// 3)
lineTo(0f, size.height)
}
```
Inside the GenericShape you can draw your custom shape.
You have access to the **size**-object. This is size of the Composable that the shape is applied to.
You can get the height with **size.height** and the width with **size.width**
1) Initially the painter will start at the top left of the parent composable(0x,0y).
With **moveTo()** you can set the coordinates of the painter. Here the coordinates will be set to the half width of the parent layout
and a 0y coordinate.
2) This will draw a line from the painter coordinates, which were set in **1)**, to the bottom right corner of the parent layout.
The painter coordinates are then automatically set to this corner.
3) This will draw a line to the bottom left corner. GenericShape will implicitly execute the **close()**-function. **close()** will draw a line from the last painter coordinates to the first definied.
### Extend the Shape interface
```kotlin
/**
* Defines a generic shape.
*/
interface Shape {
/**
/**
* Creates [Outline] of this shape for the given [size].
*
* @param size the size of the shape boundary.
* @param density the current density of the screen.
*
* @return [Outline] of this shape for the given [size].
*/
fun createOutline(size: Size, density: Density): Outline
}
```
You can extend the Shape interface to create your own implementation of Shape. Inside **createOutline** you get the size of the Composable, which the shape is applied to and the density of the screen.
You have to return an instance of **Outline**. Outline is a sealed class with the following subclasses:
* Rectangle(val rect: Rect)
* Rounded(val rrect: RRect)
* Generic(val path: Path)
Take a look at the GenericShape example when you want to understand, how the drawing of a custom shape works.
```kotlin
class CustomShape : Shape {
override fun createOutline(size: Size, density: Density): Outline {
val path = Path().apply {
moveTo(size.width / 2f, 0f)
lineTo(size.width, size.height)
lineTo(0f, size.height)
close()
}
return Outline.Generic(path)
}
}
```
@@ -0,0 +1,54 @@
# How to create your own Modifier
!!! info
This is the API of version alpha01. Newer versions may have a different one
Compose is using Modifiers to set certain aspects of a Composable like backgrounds, size or click events.
Compose already comes with
In this example i want to explain how you can create your own Modifier.
We will create a Modifier that scales the size of the Composable where it's applied to.
To create a Modifier you need to create a class that implements Modifier.Element.
```kotlin
/**
* A [Modifier.Element] that changes how its wrapped content is measured and laid out.
* It has the same measurement and layout functionality as the [androidx.compose.ui.Layout]
* component, while wrapping exactly one layout due to it being a modifier. In contrast,
* the [androidx.compose.ui.Layout] component is used to define the layout behavior of
* multiple children.
*
* @see androidx.compose.ui.Layout
*/
interface LayoutModifier : Modifier.Element {
```
```kotlin
class ScaleSizeModifier(val scale: Int) : LayoutModifier {
//1
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureScope.MeasureResult {
val pl = measurable.measure(constraints)
measurable.measure(
Constraints(
pl.width * scale,
pl.width * scale,
pl.height * scale,
pl.height * scale
)
)
return layout(pl.width, pl.height) {
pl.place(0, 0)
}
}
}
```
@@ -0,0 +1,3 @@
# How to make a Composable invisible?
When you want to set a Composable to invisible, you can use the **alpha** modifier. **alpha(0f)** will make it invisible.
@@ -0,0 +1,3 @@
# How to use an Android View in Compose
See [AndroidView](../viewinterop/androidview.md)
@@ -0,0 +1,3 @@
# How to use Compose in a ViewGroup
See [ComposeView](../platform/composeview.md)
@@ -0,0 +1,36 @@
<!---
This is the API of version 1.2.0
-->
# How to load an image
## Load Image
You can use **painterResource** to load an image from the resources
```kotlin
@Composable
fun ImageResourceDemo() {
val image: Painter = painterResource(id = R.drawable.composelogo)
Image(painter = image,contentDescription = "")
}
```
Or load an Icon from Material Icons
```kotlin
@Composable
fun ImageResourceDemo() {
Icon(Icons.Rounded.Home,contentDescription = "")
}
```
Remember to add dependencies to build.gradle
```
implementation "androidx.compose.material:material-icons-extended:$compose_version"
```
<hr>
## See also:
* [Full Example Code]({{ site.samplefolder }}/foundation/ImageResourceDemo.kt)
@@ -0,0 +1,10 @@
# Cookbook
* [Handle changes to a text field](./textfield_changes.md)
* [How to use Compose in a ViewGroup](./how_to_use_compose_in_viewgroup.md)
* [How to create HintTextField](./hint_edit_text.md)
* [How to load an Image](./loadimage.md)
* [How to use an Android View in Compose](./how_to_use_an_android_view_in_compose.md)
* [How to get Android Context](./get_android_context.md)
* [How to detect dark mode](./detect_darkmode.md)
* [How to make a Composable invisible?](./how_to_make_composable_invisible.md)
@@ -0,0 +1,35 @@
<!---
This is the API of version 1.2.0
-->
# Handle changes to a TextField
In some cases, its useful to get the value of a textfield every time the text in a text field changes. For example, you might want to build a search screen with autocomplete functionality where you want to update the results as the user types.
Here is an example how you can do it with Compose:
<p align="left">
<img src ="../../images/TextFieldDemo.png" />
</p>
```kotlin
@Composable
fun TextFieldDemo() {
Column(Modifier.padding(16.dp)) {
val textState = remember { mutableStateOf(TextFieldValue()) }
TextField(
value = textState.value,
onValueChange = { textState.value = it }
)
Text("The textfield has this text: " + textState.value.text)
}
}
```
The simplest approach is to supply an onValueChange() callback to a TextField. Whenever the text changes, the callback is invoked.
In this example, every time the TextField changes, the new text value will be saved in a state and set to the TextField and the Text.
## See also:
* [Full Example Code]({{ site.samplefolder }}/material/textfield/TextFieldDemo.kt)
@@ -0,0 +1,5 @@
# Image
WIP
You can find the official docs here: https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Image_And_Icons_Manipulations
@@ -0,0 +1,5 @@
# Getting Started
WIP
You can find the official docs here: https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Getting_Started
@@ -0,0 +1,5 @@
# Keyboard
WIP
You can find the official docs here: https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Keyboard
@@ -0,0 +1,5 @@
# Mouse
WIP
You can find the official docs here: https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Mouse_Events
@@ -0,0 +1,5 @@
# Scrollbar
WIP
You can find the official docs here: https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Desktop_Components#scrollbars
@@ -0,0 +1,5 @@
# Window
WIP
You can find the official docs here: https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Window_API_new
@@ -0,0 +1,8 @@
# Compose on Desktop
JetBrains released a Compose port for the desktop.
"Compose for Desktop targets the JVM, and supports high-performance, hardware-accelerated UI rendering on all major desktop platforms (macOS, Windows, and Linux/x64) by leveraging the powerful native Skia graphics library."
You can find more information here https://www.jetbrains.com/lp/compose/ or on their Github Repo https://github.com/JetBrains/compose-multiplatform or in the Kotlin Slack [#compose-desktop](https://kotlinlang.slack.com/archives/C01D6HTPATV)
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Mgf_9kxM1BA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
@@ -0,0 +1,19 @@
<!---
This is the API of version 1.2.0
-->
# BasicTextField
BasicTextField can be used to insert text. See [TextField](../material/textfield.md) for a material version.
<p align="center">
<img src ="{{ site.images }}/foundation/basictextfield/basictextfield.png" />
</p>
```kotlin
--8<-- "foundation/basictextfield/BasicTextFieldDemo.kt:func"
```
-------------
## See also:
* [Full Example Code]({{ site.samplefolder }}/foundation/basictextfield/BasicTextFieldDemo.kt)
@@ -0,0 +1,39 @@
<!---
This is the API of version 1.2.0
-->
# Canvas
A Canvas is a Composable that allows you to draw inside of it.
## How to draw on Canvas
<p align="left">
<img src ="{{ site.images }}/foundation/canvas/CanvasDrawExample.png" height=100 width=300 />
</p>
```kotlin
@Preview(showBackground = true)
@Composable
fun CanvasDrawExample() {
Canvas(modifier = Modifier.fillMaxSize()) {
drawRect(Color.Blue, topLeft = Offset(0f, 0f), size = Size(this.size.width, 55f))
drawCircle(Color.Red, center = Offset(50f, 200f), radius = 40f)
drawLine(
Color.Green, Offset(20f, 0f),
Offset(200f, 200f), strokeWidth = 5f
)
drawArc(
Color.Black,
0f,
60f,
useCenter = true,
size = Size(300f, 300f),
topLeft = Offset(60f, 60f)
)
}
}
```
## See also:
* [Full Example Code]({{ site.samplefolder }}/foundation/CanvasDrawExample.kt)
@@ -0,0 +1,33 @@
<!---
This is the API of version 1.2.0
-->
# Image
Image is used to display Images. It's similar to an ImageView in the classic Android View system.
<p align="left">
<img src ="{{ site.images }}/foundation/image/imagedemo.png" height=100 width=300 />
</p>
## Load Image
You can use **painterResource** to load an image from the resources
```kotlin
@Composable
fun ImageResourceDemo() {
val image: Painter = painterResource(id = R.drawable.composelogo)
Image(painter = image,contentDescription = "")
}
```
<hr>
## See also:
* [Full Example Code]({{ site.samplefolder }}/foundation/ImageResourceDemo.kt)
@@ -0,0 +1,53 @@
<!---
This is the API of version 1.2.0
-->
# BoxWithConstraints
BoxWithConstraints is a layout similar to the [Box](../../../layout/box) layout, but it has the advantage that you can get the minimum/maximum available width and height for the Composable on the screen.
You can use it to show a different content depending on the available space.
Inside the scope of BoxWithConstraints you have access to the BoxWithConstraintsScope. With it you can get the **minWidth**, **maxWidth**, **minHeight**, **maxHeight** in dp and **constraints** in pixels.
## Example:
<p align="center">
<img src ="../{{ site.images }}/foundation/layout/boxwithconstraints/boxwithconstraints.png" height=100 width=300 />
</p>
```kotlin
@Composable
fun BoxWithConstraintsDemo() {
Column {
Column {
MyBoxWithConstraintsDemo()
}
Text("Here we set the size to 150.dp", modifier = Modifier.padding(top = 20.dp))
Column(modifier = Modifier.size(150.dp)) {
MyBoxWithConstraintsDemo()
}
}
}
@Composable
private fun MyBoxWithConstraintsDemo() {
BoxWithConstraints {
val boxWithConstraintsScope = this
//You can use this scope to get the minWidth, maxWidth, minHeight, maxHeight in dp and constraints
Column {
if (boxWithConstraintsScope.maxHeight >= 200.dp) {
Text(
"This is only visible when the maxHeight is >= 200.dp",
style = TextStyle(fontSize = 20.sp)
)
}
Text("minHeight: ${boxWithConstraintsScope.minHeight}, maxHeight: ${boxWithConstraintsScope.maxHeight}, minWidth: ${boxWithConstraintsScope.minWidth} maxWidth: ${boxWithConstraintsScope.maxWidth}")
}
}
}
```
## See also:
* [Official Docs]({{ site.composedoc }}/foundation/layout/package-summary#BoxWithConstraints(androidx.compose.ui.Modifier,androidx.compose.ui.Alignment,kotlin.Boolean,kotlin.Function1))
* [Full Example Code]({{ site.samplefolder }}/foundation/layout/BoxWithConstraintsExample.kt)
@@ -0,0 +1,28 @@
<!---
This is the API of version 1.2.0
-->
# Spacer
Spacer is a Composable that can be used when you want to add an additional space between Composables
<p align="center">
<img src ="../{{ site.images }}/foundation/layout/spacer/spacer.png" height=50 width=50 style="border: 1px solid black;" />
</p>
## Example
```kotlin
@Composable
fun SpacerDemo() {
Column {
Text("Hello")
Spacer(modifier = Modifier.size(30.dp))
Text("World")
}
}
```
## See also:
* [Full Example Code]({{ site.samplefolder }}/foundation/layout/SpacerDemo.kt)
@@ -0,0 +1,50 @@
<!---
This is the API of version 1.2.0
-->
# LazyColumn
A [LazyColumn](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary#lazycolumn) is a vertically scrolling list that only composes and lays out the currently visible items.
It's similar to a Recyclerview in the classic Android View system.
<p align="left">
<img src ="{{ site.images }}/foundation/lazycolumnitems.png" height=100 width=300 />
</p>
```kotlin
@Composable
fun LazyColumnDemo() {
val list = listOf(
"A", "B", "C", "D"
) + ((0..100).map { it.toString() })
LazyColumn(modifier = Modifier.fillMaxHeight()) {
items(items = list, itemContent = { item ->
Log.d("COMPOSE", "This get rendered $item")
when (item) {
"A" -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
"B" -> {
Button(onClick = {}) {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
"C" -> {
//Do Nothing
}
"D" -> {
Text(text = item)
}
else -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
})
}
}
```
## See also:
* [Official Docs]({{ site.composedoc }}/foundation/lazy/package-summary#lazycolumn)
* [Full Example Code]({{ site.samplefolder }}/foundation/LazyColumnDemo.kt)
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/1ANt65eoNhQ" title="Lazy layouts in Compose" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
@@ -0,0 +1,54 @@
<!---
This is the API of version 1.2.0
-->
# LazyRow
A [LazyRow]({{ site.composedoc }}/foundation/lazy/package-summary#lazyrow) is a horizontal scrolling list that only composes and lays out the currently visible items.
It's similar to a horizontal Recyclerview in the classic Android View system.
<p align="left">
<img src ="{{ site.images }}/foundation/lazyrow/lazyrow.png" height=100 width=300 />
</p>
```kotlin
@Composable
fun LazyRowDemo() {
val list = listOf(
"A", "B", "C", "D"
) + ((0..100).map { it.toString() })
LazyRow(modifier = Modifier.fillMaxHeight()) {
items(items = list, itemContent = { item ->
Log.d("COMPOSE", "This get rendered $item")
when (item) {
"A" -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
"B" -> {
Button(onClick = {}) {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
"C" -> {
//Do Nothing
}
"D" -> {
Text(text = item)
}
else -> {
Text(text = item, style = TextStyle(fontSize = 80.sp))
}
}
})
}
}
```
## See also:
* [Official Docs]({{ site.composedoc }}/foundation/lazy/package-summary#lazyrow)
* [Full Example Code]({{ site.samplefolder }}/foundation/LazyRowDemo.kt)
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/1ANt65eoNhQ" title="Lazy layouts in Compose" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
[Lazy and amazy Lazy layouts in Compose](https://www.droidcon.com/2022/08/01/lazy-and-amazy-lazy-layouts-in-compose/)
@@ -0,0 +1,69 @@
<!---
This is the API of version 1.2.0
-->
# LazyVerticalGrid
Jetpack Compose provides an API for displaying grid or grid elements.
# Example
To arrange list items in a grid, ``LazyVerticalGrid`` provides a cells parameter that controls how cells are formed into columns.
The following example displays the items in a grid, using ``GridCells.Adaptive`` to set the width of each column at least 128.dp:
<p align="left">
<img src ="{{ site.images }}/foundation/lazyverticalgrid/lazyverticalgrid.png" height=100 width=300 />
</p>
```kotlin
@Composable
fun LazyVerticalGridDemo(){
val list = (1..10).map { it.toString() }
LazyVerticalGrid(
columns = GridCells.Adaptive(128.dp),
// content padding
contentPadding = PaddingValues(
start = 12.dp,
top = 16.dp,
end = 12.dp,
bottom = 16.dp
),
content = {
items(list.size) { index ->
Card(
backgroundColor = Color.Red,
modifier = Modifier
.padding(4.dp)
.fillMaxWidth(),
elevation = 8.dp,
) {
Text(
text = list[index],
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
color = Color(0xFFFFFFFF),
textAlign = TextAlign.Center,
modifier = Modifier.padding(16.dp)
)
}
}
}
)
}
```
Apart from ``GridCells.Adaptive`` there are other types of cells that provide the number of columns per row. As follows
```
colums = GridCells.Fixed(2)
```
The above code will display 2 columns in 1 row.
## See also:
* [Official Docs]({{ site.composedoc }}/foundation/lazy/package-summary#LazyVerticalGrid)
* [Full Example Code]({{ site.samplefolder }}/foundation/LazyVerticalGridDemo.kt)
@@ -0,0 +1,103 @@
<!---
This is the API of version 1.2.0
-->
# Shape
A Shape can be used to draw a Composable in specific shape.
## RectangleShape
<p align="center">
<img src ="{{ site.images }}/foundation/shape/rectangleshape.png" />
</p>
A shape describing the rectangle.
```kotlin
@Composable
fun RectangleShapeDemo(){
ExampleBox(shape = RectangleShape)
}
@Composable
fun ExampleBox(shape: Shape){
Column(modifier = Modifier.fillMaxWidth().wrapContentSize(Alignment.Center)) {
Box(
modifier = Modifier.size(100.dp).clip(shape).background(Color.Red)
)
}
}
```
## CircleShape
<p align="center">
<img src ="{{ site.images }}/foundation/shape/circleshape.png" />
</p>
Circular Shape with all the corners sized as the 50 percent of the shape size.
```kotlin
@Composable
fun CircleShapeDemo(){
ExampleBox(shape = CircleShape)
}
@Composable
fun ExampleBox(shape: Shape){
Column(modifier = Modifier.fillMaxWidth().wrapContentSize(Alignment.Center)) {
Box(
modifier = Modifier.size(100.dp).clip(shape).background(Color.Red)
)
}
}
```
## RoundedCornerShape
<p align="center">
<img src ="{{ site.images }}/foundation/shape/roundedcornershape.png" />
</p>
A shape describing the rectangle with rounded corners.
```kotlin
@Composable
fun RoundedCornerShapeDemo(){
ExampleBox(shape = RoundedCornerShape(10.dp))
}
@Composable
fun ExampleBox(shape: Shape){
Column(modifier = Modifier.fillMaxWidth().wrapContentSize(Alignment.Center)) {
Box(
modifier = Modifier.size(100.dp).clip(shape).background(Color.Red)
)
}
}
```
## CutCornerShape
<p align="center">
<img src ="{{ site.images }}/foundation/shape/cutcornershape.png" />
</p>
A shape describing the rectangle with cut corners.
```kotlin
@Composable
fun CutCornerShapeDemo(){
ExampleBox(shape = CutCornerShape(10.dp))
}
@Composable
fun ExampleBox(shape: Shape){
Column(modifier = Modifier.fillMaxWidth().wrapContentSize(Alignment.Center)) {
Box(
modifier = Modifier.size(100.dp).clip(shape).background(Color.Red)
)
}
}
```
## How to draw a custom shape?
[How to create a custom shape](../cookbook/how_to_create_custom_shape.md)
@@ -0,0 +1,125 @@
<!---
This is the API of version 1.2.0
-->
# Text
You can use **Text** to display text. You can use the **style** argument to define things like textdecoration or fontfamily.
<p align="left">
<img src ="{{ site.images }}/foundation/text/TextExample.png" height=100 width=300 />
</p>
```kotlin
@Composable
fun TextExample(){
Column {
Text("Just Text")
Text("Text with cursive font", style = TextStyle(fontFamily = FontFamily.Cursive))
Text(
text = "Text with LineThrough",
style = TextStyle(textDecoration = TextDecoration.LineThrough)
)
Text(
text = "Text with underline",
style = TextStyle(textDecoration = TextDecoration.Underline)
)
Text(
text = "Text with underline, linethrough and bold",
style = TextStyle(
textDecoration = TextDecoration.combine(
listOf(
TextDecoration.Underline,
TextDecoration.LineThrough
)
), fontWeight = FontWeight.Bold
)
)
}
}
```
# Working with Text
## Normal text
<p align="center">
<img src ="{{ site.images }}/foundation/text/normal_text.png" />
</p>
```kotlin
@Composable
fun NormalTextExample(){
Text("Just Text")
}
```
## Cursive text
<p align="center">
<img src ="{{ site.images }}/foundation/text/cursive_text.png" />
</p>
```kotlin
@Composable
fun CursiveTextExample(){
Text("Text with cursive font", style = TextStyle(fontFamily = Cursive))
}
```
## Text with LineThrough
<p align="center">
<img src ="{{ site.images }}/foundation/text/linethrough_text.png" />
</p>
```kotlin
@Composable
fun TextWithLineThroughExample(){
Text(
text = "Text with LineThrough",
style = TextStyle(textDecoration = TextDecoration.LineThrough)
)
}
```
## Text with underline
<p align="center">
<img src ="{{ site.images }}/foundation/text/underline_text.png" />
</p>
```kotlin
@Composable
fun TextWithUnderline(){
Text(
text = "Text with underline",
style = TextStyle(textDecoration = TextDecoration.Underline)
)
}
```
## Text with underline, bold and linethrough
<p align="center">
<img src ="{{ site.images }}/foundation/text/underline_bold_linethrough_text.png" />
</p>
```kotlin
@Composable
fun TextWithUnderlineStrikeThroughAndBold(){
Text(
text = "Text with underline, linethrough and bold",
style = TextStyle(
textDecoration = TextDecoration.combine(
listOf(
TextDecoration.Underline,
TextDecoration.LineThrough
)
), fontWeight = FontWeight.Bold
)
)
}
```
## See also:
* [Full Example Code]({{ site.samplefolder }}/foundation/TextExample.kt)
* [Write it down: Using text in Jetpack Compose](https://www.droidcon.com/2022/08/01/write-it-down-using-text-in-jetpack-compose/))
@@ -0,0 +1,7 @@
# Codelabs
* [Jetpack Compose basics](https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/index.html?index=..%2F..index#0)
* [Migrating to Jetpack Compose](https://codelabs.developers.google.com/codelabs/jetpack-compose-migration)
* [Jetpack Compose Theming](https://codelabs.developers.google.com/codelabs/jetpack-compose-theming/index.html?index=..%2F..index#0)
* [Layouts in Jetpack Compose](https://codelabs.developers.google.com/codelabs/jetpack-compose-layouts/index.html?index=..%2F..index#0)
* [Using State in Jetpack Compose](https://codelabs.developers.google.com/codelabs/jetpack-compose-state/index.html?index=..%2F..index#0)
* [Advanced State Side Effects](https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects#0)
@@ -0,0 +1,66 @@
# Compiler Plugin
Compose works by transforming all Kotlin functions that are annotated with **@Composable** and adding code for the Compose Runtime.
To do that it uses a Kotlin Compiler Plugin.
For instance, this Composable:
```kotlin
@Composable
fun Hello(name: String) {
Text(name)
}
```
will be transformed and compiled to Jvm ByteCode. Below you can see the decompiled code as Java code
```java
@Metadata(
mv = {1, 5, 1},
k = 2,
xi = 48,
d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0015\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003H\u0007¢\u0006\u0002\u0010\u0004¨\u0006\u0005"},
d2 = {"Hello", "", "name", "", "(Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V", "app_debug"}
)
public final class GreetingKt {
@Composable
public static final void Hello(@NotNull final String name, @Nullable Composer $composer, final int $changed) {
Intrinsics.checkNotNullParameter(name, "name");
$composer = $composer.startRestartGroup(274849561);
ComposerKt.sourceInformation($composer, "C(Hello)7@166L10:Greeting.kt#tlkiwl");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(name) ? 4 : 2);
}
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I(name, (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 14 & $dirty, 0, 65534);
}
ScopeUpdateScope var4 = $composer.endRestartGroup();
if (var4 != null) {
var4.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
GreetingKt.Hello(name, $composer, $changed | 1);
}
}));
}
}
}
```
## Where can i find the source code
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/compose/compiler/compiler-hosted/
## See also:
* Leland Richardson(@intelligibabble) started a series about the Compose Compiler https://www.youtube.com/watch?v=bg0R9-AUXQM
* [Under the hood of Jetpack Compose — part 2 of 2](https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd)
* [A Hitchhiker's Guide to Compose Compiler: Composers, Compiler Plugins, and Snapshots](https://www.droidcon.com/2022/06/28/ha-hitchhikers-guide-to-compose-compiler-composers-compiler-plugins-and-snapshots/)
* [Explained: Compose Compiler and Runtime](https://www.droidcon.com/2022/08/02/explained-compose-compiler-and-runtime/)
* [Using Compose Runtime to create a client library](https://www.droidcon.com/2022/08/02/using-compose-runtime-to-create-a-client-library/)
@@ -0,0 +1,53 @@
<!---
This is the API of version 1.2.0
-->
# Lifecycle
Compose has some "effects"-functions that can be used in Composables to track the lifecycle of a function.
* LaunchedEffect {}
will be called the first time a compose function is applied.
* DisposableEffect { }
Has a onDispose() which will be called when the compose function isn't part of the composition anymore.
The example below has a Button that will count up everytime it gets clicked.
When the count value gets 3, the Text() function will not be added anymore.
The first time the LifecycleDemo will be executed, the SideEffect in the if-clause will be executed.
When the count value gets 3 +onDispose{} inside the if-clause will be called.
```kotlin
@Composable
fun LifecycleDemo() {
val count = remember { mutableStateOf(0) }
Column {
Button(onClick = {
count.value++
}) {
Text("Click me")
}
if (count.value < 3) {
LaunchedEffect(Unit)
Log.d("Compose", "onactive with value: " + count.value)
}
DisposableEffect(Unit) {
onDispose {
Log.d("Compose", "onDispose because value=" + count.value)
}
}
Text(text = "You have clicked the button: " + count.value.toString())
}
}
}
```
## See also:
* [Full Example Code]({{ site.samplefolder }}/general/LifecycleDemo.kt)
@@ -0,0 +1,36 @@
# Compose Confusion
Because of similar naming of the Compose libraries, there is a some confusion between the different "variants" of Compose. Here is a quick overview that tries to give clarification:
## Compose Runtime
The [Compose Runtime](https://developer.android.com/jetpack/androidx/releases/compose-runtime) is a library for manipulating and managing trees of data.
This library can be used to manage UI tree nodes, but it's a general purpose library, so it can be used with any kind of data.
It is developed as a Kotlin Multiplatform Library, which means that it could be ported to every target that Kotlin Multiplatform supports.
Openly developed targets are Android, JVM and Web.
[Square](https://www.reddit.com/r/androiddev/comments/r77o7d/comment/hmyqbta/?utm_source=share&utm_medium=web2x&context=3
) is running an internal version of it on iOS.
This library contains basic things like @Composable, remember, mutableState, effects.
## Compose Compiler Plugin
The Compose Compiler Plugin is a Kotlin Compiler Plugin that transforms all the **@Composable** functions and adds the needed calls to the **Compose Runtime**
## Compose UI
[Compose UI](https://developer.android.com/jetpack/androidx/releases/compose-ui) is a set of UI libraries for Android using **Compose Runtime/Compose Compiler Plugin**
# Jetpack Compose
Jetpack Compose is a UI Toolkit for Android developed by Google.
It is using **Compose Runtime/Compose Compiler Plugin** and **Compose UI**
# Compose HTML
Compose HTML is a UI Toolkit for Web developed by JetBrains written in Kotlin/JS.
It is using the **Compose Runtime** and the **Compose Compiler Plugin**. It does not use Compose UI, because it uses Compose Wrappers for the HTML DOM UI Elements.
# Compose for Desktop
**Compose for Desktop** is a UI Toolkit developed by JetBrains. It runs on the JVM and it is using **Compose Runtime**, **Compose Compiler Plugin** and a Compose UI version for desktop which is using Skia to target Windows, macOS, Linux. Additional it provides components for desktop specific apps like scrollbars or mouse support and Swing interop.
# Compose Multiplatform
[Compose Multiplatform](https://www.jetbrains.com/lp/compose-mpp/) is a Kotlin multiplatform project which is developed by JetBrains.
It uses Jetpack Compose, Compose for Web and Compose for Desktop
@@ -0,0 +1,85 @@
<!---
This is the API of version 1.2.0
-->
# CompositionLocal
CompositionLocal is useful when you want to create a dependency in a higher node of the layout tree and use it on lower node without having to
pass it down the tree through every child Composable.
## How to create an CompositionLocal?
```kotlin
data class User(val name: String, val age: Int)
val LocalActiveUser = compositionLocalOf<User> { error("No user found!") }
```
Let's say you want to create an CompositionLocal with an User. You can use **compositionLocalOf**. Inside the function you can return an initial user object
or you can throw an exception when the user is missing.
## How to provide a value for an CompositionLocal?
```kotlin
@Composable
private fun MyUserScreen() {
val user = User("Jens", 31)
CompositionLocalProvider(LocalActiveUser provides user) {
UserInfo()
}
}
```
Somewhere above in your hierarchy you have to use CompositionLocalProvider to provide a value for your CompositionLocal.
The syntax is: "**CompositionLocal`<T>`** provides **T**".
All child @Composable of CompositionLocalProvider will implicitly be able to get the value of the CompositionLocals.
## How to use a value of an CompositionLocal?
```kotlin
@Preview
@Composable
fun UserInfo() {
Column {
Text("Name: " + LocalActiveUser.current.name)
Text("Age: " + LocalActiveUser.current.age)
}
}
```
Now you can use your CompositionLocal in your @Composable. Every CompositionLocal has a **current** property that contains the current value.
## Predefined CompositionLocals
The Compose libraries already contain some useful CompositionLocals. You can directly use them without needing add a Providers.
### LocalContext
Provides a [Context] that can be used by Android applications.
### LocalConfiguration
The [Configuration] is useful for determining how to organize the UI.
#### Device orientation
One of the things you can get from the LocalConfiguration is the orientation of your device. This can be used to give the user a different ui when the device is rotated.
```kotlin
val configuration = LocalConfiguration.current
when (configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
Text("Landscape")
}
else -> {
Text("Portrait")
}
}
```
### LocalLifecycleOwner
The CompositionLocal containing the current [LifecycleOwner].
### LocalView
The CompositionLocal containing the current Compose [View].
### LocalViewModelStoreOwner
The CompositionLocal containing the current [ViewModelStoreOwner].
## See also:
* [Official Docs](https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal)
* [Full Example Code]({{ site.samplefolder }}/general/CompositionLocalExample.kt)
@@ -0,0 +1,17 @@
# CompositionLocalProvider
CompositionLocalProvider are used to provide a value for an [CompositionLocal](../compositionlocal)
## How to provide a value for an CompositionLocal?
```kotlin
@Composable
private fun MyUserScreen() {
val user = User("Jens", 31)
CompositionLocalProvider(LocalActiveUser provides user) {
UserInfo()
}
}
```
Somewhere above in your hierarchy you have to use CompositionLocalProviders to provide a value for your CompositionLocal. You can provide the values of multiple CompositionLocals inside Providers.
The syntax is: "**CompositionLocal`<T>`** provides **T**".
All child @Composable of CompositionLocalProvider will implicitly be able to get the value of the CompositionLocals.
@@ -0,0 +1,49 @@
# Project Setup
## Gradle Dependencies
Add this inside in the **android{}** block your build.gradle
```groovy
android{
//YOUR OTHER CODE
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "{{compose.release}}"
}
}
```
Below are some Compose dependencies that are online available, can find the others [here](https://maven.google.com/web/index.html?q=compose#androidx.compose.ui)
```kotlin
dependencies {
val compose_version = "{{compose.release}}"
implementation("androidx.compose.animation:animation-core:$compose_version")
implementation("androidx.compose.animation:animation:$compose_version")
implementation("androidx.compose.ui:ui:$compose_version")
implementation("androidx.compose.foundation:foundation:$compose_version")
implementation("androidx.compose.ui:ui-geometry:$compose_version")
implementation("androidx.compose.ui:ui-graphics:$compose_version")
implementation("androidx.compose.foundation:foundation-layout:$compose_version")
implementation("androidx.compose.runtime:runtime-livedata:$compose_version")
implementation("androidx.compose.material:material:$compose_version")
implementation("androidx.compose.material:material-icons-core:$compose_version")
implementation("androidx.compose.material:material-icons-extended:$compose_version")
implementation("androidx.compose.runtime:runtime-rxjava2:$compose_version")
implementation("androidx.compose.ui:ui-text:$compose_version")
implementation("androidx.compose.ui:ui-util:$compose_version")
implementation("androidx.compose.ui:ui-viewbinding:$compose_version")
implementation("androidx.compose.ui:ui-tooling:$compose_version")
implementation("androidx.activity:activity-compose:1.3.1")
//Compose Constraintlayout
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0")
}
```
@@ -0,0 +1,36 @@
# Hello World Compose
### Setup the project
First setup your [Project Setup](getting_started.md)
### Write a simple Compose function
A basic Compose View is using a normal Kotlin function which is annotated with @Composable
```kotlin
@Composable
fun HelloWorld() {
Text("Hello World!")
}
```
### Use a Compose function as a view in your android app
To use the HelloWorld() function in your App you have to use the setContent() extension function inside a onCreate() in an Activity.
```kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelloWorld()
}
}
}
```
@@ -0,0 +1,93 @@
<!---
This is the API of version 1.2.0
-->
# Modifier
Modifiers can be used modify certain aspects of a Composable.
To set them, a Composable needs to accept a modifier as a parameter.
## Combine modifiers
```kotlin
Column(modifier = Modifier.height(500.dp).padding(100.dp)) {
Text("Hello")
}
```
You can chain multiple modifiers.
The order is important modifier elements to the left are applied before modifier elements to the right.
## Some Modifiers
The Compose libraries already contain some useful modifiers.
### LayoutModifier
#### Modifier.width()
You can use this to set the width of a Composable.
#### Modifier.height()
You can use this to set the height of a Composable.
#### Modifier.size()
You can use this to set the width and height of a Composable.
#### Modifier.fillMaxHeight()
This will set the height of the Composable to the maximum available height. This is similar to **MATCH_PARENT** from the classic View system.
#### Modifier.fillMaxWidth()
This will set the width of the Composable to the maximum available width. This is similar to **MATCH_PARENT** from the classic View system.
#### Modifier.fillMaxSize()
This will set the height/width of the Composable to the maximum available height/width
#### Modifier.padding()
You can use **Modifier.padding** to set padding to Composables that take a modifier as an argument.
<p align="left">
<img src ="../../images/general/modifier/PaddingExample.png" height=100 width=300 />
</p>
```kotlin
@Composable
fun PaddingDemo() {
Column {
Text("TextWithoutPadding")
Column(modifier = Modifier.padding(start = 80.dp)){
Text("TextWith80dpOnlyLeftPadding")
}
Column(Modifier.padding(all = 80.dp)){
Text("TextWith80dpPadding")
}
}
}
```
### DrawModifier
#### Modifier.background()
With this modifier you can set a background color/shape for the Composable
#### Modifier.clip()
This modifier can clip the Composable to rectangle, rounded, or circle
### GestureModifier
#### Modifier.clickable
Configure component to receive clicks via input or accessibility "click" event.
#### Modifier.scrollable
You can use this to make a Composable scrollable
#### Modifier.draggable
You can use this to make a Composable draggable
#### Modifier.swipeable
You drag elements which, when released, animate towards typically two or more anchor points defined in an orientation
#### Multitouch: Panning, zooming, rotating
To detect multitouch gestures used for panning, zooming and rotating, you can use the ``transformable`` modifier. This modifier does not transform elements by itself, it only detects the gestures.
## See also:
* [Official Docs](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier)
@@ -0,0 +1,12 @@
# Navigation
## On Android
In Android Projects you can use the [Jetpack Compose Navigation](https://developer.android.com/jetpack/compose/navigation)
[Codelab Jetpack Compose Navigation](https://developer.android.com/codelabs/jetpack-compose-navigation#0)
## Multiplatform
When you need a multiplaform solution for Routing, you can use [Decompose](https://github.com/arkivanov/Decompose)
[Decompose Navigation Overview](https://arkivanov.github.io/Decompose/navigation/overview/)
@@ -0,0 +1,48 @@
# Preview
## Preview
You can use the **@Preview** annotation to preview compose functions inside Android Studio.
Preview can not be used on Composables that have parameters without a default parameter.
```kotlin
@Preview
@Composable
fun TextDemo(){
Text("Hello")
}
@Preview(name = "MyPreviewName")
@Composable
fun TextDemo2(){
Text("Hello")
}
```
Android Studio Preview
<p align="center">
<img src ="../../../images/general/preview/ComposePreview.png" height=100 width=300 />
</p>
## Group Previews
```kotlin
@Preview(group = "TestGroup1")
```
The Preview annotation has a group parameter. You can use it to set a group name to your previews.
<p align="center">
<img src ="../../../images/general/preview/grouppreviews.png" />
</p>
The layout preview will now have an option to filter all the previews by the group name.
## Interactive Previews
<p align="center">
<img src ="../../../images/general/preview/interactivepreview.png" />
</p>
Above a generated preview in Android Studio you will find an "Interactive" Button. It will open your Composable in an interactive preview mode where can try your Composable directly inside
Android Studio.
## See also:
* [Official Docs](https://developer.android.com/jetpack/compose/tooling#preview-features)
@@ -0,0 +1,71 @@
# PreviewParameter
You can use **@PreviewParameter** to provide sample data for your Composables.
Let's say you have the following Composable and you want to generate a preview.
```kotlin
data class User(val name :String,val age:Int)
@Composable
fun UserInfo(user:User) {
Text(user.name+ " "+user.age)
}
```
Because UserInfo needs a User you can't directly use @Preview.
One way is to wrap your Composable in a Composable that provides a User
```kotlin
@Preview
@Composable
fun UserPreview() {
UserInfo(user = User("Jens", 31))
}
```
An other way is to use @PreviewParameter on the parameter. With PreviewParameter you can set a class
which will provide values for the needed User
```kotlin
@Preview
@Composable
fun UserInfo(@PreviewParameter(SampleUserProvider::class) user:User) {
Text(user.name+ " "+user.age)
}
```
### Create PreviewParameterProvider
**SampleUserProvider::class** will be the class which provides a User.
To create a PreviewParameterProvider you need to implement the interface PreviewParameterProvider.
The interface has two properties.
**values** is a sequence of your sample data.
```kotlin
class SampleUserProvider: PreviewParameterProvider<User> {
override val values = sequenceOf(User("Jens",31),User("Jim",44))
}
```
### Use PreviewParameterProvider
You can annotate your parameter with @PreviewParameter and the class which provides the sample data.
```kotlin
@Preview
@Composable
fun UserInfo(@PreviewParameter(SampleUserProvider::class) user:User) {
Text(user.name+ " "+user.age)
}
```
Now Android Studio will generate a preview of the Composable for every value that your provider provides.
<p align="center">
<img src ="../../../images/general/preview/previewParam1.png" height=100 width=300 />
</p>
You can limit the amount of previews by settings a limit to PreviewParameter.
```kotlin
@PreviewParameter(SampleUserProvider::class,1)
```
@@ -0,0 +1,3 @@
# Roadmap
You can find the road map here: https://developer.android.com/jetpack/androidx/compose-roadmap
@@ -0,0 +1,62 @@
<!---
This is the API of version 1.2.0
-->
# State
## Define a state
```kotlin
val textState = mutableStateOf("Hello")
```
You can use the mutableStateOf function to create a mutable state.
## Example
In this example we will create a composable with a Text and a Button. On a click on the button, the count state will go up and the text of Text will be updated.
<p align="center">
Initial state:<br>
<img src ="{{ site.images }}/general/state/state1.png" />
</p>
<p align="center">
After Button click:<br>
<img src ="{{ site.images }}/general/state/state2.png" />
</p>
```kotlin
@Composable
fun StateDemo(){
val countState = remember { mutableStateOf(0) }
Column {
Button(colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary), onClick = { countState.value++ }) {
Text("count up")
}
Text("You have clicked the Button " + countState.value.toString() + " times")
}
}
```
```kotlin
val countState = remember { mutableStateOf(0) }
```
Here we define the state for the click counter with **mutableStateOf(0)**. 0 will be the initial value. Because 0 is an Int, the counterState will only allow values which are Int.
**remember** is used to remember the countstate. Without remember, every time the value of countstate would change, the StateDemo Composable will be recomposed and your state will also
get recreated with the initial value. When you use remember it will remember the last value and not be recreated.
```kotlin
countState.value
```
With the value property you can get/set the value of the counterstate. In the onClick() function of the Button, the value will be incremented. The Text shows the value of the counterstate. When counterstate is changed,
"Text" will also change.
## See also:
* [Codelab: Using state in Jetpack Compose](https://developer.android.com/codelabs/jetpack-compose-state#0)
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/PMMY23F0CFg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
* [Invest in Real State: A workshop for State in Compose](https://www.droidcon.com/2022/08/02/invest-in-real-state-a-workshop-for-state-in-compose/)
@@ -0,0 +1,134 @@
# UI Testing
## Setup
Add the Compose testing library to your dependencies in build.gradle
```kotlin
androidTestImplementation("androidx.compose.ui:ui-test:$compose_version")
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
```
## Example
Let's say you have the following example code, it's a button with a text that says **Hello** and when you click on it, it turns to **Bye**
```kotlin
@Composable
fun TestingExample() {
val state = remember { mutableStateOf("Hello") }
Button(onClick = { state.value = "Bye" }) {
Text(state.value)
}
}
```
## TestRule
To run this Composable in your test Code you have 2 options:
### 1) createComposeRule()
```kotlin
@Rule
@JvmField
var composeTestRule: ComposeContentTestRule = createComposeRule()
```
You can use this when you want to run your Composable without a specific Activity. You can then use the **setContent()** from the TestRule to host your Composable.
```kotlin
composeTestRule.setContent {
TestingExample()
}
```
### 2) createAndroidComposeRule()
```kotlin
@Rule
@JvmField
var composeTestRule: ComposeContentTestRule = createAndroidComposeRule<UiTestingDemoActivity>()
```
You can use this when you want to start your test with a specific Activity.
## Interaction with Composables
Now we want to test how the Composable reacts when we interact with it.
Usually in Tests we would use Espresso for that, but you can use that only for classic Android Views and not for Composables.
The ComposeTestRule will offer a similar API
```kotlin
class ExampleUiTestWithAndroidComposeRule {
@Rule
@JvmField
var composeTestRule: ComposeContentTestRule = createAndroidComposeRule<UiTestingDemoActivity>()
@Test
fun whenIClickOnButton_TheTextShouldChange() {
composeTestRule.onNodeWithText("Hello").assertExists()
composeTestRule.onNodeWithText("Hello").performClick()
composeTestRule.onNodeWithText("Hello").assertDoesNotExist()
composeTestRule.onNodeWithText("Bye").assertExists()
}
}
```
As you can see above we used the TestRule methods to click on the node with the text **Hello** and asserted that the text changed to **Bye**
The TestRule offers a lot of different methods. You can find a cheatSheet [here](https://developer.android.com/jetpack/compose/testing-cheatsheet)
### TestTags
The test above is only half correct, because it looks for the text inside the button and not the button itself.
Composables have no resource ids so we cant just use **onView(withId(R.id.my_view))** to find a Composable in a test, also we can't find Composables of a specific "type"
like a button, because everything is just a Composable function.
When you can't find a Composable by a text and you want to make it detectable in your test. Jetpack Compose offers the concept of a **TestTag**.
testTag is a modifier that needs to be set to a Composable. It expects a string which will be used as a reference in your test.
```kotlin
@Composable
fun TestingExample() {
val state = remember { mutableStateOf("Hello") }
Button(onClick = { state.value = "Bye" }, modifier = Modifier.testTag("MyTestTag")) {
Text(state.value)
}
}
```
Now you can use **onNodeWithTag("MyTestTag")** to find the button Composable.
```kotlin
@Test
fun whenIClickOnButton_TheTextShouldChange() {
composeTestRule.onNodeWithTag("MyTestTag").assertTextEquals("Hello")
composeTestRule.onNodeWithTag("MyTestTag").performClick()
composeTestRule.onNodeWithText("Hello").assertDoesNotExist()
composeTestRule.onNodeWithTag("MyTestTag").assertTextEquals("Bye")
}
```
### PrintToLog
When you want to get more information about how the node tree of a Composable looks like,
you can use **printToLog()** on a node.
```kotlin
composeTestRule.onNodeWithTag("MyTestTag").printToLog("XXX")
```
will print to logcat:
```kotlin
2022-01-07 22:58:55.048 9567-9587/de.jensklingenberg.jetpackcomposeplayground D/XXX: printToLog:
Printing with useUnmergedTree = 'false'
Node #2 at (l=0.0, t=325.0, r=195.0, b=424.0)px, Tag: 'MyTestTag'
Role = 'Button'
Text = '[Hello]'
Actions = [OnClick, GetTextLayoutResult]
MergeDescendants = 'true'
```
## See also:
* [Full Example Code](https://github.com/Foso/Jetpack-Compose-Playground/blob/master/app/src/androidTest/java/de/jensklingenberg/jetpackcomposeplayground/ExampleUiTestWithAndroidComposeRule.kt)
* [Full Example Code](https://github.com/Foso/Jetpack-Compose-Playground/blob/master/app/src/androidTest/java/de/jensklingenberg/jetpackcomposeplayground/ExampleUiTestWithComposeRule.kt)
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Some files were not shown because too many files have changed in this diff Show More