Added footer, scrollable conversation and global UI improvements
This commit is contained in:
parent
2157a7e26f
commit
75bf1c3389
@ -11,7 +11,9 @@
|
||||
<entry key="../../../../../layout/compose-model-1641145605520.xml" value="0.8685185185185185" />
|
||||
<entry key="../../../../../layout/compose-model-1641154451614.xml" value="0.8768518518518519" />
|
||||
<entry key="../../../../../layout/compose-model-1641155782430.xml" value="0.7886363636363637" />
|
||||
<entry key="../../../../../layout/compose-model-1641156150634.xml" value="0.33" />
|
||||
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.5307291666666667" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_more_vert_24.xml" value="0.38981481481481484" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_person_24.xml" value="0.5307291666666667" />
|
||||
<entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.5307291666666667" />
|
||||
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.5307291666666667" />
|
||||
|
@ -4,9 +4,7 @@ import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
@ -16,8 +14,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@ -79,9 +75,32 @@ fun Base() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Footer() {
|
||||
//We create a row that we align to the bottom center of the parent box
|
||||
Row(
|
||||
Modifier
|
||||
.padding(bottom = 50.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
//Microphone floating button to manually start/stop listening
|
||||
FloatingActionButton(onClick = { /*TODO*/ }) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_baseline_mic_24),
|
||||
contentDescription = "microphone"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageFromJarvis(text: String) {
|
||||
//We create a row to contain the message and the robot image (to look like an sms)
|
||||
Row(Modifier.padding(bottom = 25.dp)) {
|
||||
|
||||
// Adding the robot image as the sender
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.robot256),
|
||||
contentDescription = "robot",
|
||||
@ -90,13 +109,13 @@ fun MessageFromJarvis(text: String) {
|
||||
.padding(end = 10.dp)
|
||||
)
|
||||
|
||||
// Adding the message box with the text given in the params
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(fraction = 0.9F)
|
||||
.clip(RoundedCornerShape(15.dp))
|
||||
.background(color = MaterialTheme.colors.secondaryVariant)
|
||||
.padding(horizontal = 10.dp, vertical = 5.dp)
|
||||
|
||||
) {
|
||||
Text(text = text, fontFamily = productSansFont)
|
||||
}
|
||||
@ -105,10 +124,13 @@ fun MessageFromJarvis(text: String) {
|
||||
|
||||
@Composable
|
||||
fun MessageFromUser(text: String) {
|
||||
//We create a row to contain the user message and we align the row to the right side (to look like a conversation between two people)
|
||||
Row(
|
||||
Modifier
|
||||
.padding(bottom = 25.dp)
|
||||
.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
.fillMaxWidth(), horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
// The message box with the text
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(fraction = 0.8F)
|
||||
@ -119,23 +141,48 @@ fun MessageFromUser(text: String) {
|
||||
Text(
|
||||
text = text,
|
||||
fontFamily = productSansFont,
|
||||
color = if (!isSystemInDarkTheme()) Color.White else Color(15, 15, 15, 255))
|
||||
color = if (!isSystemInDarkTheme()) Color.White else Color(15, 15, 15, 255)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun DefaultPreview() {
|
||||
JarvisComposeTheme {
|
||||
Column(Modifier.padding(top = 40.dp, start = 20.dp, end = 20.dp)) {
|
||||
Base()
|
||||
MessageFromJarvis(text = "Salut, je suis Jarvis! \nPose moi une question et je ferais de mon mieux pour te renseigner.")
|
||||
MessageFromUser(text = "Quel temps fait-il à Paris en ce moment ?")
|
||||
MessageFromJarvis(text = "A Paris, il fait actuellement 10 degrés et le ciel est nuageux.")
|
||||
//We create a main box with basic padding to avoid having stuff too close to every side.
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(top = 30.dp, bottom = 0.dp)
|
||||
) {
|
||||
|
||||
// This column regroup the base and all the conversations (everything except the footer)
|
||||
Column {
|
||||
Base()
|
||||
|
||||
// This column regroup only the conversations and make them scrollable
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.weight(weight = 1f, fill = false)
|
||||
) {
|
||||
// Basic interaction stuff for demo
|
||||
MessageFromJarvis(text = "Salut, je suis Jarvis! \nPose moi une question et je ferais de mon mieux pour te renseigner.")
|
||||
MessageFromUser(text = "Quel temps fait-il à Paris en ce moment ?")
|
||||
MessageFromJarvis(text = "A Paris, il fait actuellement 10 degrés et le ciel est nuageux.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Finally we add the footer to the bottom center of the main box
|
||||
Column(Modifier.align(Alignment.BottomCenter)) {
|
||||
Footer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,332 @@
|
||||
// Thanks to EugeneTheDev : https://gist.github.com/EugeneTheDev/a27664cb7e7899f964348b05883cbccd
|
||||
|
||||
package ch.mathieubroillet.jarvis.android.utils
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val dotSize = 20.dp
|
||||
val delayUnit = 150
|
||||
|
||||
@Composable
|
||||
fun DotsPulsing() {
|
||||
|
||||
@Composable
|
||||
fun Dot(
|
||||
scale: Float
|
||||
) = Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.scale(scale)
|
||||
.background(
|
||||
color = MaterialTheme.colors.primary,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
|
||||
@Composable
|
||||
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = delayUnit * 4
|
||||
0f at delay with LinearEasing
|
||||
1f at delay + delayUnit with LinearEasing
|
||||
0f at delay + delayUnit * 2
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val scale1 by animateScaleWithDelay(0)
|
||||
val scale2 by animateScaleWithDelay(delayUnit)
|
||||
val scale3 by animateScaleWithDelay(delayUnit * 2)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
val spaceSize = 2.dp
|
||||
|
||||
Dot(scale1)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(scale2)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(scale3)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotsElastic() {
|
||||
val minScale = 0.6f
|
||||
|
||||
@Composable
|
||||
fun Dot(
|
||||
scale: Float
|
||||
) = Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.scale(scaleX = minScale, scaleY = scale)
|
||||
.background(
|
||||
color = MaterialTheme.colors.primary,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
|
||||
@Composable
|
||||
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = minScale,
|
||||
targetValue = minScale,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = delayUnit * 4
|
||||
minScale at delay with LinearEasing
|
||||
1f at delay + delayUnit with LinearEasing
|
||||
minScale at delay + delayUnit * 2
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val scale1 by animateScaleWithDelay(0)
|
||||
val scale2 by animateScaleWithDelay(delayUnit)
|
||||
val scale3 by animateScaleWithDelay(delayUnit * 2)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
val spaceSize = 2.dp
|
||||
|
||||
Dot(scale1)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(scale2)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(scale3)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotsFlashing() {
|
||||
val minAlpha = 0.1f
|
||||
|
||||
@Composable
|
||||
fun Dot(
|
||||
alpha: Float
|
||||
) = Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.alpha(alpha)
|
||||
.background(
|
||||
color = MaterialTheme.colors.primary,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
|
||||
@Composable
|
||||
fun animateAlphaWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = minAlpha,
|
||||
targetValue = minAlpha,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = delayUnit * 4
|
||||
minAlpha at delay with LinearEasing
|
||||
1f at delay + delayUnit with LinearEasing
|
||||
minAlpha at delay + delayUnit * 2
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val alpha1 by animateAlphaWithDelay(0)
|
||||
val alpha2 by animateAlphaWithDelay(delayUnit)
|
||||
val alpha3 by animateAlphaWithDelay(delayUnit * 2)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
val spaceSize = 2.dp
|
||||
|
||||
Dot(alpha1)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(alpha2)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(alpha3)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotsTyping() {
|
||||
val maxOffset = 10f
|
||||
|
||||
@Composable
|
||||
fun Dot(
|
||||
offset: Float
|
||||
) = Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.offset(y = -offset.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colors.primary,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
|
||||
@Composable
|
||||
fun animateOffsetWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = delayUnit * 4
|
||||
0f at delay with LinearEasing
|
||||
maxOffset at delay + delayUnit with LinearEasing
|
||||
0f at delay + delayUnit * 2
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val offset1 by animateOffsetWithDelay(0)
|
||||
val offset2 by animateOffsetWithDelay(delayUnit)
|
||||
val offset3 by animateOffsetWithDelay(delayUnit * 2)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(top = maxOffset.dp)
|
||||
) {
|
||||
val spaceSize = 2.dp
|
||||
|
||||
Dot(offset1)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(offset2)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(offset3)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotsCollision() {
|
||||
val maxOffset = 30f
|
||||
val delayUnit = 500 // it's better to use longer delay for this animation
|
||||
|
||||
@Composable
|
||||
fun Dot(
|
||||
offset: Float
|
||||
) = Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.offset(x = offset.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colors.primary,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
|
||||
val offsetLeft by infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = delayUnit * 3
|
||||
0f at 0 with LinearEasing
|
||||
-maxOffset at delayUnit / 2 with LinearEasing
|
||||
0f at delayUnit
|
||||
}
|
||||
)
|
||||
)
|
||||
val offsetRight by infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = delayUnit * 3
|
||||
0f at delayUnit with LinearEasing
|
||||
maxOffset at delayUnit + delayUnit / 2 with LinearEasing
|
||||
0f at delayUnit * 2
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(horizontal = maxOffset.dp)
|
||||
) {
|
||||
val spaceSize = 2.dp
|
||||
|
||||
Dot(offsetLeft)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(0f)
|
||||
Spacer(Modifier.width(spaceSize))
|
||||
Dot(offsetRight)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun DotsPreview() = MaterialTheme {
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
val spaceSize = 16.dp
|
||||
|
||||
Text(
|
||||
text = "Dots pulsing",
|
||||
style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsPulsing()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots elastic",
|
||||
style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsElastic()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots flashing",
|
||||
style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsFlashing()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots typing",
|
||||
style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsTyping()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots collision",
|
||||
style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsCollision()
|
||||
}
|
||||
}
|
@ -6,5 +6,5 @@
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
|
||||
</vector>
|
Loading…
Reference in New Issue
Block a user