feat: refs #6659 jetPackCompose

This commit is contained in:
Sergio De la torre 2025-02-20 09:34:41 +01:00
parent f55bd3768f
commit 5d600359fa
14 changed files with 820 additions and 32 deletions

View File

@ -5,6 +5,7 @@ plugins {
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.compose")
}
android {
@ -15,8 +16,8 @@ android {
applicationId = "es.verdnatura"
minSdk = 26
targetSdk = 33 // se deja con target si no Play Protect la bloquea
versionCode = 393 //JAF en informatica
versionName = "25.0"
versionCode = 397 //JAF 393 en informatica
versionName = "25.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@ -83,6 +84,9 @@ android {
}
*/
dataBinding = true
compose = true
}
compileOptions {
@ -142,15 +146,39 @@ android {
implementation(libs.androidx.datastore.core)
implementation(libs.zxing.android.embedded) { isTransitive = false }
implementation(libs.core)
//implementation(libs.koin.androidx.compose)
// Compose
//implementation(libs.androidx.compose.bom)
/* implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material:material")
implementation("androidx.compose.runtime:runtime")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") */
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2024.10.01")
implementation(composeBom)
androidTestImplementation(composeBom)
// Choose one of the following:
// Material Design 3
implementation(libs.androidx.material3)
implementation(libs.androidx.ui)
// Android Studio Preview support
implementation(libs.androidx.ui.tooling.preview)
debugImplementation(libs.androidx.ui.tooling)
// UI Tests
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.test.manifest)
// custom design system based on Foundation)
//implementation(libs.androidx.material.icons.core)
// Optional - Add full set of material icons
implementation(libs.androidx.material.icons.extended)
// Optional - Add window size utils
implementation(libs.androidx.adaptive)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.lottie.compose)
}
}
}
dependencies {
testImplementation(libs.junit)
}

View File

@ -27,6 +27,12 @@
android:name=".presentation.view.feature.articulo.fragment.ImageViewActivity"
android:configChanges="orientation"
android:screenOrientation="portrait" />
<activity
android:name=".presentation.composable.ImageViewActivityComposable"
android:configChanges="orientation"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".presentation.view.feature.restaurant.RestaurantActivity"
android:configChanges="orientation"
@ -69,6 +75,8 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.ViewModel
import es.verdnatura.MobileApplication
import es.verdnatura.domain.SalixService
import org.json.JSONObject
abstract class BaseViewModel : ViewModel, LifecycleObserver {
lateinit var app: MobileApplication
@ -18,23 +17,3 @@ abstract class BaseViewModel : ViewModel, LifecycleObserver {
}
}
fun nameofFunction(function: Any): String {
return try {
function.javaClass.enclosingMethod!!.name + "->"
} catch (e: Exception) {
"ActivityMain->"
}
}
fun getMessageFromAllResponse(callFunction: String, responseMessage: String): String {
var messageFromError: String = try {
val answerError = JSONObject(responseMessage)
answerError.get("Message").toString()
} catch (e: Exception) {
responseMessage
}
return "$messageFromError.\r${"Callback: $callFunction."}"
}

View File

@ -0,0 +1,13 @@
package es.verdnatura.presentation.base
import android.app.Application
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.ViewModel
import es.verdnatura.MobileApplication
import es.verdnatura.domain.SalixService
abstract class BaseViewModelCompose(application: Application) : ViewModel(), LifecycleObserver {
protected val app: MobileApplication = application as MobileApplication
protected val salix: SalixService = app.salix
}

View File

