feat: refs #6659 jetPackCompose
This commit is contained in:
parent
f55bd3768f
commit
5d600359fa
|
@ -5,6 +5,7 @@ plugins {
|
||||||
id("com.google.gms.google-services")
|
id("com.google.gms.google-services")
|
||||||
id("com.google.firebase.crashlytics")
|
id("com.google.firebase.crashlytics")
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
|
id("org.jetbrains.kotlin.plugin.compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -15,8 +16,8 @@ android {
|
||||||
applicationId = "es.verdnatura"
|
applicationId = "es.verdnatura"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 33 // se deja con target si no Play Protect la bloquea
|
targetSdk = 33 // se deja con target si no Play Protect la bloquea
|
||||||
versionCode = 393 //JAF en informatica
|
versionCode = 397 //JAF 393 en informatica
|
||||||
versionName = "25.0"
|
versionName = "25.6"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +84,9 @@ android {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
dataBinding = true
|
dataBinding = true
|
||||||
|
|
||||||
|
compose = true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
@ -142,15 +146,39 @@ android {
|
||||||
implementation(libs.androidx.datastore.core)
|
implementation(libs.androidx.datastore.core)
|
||||||
implementation(libs.zxing.android.embedded) { isTransitive = false }
|
implementation(libs.zxing.android.embedded) { isTransitive = false }
|
||||||
implementation(libs.core)
|
implementation(libs.core)
|
||||||
|
//implementation(libs.koin.androidx.compose)
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
//implementation(libs.androidx.compose.bom)
|
dependencies {
|
||||||
/* implementation("androidx.compose.ui:ui")
|
|
||||||
implementation("androidx.compose.material:material")
|
val composeBom = platform("androidx.compose:compose-bom:2024.10.01")
|
||||||
implementation("androidx.compose.runtime:runtime")
|
implementation(composeBom)
|
||||||
implementation("androidx.activity:activity-compose:1.8.2")
|
androidTestImplementation(composeBom)
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview") */
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,12 @@
|
||||||
android:name=".presentation.view.feature.articulo.fragment.ImageViewActivity"
|
android:name=".presentation.view.feature.articulo.fragment.ImageViewActivity"
|
||||||
android:configChanges="orientation"
|
android:configChanges="orientation"
|
||||||
android:screenOrientation="portrait" />
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".presentation.composable.ImageViewActivityComposable"
|
||||||
|
android:configChanges="orientation"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".presentation.view.feature.restaurant.RestaurantActivity"
|
android:name=".presentation.view.feature.restaurant.RestaurantActivity"
|
||||||
android:configChanges="orientation"
|
android:configChanges="orientation"
|
||||||
|
@ -69,6 +75,8 @@
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import es.verdnatura.MobileApplication
|
import es.verdnatura.MobileApplication
|
||||||
import es.verdnatura.domain.SalixService
|
import es.verdnatura.domain.SalixService
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
abstract class BaseViewModel : ViewModel, LifecycleObserver {
|
abstract class BaseViewModel : ViewModel, LifecycleObserver {
|
||||||
lateinit var app: MobileApplication
|
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."}"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ class BuscarItemFragment(
|
||||||
override fun getLayoutId(): Int = R.layout.fragment_buscar_item
|
override fun getLayoutId(): Int = R.layout.fragment_buscar_item
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
binding.mainToolbar.toolbarTitle.text = getString(R.string.getubicaition)
|
binding.mainToolbar.toolbarTitle.text = getString(R.string.getubication)
|
||||||
setEvents()
|
setEvents()
|
||||||
if (itemFk != null) {
|
if (itemFk != null) {
|
||||||
viewModel.getIdFromCodeSalix(itemFk!!.toString())
|
viewModel.getIdFromCodeSalix(itemFk!!.toString())
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}*/
|
|
@ -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", "Sí", "No", "Alta", "12/02/2025", "previa"),
|
||||||
|
LocationItem("P2", "5678DEF", "No", "Sí", "Baja", "13/02/2025", "previa")
|
||||||
|
),
|
||||||
|
onTextChange = {},
|
||||||
|
titleToolBar = "Buscar Item",
|
||||||
|
onBackClick = {},
|
||||||
|
onLongClick = {},
|
||||||
|
onClick = {})
|
||||||
|
|
||||||
|
}
|
|
@ -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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ buildscript {
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.google.devtools.ksp) apply false
|
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.jetbrainsCompose) apply false
|
||||||
alias(libs.plugins.compose.compiler) apply false*/
|
alias(libs.plugins.compose.compiler) apply false*/
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue