diff --git a/composeApp/src/commonMain/composeResources/values/string.xml b/composeApp/src/commonMain/composeResources/values/string.xml index b094b75..613b3c5 100644 --- a/composeApp/src/commonMain/composeResources/values/string.xml +++ b/composeApp/src/commonMain/composeResources/values/string.xml @@ -2,7 +2,7 @@ Home Favourite - Profile + Gemini Cart RATING TYPE @@ -18,4 +18,5 @@ Category All Number Of People + Gemini ChatBot \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index 48745d6..7f0e70b 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -9,7 +9,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.tab.CurrentTab -import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabNavigator import coil3.ImageLoader import coil3.PlatformContext @@ -26,7 +25,7 @@ import ui.component.tabs import ui.screen.CartTab import ui.screen.FavoriteTab import ui.screen.HomeTab -import ui.screen.ProfileTab +import ui.screen.GeminiTab import ui.viewmodel.HomeViewModel import util.AnimateVisibility @@ -58,7 +57,7 @@ internal fun App( HomeTab -> LocalNavigator.currentOrThrow.push(HomeTab) FavoriteTab -> LocalNavigator.currentOrThrow.push(FavoriteTab) CartTab -> LocalNavigator.currentOrThrow.push(CartTab) - ProfileTab -> LocalNavigator.currentOrThrow.push(ProfileTab) + GeminiTab -> LocalNavigator.currentOrThrow.push(GeminiTab) } } } diff --git a/composeApp/src/commonMain/kotlin/model/ChatMessage.kt b/composeApp/src/commonMain/kotlin/model/ChatMessage.kt new file mode 100644 index 0000000..29e29bc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/model/ChatMessage.kt @@ -0,0 +1,3 @@ +package model + +data class ChatMessage(val text: String, val isSender: Boolean) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/component/BottomNavigationBar.kt b/composeApp/src/commonMain/kotlin/ui/component/BottomNavigationBar.kt index 1f542f3..29271f7 100644 --- a/composeApp/src/commonMain/kotlin/ui/component/BottomNavigationBar.kt +++ b/composeApp/src/commonMain/kotlin/ui/component/BottomNavigationBar.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape @@ -40,7 +39,7 @@ import theme.SecondTextColor import ui.screen.CartTab import ui.screen.FavoriteTab import ui.screen.HomeTab -import ui.screen.ProfileTab +import ui.screen.GeminiTab interface Tabx: Tab { fun defaultTitle(): StringResource @@ -51,7 +50,7 @@ val tabs = arrayListOf().apply { add(HomeTab) add(FavoriteTab) add(CartTab) - add(ProfileTab) + add(GeminiTab) } @Composable diff --git a/composeApp/src/commonMain/kotlin/ui/component/Shimmer.kt b/composeApp/src/commonMain/kotlin/ui/component/Shimmer.kt new file mode 100644 index 0000000..c3925b0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/component/Shimmer.kt @@ -0,0 +1,82 @@ +package ui.component + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import theme.PrimaryColor + +val ShimmerColorShades = listOf( + Color.LightGray.copy(0.9f), + PrimaryColor.copy(0.2f), + Color.LightGray.copy(0.9f) +) +@Composable +fun ShimmerAnimation( +) { + /* + Create InfiniteTransition + which holds child animation like [Transition] + animations start running as soon as they enter + the composition and do not stop unless they are removed + */ + val transition = rememberInfiniteTransition() + val translateAnim by transition.animateFloat( + /* + Specify animation positions, + initial Values 0F means it + starts from 0 position + */ + initialValue = 0f, + targetValue = 1000f, + animationSpec = infiniteRepeatable( + + + // Tween Animates between values over specified [durationMillis] + tween(durationMillis = 1200, easing = FastOutSlowInEasing), + RepeatMode.Reverse + ) + ) + + /* + Create a gradient using the list of colors + Use Linear Gradient for animating in any direction according to requirement + start=specifies the position to start with in cartesian like system Offset(10f,10f) means x(10,0) , y(0,10) + end = Animate the end position to give the shimmer effect using the transition created above + */ + val brush = Brush.linearGradient( + colors = ShimmerColorShades, + start = Offset(10f, 10f), + end = Offset(translateAnim, translateAnim) + ) + + ShimmerItem(brush = brush) +} + +@Composable +fun ShimmerItem( + brush: Brush +) { + Spacer( + modifier = Modifier + .padding(top = 8.dp) + .fillMaxWidth() + .size(20.dp) + .background(brush = brush, shape = RoundedCornerShape(8.dp)) + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/CartTab.kt b/composeApp/src/commonMain/kotlin/ui/screen/CartTab.kt index 98496d6..8db7c8f 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/CartTab.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/CartTab.kt @@ -78,7 +78,7 @@ fun CartScreenView( modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 36.dp), text = stringResource(Res.string.cart_tab), color = TextColor, - style = MaterialTheme.typography.bodySmall, + style = MaterialTheme.typography.headlineMedium, textAlign = TextAlign.Center ) } diff --git a/composeApp/src/commonMain/kotlin/ui/screen/FavoriteTab.kt b/composeApp/src/commonMain/kotlin/ui/screen/FavoriteTab.kt index 8f8be4d..723a2da 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/FavoriteTab.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/FavoriteTab.kt @@ -88,7 +88,7 @@ fun FavoriteScreenView( modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 36.dp), text = stringResource(Res.string.favorite_destination), color = TextColor, - style = MaterialTheme.typography.bodySmall, + style = MaterialTheme.typography.headlineMedium, textAlign = TextAlign.Center ) } diff --git a/composeApp/src/commonMain/kotlin/ui/screen/ProfileTab.kt b/composeApp/src/commonMain/kotlin/ui/screen/GeminiTab.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/ui/screen/ProfileTab.kt rename to composeApp/src/commonMain/kotlin/ui/screen/GeminiTab.kt index 8997a94..a02b338 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/ProfileTab.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/GeminiTab.kt @@ -1,5 +1,11 @@ package ui.screen +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.expandIn +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkOut import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme @@ -18,7 +24,6 @@ import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi @@ -32,29 +37,24 @@ import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.mikepenz.markdown.m3.Markdown import com.mikepenz.markdown.m3.markdownColor -import com.mikepenz.markdown.m3.markdownTypography -import com.mikepenz.markdown.model.MarkdownTypography import data.GeminiApi import dev.shreyaspatil.ai.client.generativeai.type.GenerateContentResponse import io.github.vinceglb.filekit.compose.rememberFilePickerLauncher @@ -63,7 +63,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import org.jetbrains.compose.ui.tooling.preview.Preview import theme.BorderColor import theme.CodeBackground import theme.LinkColor @@ -71,12 +70,14 @@ import theme.PrimaryColor import theme.TextColor import toComposeImageBitmap import travelbuddy.composeapp.generated.resources.Res +import travelbuddy.composeapp.generated.resources.gemini import travelbuddy.composeapp.generated.resources.menu_profile import travelbuddy.composeapp.generated.resources.profile_tab +import ui.component.ShimmerAnimation import ui.component.Tabx import util.BOTTOM_NAV_SPACE -data object ProfileTab : Tabx { +data object GeminiTab : Tabx { override fun defaultTitle(): StringResource = Res.string.profile_tab override fun defaultIcon(): DrawableResource = Res.drawable.menu_profile @@ -98,13 +99,13 @@ data object ProfileTab : Tabx { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow - ProfileScreenView(navigator) + GeminiScreenView(navigator) } } @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) @Composable -fun ProfileScreenView(navigator: Navigator){ +fun GeminiScreenView(navigator: Navigator){ val api = remember { GeminiApi() } val coroutineScope = rememberCoroutineScope() var prompt by remember { mutableStateOf("") } @@ -113,11 +114,7 @@ fun ProfileScreenView(navigator: Navigator){ var showProgress by remember { mutableStateOf(false) } var filePath by remember { mutableStateOf("") } var image by remember { mutableStateOf(null) } - val canClearPrompt by remember { - derivedStateOf { - prompt.isNotBlank() - } - } + val canClearPrompt by remember { derivedStateOf { prompt.isNotBlank() } } Surface(modifier = Modifier.fillMaxWidth().padding(bottom = BOTTOM_NAV_SPACE)) { val imagePickerLauncher = rememberFilePickerLauncher(PickerType.Image) { selectedImage -> @@ -134,7 +131,17 @@ fun ProfileScreenView(navigator: Navigator){ .verticalScroll(rememberScrollState()) .fillMaxWidth().padding(16.dp) ) { - FlowRow { + Text( + modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 26.dp), + text = stringResource(Res.string.gemini), + color = TextColor, + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center + ) + + FlowRow( + modifier = Modifier.fillMaxWidth().padding(top = 16.dp), + ) { OutlinedTextField( value = prompt, onValueChange = { prompt = it }, @@ -181,6 +188,7 @@ fun ProfileScreenView(navigator: Navigator){ .onStart { showProgress = true } .onCompletion { showProgress = false } .collect { + showProgress = false println("response = ${it.text}") content += it.text } @@ -237,7 +245,13 @@ fun ProfileScreenView(navigator: Navigator){ verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - CircularProgressIndicator() + Column( + modifier = Modifier.fillMaxSize() + ) { + repeat(5) { + ShimmerAnimation() + } + } } } else { SelectionContainer { @@ -268,4 +282,4 @@ fun generateContentAsFlow( api.generateContent(prompt, imageByteArray) } ?: run { api.generateContent(prompt) -} \ No newline at end of file +}