@ -0,0 +1,180 @@
package es.verdnatura.presentation.view.commom.webview
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcher
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import es.verdnatura.presentation.common.OnBackPressedListener
import es.verdnatura.presentation.view.commom.webview.data.WebViewEvent
import es.verdnatura.presentation.view.commom.webview.data.WebViewScreen
import org.json.JSONObject
import kotlin.math.abs
class WebFragmentCompose(
var entryPoint: String = ""
) : Fragment(), OnBackPressedListener {
private val viewModel: WebViewViewModel by viewModels()
private var velocityTracker: VelocityTracker? = null
private lateinit var backDispatcher: OnBackPressedDispatcher
private var webView: WebView? = null
companion object {
private const val FLING_THRESHOLD_VELOCITY = 2000
private const val FLING_THRESHOLD_VERTICAL = 1000
fun newInstance(entryPoint: String) = WebFragmentCompose(entryPoint)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
val state by viewModel.state.collectAsStateWithLifecycle()
// Recolectar acciones del ViewModel
LaunchedEffect(Unit) {
viewModel.webViewActions.collect { event ->
when (event) {
is WebViewEvent.OnBackGesture -> {
webView?.goBack()
}
// Manejar otros eventos si es necesario
is WebViewEvent.LoadUrl -> println("")
is WebViewEvent.OnPageFinished -> println("")
is WebViewEvent.OnUrlLoading -> println("")
is WebViewEvent.ToggleToolbar -> println("")
is WebViewEvent.UpdateTitle -> println("")
else -> {}
}
}
}
WebViewScreen(
state = state,
onWebViewCreated = { webView ->
this@WebFragmentCompose.webView = webView
setupWebView(webView)
},
)
}
}
}
private fun setupWebView(webView: WebView) {
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
request?.url?.toString()?.let {
viewModel.processEvent(WebViewEvent.OnUrlLoading(it))
}
return true
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
viewModel.processEvent(WebViewEvent.OnPageFinished(url ?: ""))
viewModel.processEvent(WebViewEvent.UpdateTitle(webView.title ?: ""))
}
}
webView.setOnTouchListener { _, event ->
handleTouchEvent(event)
}
// Cargar URL inicial si existe
// if (state.value.initialUrl.isNotEmpty()) {
// }
}
private fun handleTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
velocityTracker?.clear()
velocityTracker = velocityTracker ?: VelocityTracker.obtain()
velocityTracker?.addMovement(event)
}
MotionEvent.ACTION_MOVE -> {
velocityTracker?.addMovement(event)
}
MotionEvent.ACTION_UP -> {
velocityTracker?.let { tracker ->
tracker.computeCurrentVelocity(1000)
val velocityX = tracker.xVelocity
val velocityY = abs(tracker.yVelocity)
if (velocityX > FLING_THRESHOLD_VELOCITY &&
velocityY < FLING_THRESHOLD_VERTICAL &&
webView?.canGoBack() == true
) {
viewModel.processEvent(WebViewEvent.OnBackGesture)
return true
}
}
}
MotionEvent.ACTION_CANCEL -> {
velocityTracker?.recycle()
velocityTracker = null
}
}
return false
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!onBackPressedHandled()) {
isEnabled = false
requireActivity().onBackPressedDispatcher
}
}
})
val dataScanned = JSONObject(entryPoint)
viewModel.setInitialUrl(dataScanned.get("web").toString())
}
override fun onDestroy() {
super.onDestroy()
velocityTracker?.recycle()
velocityTracker = null
webView = null
}
override fun onBackPressedHandled(): Boolean {
viewModel.processEvent(WebViewEvent.OnBackGesture)
/* if (webView!!.canGoBack()) {
webView!!.goBack()
} else {
(context as MainActivity).onMyBackPressed()
}*/
return true
}
}

View File

@ -0,0 +1,64 @@
package es.verdnatura.presentation.view.commom.webview
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import es.verdnatura.presentation.view.commom.webview.data.WebViewEvent
import es.verdnatura.presentation.view.commom.webview.data.WebViewState
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class WebViewViewModel : ViewModel() {
private val _state = MutableStateFlow(WebViewState())
val state = _state.asStateFlow()
private val _webViewActions = Channel<WebViewEvent>()
val webViewActions = _webViewActions.receiveAsFlow()
fun processEvent(event: WebViewEvent) {
when (event) {
is WebViewEvent.LoadUrl -> {
_state.update { it.copy(url = event.url, isLoading = true) }
viewModelScope.launch {
_webViewActions.send(event)
}
}
is WebViewEvent.UpdateTitle -> {
_state.update { it.copy(title = event.title) }
}
is WebViewEvent.OnPageFinished -> {
_state.update { it.copy(isLoading = false) }
}
is WebViewEvent.ToggleToolbar -> {
_state.update { it.copy(showToolbar = event.show) }
}
is WebViewEvent.OnBackGesture -> {
viewModelScope.launch {
_webViewActions.send(event)
}
}
is WebViewEvent.OnUrlLoading -> {
processEvent(WebViewEvent.LoadUrl(event.url))
}
}
}
fun setInitialUrl(webURL: String) {
try {
println("webURL $webURL")
_state.update { it.copy(initialUrl = webURL) }
processEvent(WebViewEvent.LoadUrl("https://salix.verdnatura.es"))
} catch (e: Exception) {
// Manejar error
}
}
}

