From 5d600359fa8db3ec54d6e46cd5317e7d1d27f84b Mon Sep 17 00:00:00 2001 From: Sergio De la torre Date: Thu, 20 Feb 2025 09:34:41 +0100 Subject: [PATCH] feat: refs #6659 jetPackCompose --- app/build.gradle.kts | 46 +++- app/src/main/AndroidManifest.xml | 8 + .../presentation/base/BaseViewModel.kt | 21 -- .../presentation/base/BaseViewModelCompose.kt | 13 ++ .../view/commom/webview/WebFragmentCompose.kt | 180 +++++++++++++++ .../view/commom/webview/WebViewViewModel.kt | 64 ++++++ .../commom/webview/data/WebViewComposable.kt | 67 ++++++ .../view/commom/webview/data/WebViewState.kt | 18 ++ .../fragment/BuscarItemComposeViewModel.kt | 66 ++++++ .../buscaritem/fragment/BuscarItemFragment.kt | 4 +- .../fragment/BuscarItemFragmentCompose.kt | 118 ++++++++++ .../buscaritem/fragment/LocationScreen.kt | 207 ++++++++++++++++++ .../fragment/LottieLoadingAnimation.kt | 38 ++++ build.gradle.kts | 2 + 14 files changed, 820 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/es/verdnatura/presentation/base/BaseViewModelCompose.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebFragmentCompose.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebViewViewModel.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewComposable.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewState.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemComposeViewModel.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragmentCompose.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LocationScreen.kt create mode 100644 app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LottieLoadingAnimation.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1ecf925d..7dded68b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cbd2dee9..e69849a6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,12 @@ android:name=".presentation.view.feature.articulo.fragment.ImageViewActivity" android:configChanges="orientation" android:screenOrientation="portrait" /> + + + + \ No newline at end of file diff --git a/app/src/main/java/es/verdnatura/presentation/base/BaseViewModel.kt b/app/src/main/java/es/verdnatura/presentation/base/BaseViewModel.kt index ac1d1d15..361c9079 100644 --- a/app/src/main/java/es/verdnatura/presentation/base/BaseViewModel.kt +++ b/app/src/main/java/es/verdnatura/presentation/base/BaseViewModel.kt @@ -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."}" - -} diff --git a/app/src/main/java/es/verdnatura/presentation/base/BaseViewModelCompose.kt b/app/src/main/java/es/verdnatura/presentation/base/BaseViewModelCompose.kt new file mode 100644 index 00000000..db1ff33e --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/base/BaseViewModelCompose.kt @@ -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 + +} \ No newline at end of file diff --git a/app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebFragmentCompose.kt b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebFragmentCompose.kt new file mode 100644 index 00000000..a8dd2c9a --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebFragmentCompose.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebViewViewModel.kt b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebViewViewModel.kt new file mode 100644 index 00000000..4d14fe6c --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/WebViewViewModel.kt @@ -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() + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewComposable.kt b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewComposable.kt new file mode 100644 index 00000000..1f5dfe98 --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewComposable.kt @@ -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) + ) + } + } + } +} diff --git a/app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewState.kt b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewState.kt new file mode 100644 index 00000000..8aa54bf2 --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/commom/webview/data/WebViewState.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemComposeViewModel.kt b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemComposeViewModel.kt new file mode 100644 index 00000000..6cf2b38c --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemComposeViewModel.kt @@ -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 = 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(null) + val itemFk = _itemFk.asStateFlow()*/ + + /* private val _itemShelvingsList = MutableStateFlow(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(application.applicationContext) { + override fun onSuccess(response: Response) { + 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>(application.applicationContext) { + override fun onSuccess(response: Response>) { + response.body()?.let { list -> + //_itemShelvingsList.value = ItemShelvingsList(list) + _uiState.update { it.copy(items = list) } + } + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragment.kt b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragment.kt index 99e5ab9f..2e884ec1 100644 --- a/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragment.kt +++ b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragment.kt @@ -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 -> diff --git a/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragmentCompose.kt b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragmentCompose.kt new file mode 100644 index 00000000..8ce30617 --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/BuscarItemFragmentCompose.kt @@ -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 create(modelClass: Class): T { + if (modelClass.isAssignableFrom(BuscarItemComposeViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return BuscarItemComposeViewModel(context) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +}*/ diff --git a/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LocationScreen.kt b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LocationScreen.kt new file mode 100644 index 00000000..3cbffbf1 --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LocationScreen.kt @@ -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, + 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", "Sí", "No", "Alta", "12/02/2025", "previa"), + LocationItem("P2", "5678DEF", "No", "Sí", "Baja", "13/02/2025", "previa") + ), + onTextChange = {}, + titleToolBar = "Buscar Item", + onBackClick = {}, + onLongClick = {}, + onClick = {}) + +} diff --git a/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LottieLoadingAnimation.kt b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LottieLoadingAnimation.kt new file mode 100644 index 00000000..eb03b272 --- /dev/null +++ b/app/src/main/java/es/verdnatura/presentation/view/feature/buscaritem/fragment/LottieLoadingAnimation.kt @@ -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)) + ) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 134acf88..c1e79fc7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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*/ }