View File

@ -0,0 +1,67 @@
package es.verdnatura.presentation.view.commom.webview.data
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WebViewScreen(
state: WebViewState,
onWebViewCreated: (WebView) -> Unit
) {
Surface(
modifier = Modifier.fillMaxSize() // Asegura que ocupe toda la pantalla
) {
Box(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding() // Evita que la WebView quede debajo de la barra de estado
) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
WebView(context).apply {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
loadWithOverviewMode = true
useWideViewPort = true
builtInZoomControls = true
displayZoomControls = false
setSupportZoom(true)
allowFileAccess = true
}
webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
}
}
onWebViewCreated(this)
}
},
update = { webView ->
if (state.url.isNotEmpty() && state.url != webView.url) {
webView.loadUrl(state.url)
}
}
)
if (state.isLoading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}

View File

@ -0,0 +1,18 @@
package es.verdnatura.presentation.view.commom.webview.data
data class WebViewState(
val title: String = "",
val url: String = "",
val isLoading: Boolean = false,
val showToolbar: Boolean = true,
val initialUrl: String = ""
)
sealed class WebViewEvent {
data class LoadUrl(val url: String) : WebViewEvent()
data class UpdateTitle(val title: String) : WebViewEvent()
data class OnPageFinished(val url: String) : WebViewEvent()
data class ToggleToolbar(val show: Boolean) : WebViewEvent()
data object OnBackGesture : WebViewEvent()
data class OnUrlLoading(val url: String) : WebViewEvent()
}

View File

@ -0,0 +1,66 @@
package es.verdnatura.presentation.view.feature.buscaritem.fragment
import es.verdnatura.domain.SalixCallback
import es.verdnatura.domain.userCases.GetItemFromBarcodeUseCase
import es.verdnatura.presentation.base.BaseViewModelCompose
import es.verdnatura.presentation.view.feature.buscaritem.model.ItemShelvings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import retrofit2.Response
data class UiState(
val itemFk: String? = null,
val items: List<ItemShelvings> = emptyList()
)
class BuscarItemComposeViewModel(var application: android.app.Application) :
BaseViewModelCompose(application) {
private val getItemFromBarcodeUseCase = GetItemFromBarcodeUseCase(salix)
private val _uiState = MutableStateFlow(UiState())
val uiState = _uiState.asStateFlow()
/* private val _itemFk = MutableStateFlow<String?>(null)
val itemFk = _itemFk.asStateFlow()*/
/* private val _itemShelvingsList = MutableStateFlow<ItemShelvingsList?>(null)
val itemShelvingsList = _itemShelvingsList.asStateFlow()*/
/*val loadItemShelvingsList = _itemShelvingsList.map {
it?.let { Event(it) }
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = null
)*/
fun getIdFromCodeSalix(code: String) {
_uiState.update { it.copy(itemFk = code) }
//_itemFk.value = code
getItemFromBarcodeUseCase.execute(code)
.enqueue(object : SalixCallback<Int?>(application.applicationContext) {
override fun onSuccess(response: Response<Int?>) {
response.body()?.let { id ->
itemshelvingsGet(id)
}
}
})
}
fun itemshelvingsGet(itemFk: Any) {
salix.itemShelvingsGet(
filter = """{"where":{"itemFk":$itemFk},"fields":["created","visible","available","shelvingFk"],
|"include":[{"relation":"shelving","scope":{"fields":["code","priority","parkingFk"],
|"include":{"relation":"parking","scope":{"fields":["code","sectorFk"]}}}}]}""".trimMargin()
)
.enqueue(object : SalixCallback<List<ItemShelvings>>(application.applicationContext) {
override fun onSuccess(response: Response<List<ItemShelvings>>) {
response.body()?.let { list ->
//_itemShelvingsList.value = ItemShelvingsList(list)
_uiState.update { it.copy(items = list) }
}
}
})
}
}

View File

@ -31,7 +31,7 @@ class BuscarItemFragment(
override fun getLayoutId(): Int = R.layout.fragment_buscar_item
override fun init() {
binding.mainToolbar.toolbarTitle.text = getString(R.string.getubicaition)
binding.mainToolbar.toolbarTitle.text = getString(R.string.getubication)
setEvents()
if (itemFk != null) {
viewModel.getIdFromCodeSalix(itemFk!!.toString())
@ -72,7 +72,7 @@ class BuscarItemFragment(
override fun observeViewModel() {
with(viewModel) {
loadItemShelvingsList.observe(viewLifecycleOwner) { event ->
event.getContentIfNotHandled().notNull { itemResponse ->

View File

@ -0,0 +1,118 @@
package es.verdnatura.presentation.view.feature.buscaritem.fragment
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import es.verdnatura.R
import es.verdnatura.domain.toast
import es.verdnatura.presentation.common.OnPasillerosItemClickListener
import es.verdnatura.presentation.view.feature.main.activity.MainActivity
import es.verdnatura.presentation.view.feature.pasillero.model.PasillerosItemVO
import org.koin.androidx.viewmodel.ext.android.viewModel
class BuscarItemFragmentCompose(
var itemFk: Any? = null
) : Fragment() {
private var pasillerosItemClickListener: OnPasillerosItemClickListener? = null
private val viewModel: BuscarItemComposeViewModel by viewModel()
companion object {
fun newInstance(entryPoint: Int?) = BuscarItemFragmentCompose(entryPoint)
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnPasillerosItemClickListener) pasillerosItemClickListener = context
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
SetView()
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemFk?.let {
viewModel.getIdFromCodeSalix(it.toString())
}
}
private fun showSector(item: LocationItem) {
item.sector.toast(requireContext())
}
private fun openUbicadorFragment(item: LocationItem) {
pasillerosItemClickListener!!.onPasillerosItemClickListener(
PasillerosItemVO(
title =
R.string.titleUbicator
), item.matricula
)
}
@Composable
private fun SetView() {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LocationScreen(
items = uiState.items.map {
LocationItem(
parking = it.shelving.parking.code,
matricula = it.shelving.code,
visible = it.visible.toString(),
reserve = it.available.toString(),
priority = it.shelving.priority.toString(),
fecha = it.created,
sector = it.shelving.parking.sector.description
)
},
onTextChange = { input ->
viewModel.getIdFromCodeSalix(input)
},
titleToolBar = if (uiState.items.isNotEmpty()) {
getString(
R.string.itemsTotal,
uiState.itemFk,
getString(R.string.visibleTotal),
uiState.items.sumOf { it.visible }
)
} else getString(R.string.getubication),
onBackClick = { (context as MainActivity).onMyBackPressed() },
onLongClick = { item -> item.sector.toast(requireContext()) },
onClick = { item ->
pasillerosItemClickListener?.onPasillerosItemClickListener(
PasillerosItemVO(title = R.string.titleUbicator),
item.matricula
)
}
)
}
}
/*
class BuscarItemViewModelFactory(private val context: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(BuscarItemComposeViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return BuscarItemComposeViewModel(context) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}*/

View File

@ -0,0 +1,207 @@
package es.verdnatura.presentation.view.feature.buscaritem.fragment
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import es.verdnatura.R
import es.verdnatura.presentation.composable.CustomToolbar
import es.verdnatura.presentation.composable.ScanLineTextSearch
@Composable
fun LocationScreen(
//viewModel: BuscarItemComposeViewModel = viewModel(), // Falta ver de arreglar con koin o viewmodel → No necesario, en el Fragment observa
items: List<LocationItem>,
onTextChange: (String) -> Unit,
titleToolBar: String,
onBackClick: () -> Unit = {},
modifier: Modifier = Modifier,
onLongClick: (LocationItem) -> Unit,
onClick: (LocationItem) -> Unit
) {
var searchText by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
var showLoading by remember { mutableStateOf(false) }
val onImeAction: () -> Unit = {
onTextChange(searchText)
searchText = ""
keyboardController?.hide()
}
Box(
modifier = modifier
.fillMaxSize()
.background(Color.Black)
) {
Column(
modifier = modifier
.fillMaxSize()
.background(Color.Black)
) {
if (showLoading) {
LottieLoadingAnimation(true)
}
CustomToolbar(
title = titleToolBar,
subtitle = "",
showBackButton = true,
showSwitch = false,
iconList = listOf(),
onBackClick = onBackClick,
onSwitchChange = { },
)
Spacer(modifier = Modifier.height(8.dp))
ScanLineTextSearch(
value = searchText,
onValueChange = {
searchText = it
showLoading = true
},
onImeAction,
modifier = Modifier
.focusRequester(focusRequester)
.onFocusEvent {
if (it.isFocused) {
keyboardController?.hide() // Asegura que el teclado no se abra
}
}
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
keyboardController?.hide()
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.Start
) {
listOf(
R.string.parking,
R.string.Matrícula,
R.string.Visible,
R.string.reserve,
R.string.priority,
R.string.Fecha
).forEach { textRes ->
Text(
text = stringResource(id = textRes),
color = Color.White,
fontSize = 14.sp,
modifier = Modifier.weight(1f)
)
}
}
HorizontalDivider(thickness = 1.dp, color = Color.White)
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(items) { item ->
LocationRow(
item = item,
onLongClick = { selectedItem ->
onLongClick(selectedItem)
},
onClick = { selectedItem ->
onClick(selectedItem)
},
)
HorizontalDivider(thickness = 1.dp, color = Color.White)
}
}
showLoading = false
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LocationRow(
item: LocationItem, onLongClick: (LocationItem) -> Unit, onClick: (LocationItem) -> Unit
) {
Row(modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
.combinedClickable(onClick = { onClick(item) }, onLongClick = { onLongClick(item) }
), horizontalArrangement = Arrangement.SpaceEvenly) {
Text(text = item.parking, color = Color.White, modifier = Modifier.weight(1f))
Text(text = item.matricula, color = Color.White, modifier = Modifier.weight(1f))
Text(text = item.visible, color = Color.White, modifier = Modifier.weight(1f))
Text(text = item.reserve, color = Color.White, modifier = Modifier.weight(1f))
Text(text = item.priority, color = Color.White, modifier = Modifier.weight(1f))
Text(
text = item.fecha,
color = Color.White,
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center
)
}
}
// Modelo de datos
data class LocationItem(
val parking: String,
val matricula: String,
val visible: String,
val reserve: String,
val priority: String,
val fecha: String,
val sector: String
)
// Vista previa
@Preview(showBackground = true)
@Composable
fun PreviewLocationScreen() {
LocationScreen(
// viewModel = null,
items = listOf(
LocationItem("P1", "1234ABC", "", "No", "Alta", "12/02/2025", "previa"),
LocationItem("P2", "5678DEF", "No", "", "Baja", "13/02/2025", "previa")
),
onTextChange = {},
titleToolBar = "Buscar Item",
onBackClick = {},
onLongClick = {},
onClick = {})
}

View File

@ -0,0 +1,38 @@
package es.verdnatura.presentation.view.feature.buscaritem.fragment
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import es.verdnatura.R
@Composable
fun LottieLoadingAnimation(isVisible: Boolean = true) {
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(),
exit = fadeOut()
) {
val composition by rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.orange_loading)
)
LottieAnimation(
composition = composition,
iterations = LottieConstants.IterateForever,
speed = 2f,
modifier = Modifier
.wrapContentWidth()
.height(dimensionResource(id = R.dimen.verdnatura_logo_large_height))
)
}
}

View File

@ -17,6 +17,8 @@ buildscript {
}
plugins {
alias(libs.plugins.google.devtools.ksp) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.compose.compiler) apply false
/*alias(libs.plugins.jetbrainsCompose) apply false
alias(libs.plugins.compose.compiler) apply false*/
}