feat(mobile): match 100% web functionality and aesthetic for DripZone native Kotlin app
- Integrated requested splash screen dripzone_splash_clean_1787718656035 with SplashActivity - Aligned dark streetwear palette: #0A0A0A, purple #9333EA, fuchsia #D946EF, glow borders - Added CitySelectorBottomSheet with Colombian cities, delivery times, and 1st free exchange guarantee - Implemented multi-product CartManager, CartBottomSheet, breakdown and WhatsApp checkout - Updated ProductDetailBottomSheet with size chips, Add to Cart, and direct WhatsApp buy - Added MobileBottomDock with 5 tabs and real-time cart badge counter - Hardened security: network security config (cleartext blocked), encrypted preferences, tapjacking protection
@ -28,6 +28,20 @@
|
||||
android:theme="@style/Theme.DripZone"
|
||||
tools:targetApi="31">
|
||||
|
||||
<!-- Splash Activity: Entry point featuring dripzone_splash_clean -->
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:theme="@style/Theme.DripZone.Splash"
|
||||
android:screenOrientation="portrait"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Main Store Activity -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
@ -36,14 +50,7 @@
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
android:exported="false" />
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
package co.dripzone.app
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import co.dripzone.app.api.ApiClient
|
||||
import co.dripzone.app.databinding.ActivityMainBinding
|
||||
import co.dripzone.app.model.CartManager
|
||||
import co.dripzone.app.model.Category
|
||||
import co.dripzone.app.model.City
|
||||
import co.dripzone.app.model.Product
|
||||
import co.dripzone.app.security.SecurityManager
|
||||
import co.dripzone.app.ui.CartBottomSheet
|
||||
import co.dripzone.app.ui.CategoryAdapter
|
||||
import co.dripzone.app.ui.CitySelectorBottomSheet
|
||||
import co.dripzone.app.ui.CustomOrderBottomSheet
|
||||
import co.dripzone.app.ui.ProductAdapter
|
||||
import co.dripzone.app.ui.ProductDetailBottomSheet
|
||||
@ -28,20 +34,39 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var categoryAdapter: CategoryAdapter
|
||||
|
||||
private var allProducts: List<Product> = emptyList()
|
||||
private var availableCities: List<City> = City.defaultCities()
|
||||
private var selectedCity: City = City.defaultCities().first()
|
||||
private var selectedCategory: String = "Todos"
|
||||
private var searchQuery: String = ""
|
||||
|
||||
private val cartListener = {
|
||||
updateCartBadges()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
CartManager.init(this)
|
||||
CartManager.addListener(cartListener)
|
||||
|
||||
loadSavedCity()
|
||||
setupRecyclerViews()
|
||||
setupCategories()
|
||||
setupSearch()
|
||||
setupListeners()
|
||||
setupCitySelector()
|
||||
setupBottomDock()
|
||||
setupHeaderListeners()
|
||||
|
||||
updateCartBadges()
|
||||
loadProducts(forceRefresh = false)
|
||||
loadCities()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
CartManager.removeListener(cartListener)
|
||||
}
|
||||
|
||||
private fun setupRecyclerViews() {
|
||||
@ -51,21 +76,8 @@ class MainActivity : AppCompatActivity() {
|
||||
.show(supportFragmentManager, ProductDetailBottomSheet.TAG)
|
||||
},
|
||||
onQuickOrderClick = { product ->
|
||||
val whatsappMsg = buildString {
|
||||
append("¡Hola DripZone! 🔥 Quiero pedir este producto con *Pago Contraentrega*:\n\n")
|
||||
append("👟 *${product.name}*\n")
|
||||
append("🏷️ *Marca:* ${product.brand}\n")
|
||||
append("💰 *Precio:* ${product.formattedPrice()}\n")
|
||||
if (product.firstImage().isNotBlank()) {
|
||||
append("🔗 *Foto:* ${product.firstImage()}\n")
|
||||
}
|
||||
append("\n¿Tienen disponibilidad para envío inmediato?")
|
||||
}
|
||||
SecurityManager.openWhatsApp(
|
||||
this,
|
||||
SecurityManager.WHATSAPP_STORE_PHONE,
|
||||
whatsappMsg
|
||||
)
|
||||
ProductDetailBottomSheet.newInstance(product)
|
||||
.show(supportFragmentManager, ProductDetailBottomSheet.TAG)
|
||||
}
|
||||
)
|
||||
|
||||
@ -96,15 +108,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private fun setupSearch() {
|
||||
binding.btnToggleSearch.setOnClickListener {
|
||||
if (binding.layoutSearchBar.visibility == View.VISIBLE) {
|
||||
binding.layoutSearchBar.visibility = View.GONE
|
||||
binding.etSearch.text?.clear()
|
||||
hideKeyboard()
|
||||
} else {
|
||||
binding.layoutSearchBar.visibility = View.VISIBLE
|
||||
binding.etSearch.requestFocus()
|
||||
showKeyboard()
|
||||
}
|
||||
toggleSearch()
|
||||
}
|
||||
|
||||
binding.btnClearSearch.setOnClickListener {
|
||||
@ -142,28 +146,170 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
// Swipe to Refresh: fetches newest drops published from WhatsApp supplier groups
|
||||
binding.swipeRefresh.setColorSchemeResources(R.color.accent_green)
|
||||
private fun toggleSearch() {
|
||||
if (binding.layoutSearchBar.visibility == View.VISIBLE) {
|
||||
binding.layoutSearchBar.visibility = View.GONE
|
||||
binding.etSearch.text?.clear()
|
||||
hideKeyboard()
|
||||
} else {
|
||||
binding.layoutSearchBar.visibility = View.VISIBLE
|
||||
binding.etSearch.requestFocus()
|
||||
showKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCitySelector() {
|
||||
updateCityDisplay()
|
||||
binding.btnChangeCityBar.setOnClickListener {
|
||||
openCitySelector()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openCitySelector() {
|
||||
CitySelectorBottomSheet.newInstance(
|
||||
cities = availableCities,
|
||||
selectedCity = selectedCity
|
||||
) { city ->
|
||||
selectedCity = city
|
||||
saveSelectedCity(city)
|
||||
updateCityDisplay()
|
||||
}.show(supportFragmentManager, CitySelectorBottomSheet.TAG)
|
||||
}
|
||||
|
||||
private fun updateCityDisplay() {
|
||||
val freeExchangeText = if (selectedCity.firstExchangeFree) " • 1er Cambio Gratis" else ""
|
||||
binding.tvHeaderCityInfo.text = "Enviando a ${selectedCity.name} • ${selectedCity.deliveryTime}$freeExchangeText"
|
||||
}
|
||||
|
||||
private fun loadSavedCity() {
|
||||
try {
|
||||
val prefs = SecurityManager.getSecurePreferences(this)
|
||||
val cityName = prefs.getString("saved_city_name", null)
|
||||
if (!cityName.isNullOrBlank()) {
|
||||
val found = availableCities.find { it.name.equals(cityName, ignoreCase = true) }
|
||||
if (found != null) {
|
||||
selectedCity = found
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSelectedCity(city: City) {
|
||||
try {
|
||||
val prefs = SecurityManager.getSecurePreferences(this)
|
||||
prefs.edit().putString("saved_city_name", city.name).apply()
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBottomDock() {
|
||||
// 1. Inicio
|
||||
binding.navBtnHome.setOnClickListener {
|
||||
setDockActive("home")
|
||||
binding.rvProducts.smoothScrollToPosition(0)
|
||||
}
|
||||
|
||||
// 2. Buscar
|
||||
binding.navBtnSearch.setOnClickListener {
|
||||
setDockActive("search")
|
||||
toggleSearch()
|
||||
}
|
||||
|
||||
// 3. Carrito
|
||||
binding.navBtnCart.setOnClickListener {
|
||||
openCart()
|
||||
}
|
||||
|
||||
// 4. Encargos
|
||||
binding.navBtnEncargos.setOnClickListener {
|
||||
CustomOrderBottomSheet.newInstance()
|
||||
.show(supportFragmentManager, CustomOrderBottomSheet.TAG)
|
||||
}
|
||||
|
||||
// 5. WhatsApp Asesor
|
||||
binding.navBtnWhatsApp.setOnClickListener {
|
||||
openGeneralWhatsApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDockActive(tab: String) {
|
||||
val fuchsia = ContextCompat.getColor(this, R.color.drip_fuchsia)
|
||||
val muted = ContextCompat.getColor(this, R.color.text_secondary)
|
||||
|
||||
binding.ivNavHome.setColorFilter(if (tab == "home") fuchsia else muted)
|
||||
binding.tvNavHome.setTextColor(if (tab == "home") fuchsia else muted)
|
||||
|
||||
binding.ivNavSearch.setColorFilter(if (tab == "search") fuchsia else muted)
|
||||
binding.tvNavSearch.setTextColor(if (tab == "search") fuchsia else muted)
|
||||
}
|
||||
|
||||
private fun setupHeaderListeners() {
|
||||
// Header Cart icon
|
||||
binding.btnHeaderCart.setOnClickListener {
|
||||
openCart()
|
||||
}
|
||||
|
||||
// Header WhatsApp button
|
||||
binding.btnHeaderWhatsApp.setOnClickListener {
|
||||
openGeneralWhatsApp()
|
||||
}
|
||||
|
||||
// SwipeRefresh: pulls latest live drops from suppliers
|
||||
binding.swipeRefresh.setColorSchemeResources(R.color.drip_fuchsia, R.color.drip_purple)
|
||||
binding.swipeRefresh.setProgressBackgroundColorSchemeResource(R.color.bg_card)
|
||||
binding.swipeRefresh.setOnRefreshListener {
|
||||
loadProducts(forceRefresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
// WhatsApp direct chat from header
|
||||
binding.btnHeaderWhatsApp.setOnClickListener {
|
||||
val generalInquiry = "¡Hola DripZone! 🔥 Me comunico desde la aplicación móvil. Quiero información sobre los productos y envíos contraentrega."
|
||||
SecurityManager.openWhatsApp(
|
||||
this,
|
||||
SecurityManager.WHATSAPP_STORE_PHONE,
|
||||
generalInquiry
|
||||
)
|
||||
private fun openCart() {
|
||||
CartBottomSheet.newInstance(selectedCity) {
|
||||
openCitySelector()
|
||||
}.show(supportFragmentManager, CartBottomSheet.TAG)
|
||||
}
|
||||
|
||||
private fun openGeneralWhatsApp() {
|
||||
val inquiry = "¡Hola DripZone! 🔥 Me comunico desde la app móvil. Quiero información sobre los productos disponibles para entrega contraentrega en ${selectedCity.name}."
|
||||
SecurityManager.openWhatsApp(
|
||||
this,
|
||||
SecurityManager.WHATSAPP_STORE_PHONE,
|
||||
inquiry
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateCartBadges() {
|
||||
val count = CartManager.getTotalCount()
|
||||
if (count > 0) {
|
||||
binding.tvHeaderCartBadge.visibility = View.VISIBLE
|
||||
binding.tvHeaderCartBadge.text = count.toString()
|
||||
|
||||
binding.tvBottomCartBadge.visibility = View.VISIBLE
|
||||
binding.tvBottomCartBadge.text = count.toString()
|
||||
} else {
|
||||
binding.tvHeaderCartBadge.visibility = View.GONE
|
||||
binding.tvBottomCartBadge.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Floating Action Button: Encargos Especiales
|
||||
binding.fabCustomOrder.setOnClickListener {
|
||||
CustomOrderBottomSheet.newInstance()
|
||||
.show(supportFragmentManager, CustomOrderBottomSheet.TAG)
|
||||
private fun loadCities() {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val fetched = ApiClient.getCities(this@MainActivity)
|
||||
if (fetched.isNotEmpty()) {
|
||||
availableCities = fetched
|
||||
val currentCityId = selectedCity.id
|
||||
val matched = availableCities.find { it.id == currentCityId }
|
||||
if (matched != null) {
|
||||
selectedCity = matched
|
||||
}
|
||||
updateCityDisplay()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Keep default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,17 +327,17 @@ class MainActivity : AppCompatActivity() {
|
||||
if (forceRefresh) {
|
||||
Snackbar.make(
|
||||
binding.rootCoordinator,
|
||||
"Catálogo actualizado con los últimos drops",
|
||||
"Catálogo sincronizado con los últimos drops de WhatsApp",
|
||||
Snackbar.LENGTH_SHORT
|
||||
).setBackgroundTint(getColor(R.color.bg_card_elevated))
|
||||
.setTextColor(getColor(R.color.accent_green))
|
||||
.setTextColor(getColor(R.color.drip_fuchsia))
|
||||
.show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Snackbar.make(
|
||||
binding.rootCoordinator,
|
||||
"Cargando catálogo en modo sin conexión",
|
||||
Snackbar.LENGTH_LONG
|
||||
"Catálogo cargado en modo sin conexión",
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
} finally {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
@ -242,12 +388,12 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun showKeyboard() {
|
||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
imm?.showSoftInput(binding.etSearch, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
imm?.hideSoftInputFromWindow(binding.etSearch.windowToken, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package co.dripzone.app
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import co.dripzone.app.databinding.ActivitySplashBinding
|
||||
|
||||
@SuppressLint("CustomSplashScreen")
|
||||
class SplashActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivitySplashBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivitySplashBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Smooth 1.2s delay to showcase DripZone clean splash screen branding
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
val intent = Intent(this@SplashActivity, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
}, 1200)
|
||||
}
|
||||
}
|
||||
@ -162,4 +162,32 @@ object ApiClient {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getCities(context: Context): List<co.dripzone.app.model.City> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val request = Request.Builder()
|
||||
.url("https://dripzone.almeira.com.co/api/cities")
|
||||
.header("Accept", "application/json")
|
||||
.build()
|
||||
val response = client.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body?.string()
|
||||
if (!body.isNullOrBlank()) {
|
||||
val obj = gson.fromJson(body, com.google.gson.JsonObject::class.java)
|
||||
if (obj.has("cities")) {
|
||||
val listType = object : TypeToken<List<co.dripzone.app.model.City>>() {}.type
|
||||
val list: List<co.dripzone.app.model.City>? = gson.fromJson(obj.get("cities"), listType)
|
||||
if (!list.isNullOrEmpty()) {
|
||||
return@withContext list
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error fetching cities from API: ${e.message}")
|
||||
}
|
||||
co.dripzone.app.model.City.defaultCities()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package co.dripzone.app.model
|
||||
|
||||
import java.io.Serializable
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
data class CartItem(
|
||||
val product: Product,
|
||||
val selectedSize: String,
|
||||
var quantity: Int = 1
|
||||
) : Serializable {
|
||||
|
||||
val itemTotal: Double
|
||||
get() = product.effectivePrice * quantity
|
||||
|
||||
fun formattedItemTotal(): String {
|
||||
return try {
|
||||
val format = NumberFormat.getCurrencyInstance(Locale("es", "CO"))
|
||||
format.maximumFractionDigits = 0
|
||||
format.format(itemTotal) + " COP"
|
||||
} catch (e: Exception) {
|
||||
"$ ${itemTotal.toLong()} COP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package co.dripzone.app.model
|
||||
|
||||
import android.content.Context
|
||||
import co.dripzone.app.security.SecurityManager
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
object CartManager {
|
||||
|
||||
private const val CART_PREFS_KEY = "dripzone_cart_items"
|
||||
private val cartItems = mutableListOf<CartItem>()
|
||||
private val listeners = mutableListOf<() -> Unit>()
|
||||
private val gson = Gson()
|
||||
private var initialized = false
|
||||
|
||||
fun init(context: Context) {
|
||||
if (initialized) return
|
||||
initialized = true
|
||||
try {
|
||||
val prefs = SecurityManager.getSecurePreferences(context)
|
||||
val json = prefs.getString(CART_PREFS_KEY, null)
|
||||
if (!json.isNullOrBlank()) {
|
||||
val type = object : TypeToken<List<CartItem>>() {}.type
|
||||
val loaded: List<CartItem>? = gson.fromJson(json, type)
|
||||
if (loaded != null) {
|
||||
cartItems.clear()
|
||||
cartItems.addAll(loaded)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Safe fallback
|
||||
}
|
||||
}
|
||||
|
||||
private fun persist(context: Context) {
|
||||
try {
|
||||
val prefs = SecurityManager.getSecurePreferences(context)
|
||||
val json = gson.toJson(cartItems)
|
||||
prefs.edit().putString(CART_PREFS_KEY, json).apply()
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
fun addItem(context: Context, product: Product, size: String, quantity: Int = 1) {
|
||||
init(context)
|
||||
val existingIndex = cartItems.indexOfFirst {
|
||||
it.product.id == product.id && it.selectedSize == size
|
||||
}
|
||||
if (existingIndex >= 0) {
|
||||
cartItems[existingIndex].quantity += quantity
|
||||
} else {
|
||||
cartItems.add(CartItem(product = product, selectedSize = size, quantity = quantity))
|
||||
}
|
||||
persist(context)
|
||||
notifyListeners()
|
||||
}
|
||||
|
||||
fun removeItem(context: Context, productId: String, size: String) {
|
||||
init(context)
|
||||
cartItems.removeAll { it.product.id == productId && it.selectedSize == size }
|
||||
persist(context)
|
||||
notifyListeners()
|
||||
}
|
||||
|
||||
fun updateQuantity(context: Context, productId: String, size: String, delta: Int) {
|
||||
init(context)
|
||||
val index = cartItems.indexOfFirst { it.product.id == productId && it.selectedSize == size }
|
||||
if (index >= 0) {
|
||||
val newQty = cartItems[index].quantity + delta
|
||||
if (newQty <= 0) {
|
||||
cartItems.removeAt(index)
|
||||
} else {
|
||||
cartItems[index].quantity = newQty
|
||||
}
|
||||
persist(context)
|
||||
notifyListeners()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear(context: Context) {
|
||||
cartItems.clear()
|
||||
persist(context)
|
||||
notifyListeners()
|
||||
}
|
||||
|
||||
fun getItems(): List<CartItem> = cartItems.toList()
|
||||
|
||||
fun getTotalCount(): Int = cartItems.sumOf { it.quantity }
|
||||
|
||||
fun getSubtotal(): Double = cartItems.sumOf { it.itemTotal }
|
||||
|
||||
fun formattedSubtotal(): String {
|
||||
val total = getSubtotal()
|
||||
return try {
|
||||
val format = NumberFormat.getCurrencyInstance(Locale("es", "CO"))
|
||||
format.maximumFractionDigits = 0
|
||||
format.format(total) + " COP"
|
||||
} catch (e: Exception) {
|
||||
"$ ${total.toLong()} COP"
|
||||
}
|
||||
}
|
||||
|
||||
fun addListener(listener: () -> Unit) {
|
||||
if (!listeners.contains(listener)) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeListener(listener: () -> Unit) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
private fun notifyListeners() {
|
||||
listeners.forEach { it.invoke() }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,122 @@
|
||||
package co.dripzone.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.io.Serializable
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
data class City(
|
||||
@SerializedName("id")
|
||||
val id: String = "",
|
||||
|
||||
@SerializedName("name")
|
||||
val name: String = "",
|
||||
|
||||
@SerializedName("department")
|
||||
val department: String = "",
|
||||
|
||||
@SerializedName("shippingCost")
|
||||
val shippingCost: Double = 0.0,
|
||||
|
||||
@SerializedName("deliveryTime")
|
||||
val deliveryTime: String = "24-48 horas",
|
||||
|
||||
@SerializedName("allowsContraentrega")
|
||||
val allowsContraentrega: Boolean = true,
|
||||
|
||||
@SerializedName("firstExchangeFree")
|
||||
val firstExchangeFree: Boolean = false,
|
||||
|
||||
@SerializedName("isActive")
|
||||
val isActive: Boolean = true
|
||||
) : Serializable {
|
||||
|
||||
fun formattedShipping(): String {
|
||||
return if (shippingCost <= 0) {
|
||||
"Envío GRATIS"
|
||||
} else {
|
||||
val format = NumberFormat.getCurrencyInstance(Locale("es", "CO"))
|
||||
format.maximumFractionDigits = 0
|
||||
format.format(shippingCost) + " COP"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun defaultCities(): List<City> {
|
||||
return listOf(
|
||||
City(
|
||||
id = "cali",
|
||||
name = "Cali",
|
||||
department = "Valle del Cauca",
|
||||
shippingCost = 0.0,
|
||||
deliveryTime = "Mismo día / 24h",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = true
|
||||
),
|
||||
City(
|
||||
id = "bogota",
|
||||
name = "Bogotá",
|
||||
department = "Cundinamarca",
|
||||
shippingCost = 15000.0,
|
||||
deliveryTime = "1 a 2 días",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = false
|
||||
),
|
||||
City(
|
||||
id = "medellin",
|
||||
name = "Medellín",
|
||||
department = "Antioquia",
|
||||
shippingCost = 15000.0,
|
||||
deliveryTime = "1 a 2 días",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = false
|
||||
),
|
||||
City(
|
||||
id = "barranquilla",
|
||||
name = "Barranquilla",
|
||||
department = "Atlántico",
|
||||
shippingCost = 18000.0,
|
||||
deliveryTime = "2 a 3 días",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = false
|
||||
),
|
||||
City(
|
||||
id = "bucaramanga",
|
||||
name = "Bucaramanga",
|
||||
department = "Santander",
|
||||
shippingCost = 16000.0,
|
||||
deliveryTime = "2 a 3 días",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = false
|
||||
),
|
||||
City(
|
||||
id = "pereira",
|
||||
name = "Pereira / Eje Cafetero",
|
||||
department = "Risaralda",
|
||||
shippingCost = 14000.0,
|
||||
deliveryTime = "24 a 48 horas",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = false
|
||||
),
|
||||
City(
|
||||
id = "cartagena",
|
||||
name = "Cartagena",
|
||||
department = "Bolívar",
|
||||
shippingCost = 18000.0,
|
||||
deliveryTime = "2 a 3 días",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = false
|
||||
),
|
||||
City(
|
||||
id = "otra",
|
||||
name = "Otro municipio de Colombia",
|
||||
department = "Nacional",
|
||||
shippingCost = 18000.0,
|
||||
deliveryTime = "2 a 4 días",
|
||||
allowsContraentrega = true,
|
||||
firstExchangeFree = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package co.dripzone.app.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import co.dripzone.app.R
|
||||
import co.dripzone.app.databinding.ItemCartItemBinding
|
||||
import co.dripzone.app.model.CartItem
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
|
||||
class CartAdapter(
|
||||
private val items: MutableList<CartItem>,
|
||||
private val onQuantityChanged: (CartItem, Int) -> Unit,
|
||||
private val onItemRemoved: (CartItem) -> Unit
|
||||
) : RecyclerView.Adapter<CartAdapter.CartViewHolder>() {
|
||||
|
||||
inner class CartViewHolder(private val binding: ItemCartItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: CartItem) {
|
||||
val context = binding.root.context
|
||||
|
||||
binding.tvCartName.text = item.product.name
|
||||
binding.tvCartSize.text = "Talla: ${item.selectedSize}"
|
||||
binding.tvCartPrice.text = item.formattedItemTotal()
|
||||
binding.tvQuantity.text = item.quantity.toString()
|
||||
|
||||
val imageUrl = item.product.firstImage()
|
||||
if (imageUrl.isNotBlank()) {
|
||||
Glide.with(context)
|
||||
.load(imageUrl)
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.centerCrop()
|
||||
.placeholder(R.drawable.bg_product_card)
|
||||
.into(binding.ivCartThumb)
|
||||
} else {
|
||||
binding.ivCartThumb.setImageResource(R.drawable.splash)
|
||||
}
|
||||
|
||||
binding.btnPlus.setOnClickListener {
|
||||
onQuantityChanged(item, 1)
|
||||
}
|
||||
|
||||
binding.btnMinus.setOnClickListener {
|
||||
onQuantityChanged(item, -1)
|
||||
}
|
||||
|
||||
binding.btnRemoveCartItem.setOnClickListener {
|
||||
onItemRemoved(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder {
|
||||
val binding = ItemCartItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return CartViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
fun updateData(newItems: List<CartItem>) {
|
||||
items.clear()
|
||||
items.addAll(newItems)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
package co.dripzone.app.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import co.dripzone.app.databinding.BottomSheetCartBinding
|
||||
import co.dripzone.app.model.CartItem
|
||||
import co.dripzone.app.model.CartManager
|
||||
import co.dripzone.app.model.City
|
||||
import co.dripzone.app.security.SecurityManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class CartBottomSheet : BottomSheetDialogFragment() {
|
||||
|
||||
private var _binding: BottomSheetCartBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var cartAdapter: CartAdapter? = null
|
||||
private var selectedCity: City = City.defaultCities().first()
|
||||
private var onOpenCitySelectorListener: (() -> Unit)? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = BottomSheetCartBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.btnCloseCart.setOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
binding.cardChangeCity.setOnClickListener {
|
||||
onOpenCitySelectorListener?.invoke()
|
||||
}
|
||||
|
||||
val items = CartManager.getItems().toMutableList()
|
||||
cartAdapter = CartAdapter(
|
||||
items = items,
|
||||
onQuantityChanged = { item, delta ->
|
||||
CartManager.updateQuantity(requireContext(), item.product.id, item.selectedSize, delta)
|
||||
refreshCartUI()
|
||||
},
|
||||
onItemRemoved = { item ->
|
||||
CartManager.removeItem(requireContext(), item.product.id, item.selectedSize)
|
||||
refreshCartUI()
|
||||
}
|
||||
)
|
||||
binding.rvCartItems.adapter = cartAdapter
|
||||
|
||||
binding.btnCheckoutWhatsApp.setOnClickListener {
|
||||
val cartList = CartManager.getItems()
|
||||
if (cartList.isEmpty()) return@setOnClickListener
|
||||
|
||||
val subtotal = CartManager.getSubtotal()
|
||||
val total = subtotal + selectedCity.shippingCost
|
||||
val balanceDue = if (total > 10000.0) total - 10000.0 else 0.0
|
||||
|
||||
val itemsText = cartList.joinToString("\n") {
|
||||
"👟 *${it.product.name}* (Talla: *${it.selectedSize}*, Cant: ${it.quantity}) - ${it.formattedItemTotal()}"
|
||||
}
|
||||
|
||||
val format = NumberFormat.getCurrencyInstance(Locale("es", "CO"))
|
||||
format.maximumFractionDigits = 0
|
||||
val formattedTotal = format.format(total) + " COP"
|
||||
val formattedBalance = format.format(balanceDue) + " COP"
|
||||
|
||||
val message = buildString {
|
||||
append("¡Hola DripZone! 🔥 Quiero ordenar este pedido desde la app móvil:\n\n")
|
||||
append("📦 *RESUMEN DEL PEDIDO:*\n")
|
||||
append(itemsText)
|
||||
append("\n\n📍 *Ciudad de Despacho:* ${selectedCity.name} (${selectedCity.formattedShipping()})\n")
|
||||
append("💰 *TOTAL A PAGAR:* $formattedTotal\n")
|
||||
append("⚡ *Condición:* Pago Contraentrega (Separo con $10.000 COP y cancelo saldo de $formattedBalance al recibir en mi puerta).\n\n")
|
||||
append("¿Me confirman disponibilidad para programar el despacho?")
|
||||
}
|
||||
|
||||
SecurityManager.openWhatsApp(
|
||||
requireContext(),
|
||||
SecurityManager.WHATSAPP_STORE_PHONE,
|
||||
message
|
||||
)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
refreshCartUI()
|
||||
}
|
||||
|
||||
fun setSelectedCity(city: City) {
|
||||
selectedCity = city
|
||||
if (_binding != null) {
|
||||
refreshCartUI()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshCartUI() {
|
||||
val items = CartManager.getItems()
|
||||
cartAdapter?.updateData(items)
|
||||
|
||||
val count = CartManager.getTotalCount()
|
||||
binding.tvCartCountSubtitle.text = "$count ${if (count == 1) "artículo seleccionado" else "artículos seleccionados"}"
|
||||
|
||||
if (items.isEmpty()) {
|
||||
binding.layoutCartEmpty.visibility = View.VISIBLE
|
||||
binding.layoutCartContent.visibility = View.GONE
|
||||
} else {
|
||||
binding.layoutCartEmpty.visibility = View.GONE
|
||||
binding.layoutCartContent.visibility = View.VISIBLE
|
||||
|
||||
binding.tvCartCityName.text = "Destino: ${selectedCity.name}"
|
||||
binding.tvCartCityInfo.text = "${selectedCity.deliveryTime} • Toca para cambiar ciudad"
|
||||
binding.tvCartShippingFee.text = selectedCity.formattedShipping()
|
||||
|
||||
val subtotal = CartManager.getSubtotal()
|
||||
val shipping = selectedCity.shippingCost
|
||||
val total = subtotal + shipping
|
||||
val balanceDue = if (total > 10000.0) total - 10000.0 else 0.0
|
||||
|
||||
val format = NumberFormat.getCurrencyInstance(Locale("es", "CO"))
|
||||
format.maximumFractionDigits = 0
|
||||
|
||||
binding.tvSubtotal.text = format.format(subtotal) + " COP"
|
||||
binding.tvShippingTotal.text = selectedCity.formattedShipping()
|
||||
binding.tvCartTotalAmount.text = format.format(total) + " COP"
|
||||
binding.tvSeparationBreakdown.text = "Separas tu pedido con $10.000 COP y cancelas el saldo restante (${format.format(balanceDue)} COP) en efectivo al recibir."
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "CartBottomSheet"
|
||||
|
||||
fun newInstance(city: City, onOpenCitySelector: () -> Unit): CartBottomSheet {
|
||||
return CartBottomSheet().apply {
|
||||
this.selectedCity = city
|
||||
this.onOpenCitySelectorListener = onOpenCitySelector
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package co.dripzone.app.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import co.dripzone.app.R
|
||||
import co.dripzone.app.databinding.ItemCityBinding
|
||||
import co.dripzone.app.model.City
|
||||
|
||||
class CityAdapter(
|
||||
private val cities: List<City>,
|
||||
private var selectedCityId: String,
|
||||
private val onCitySelected: (City) -> Unit
|
||||
) : RecyclerView.Adapter<CityAdapter.CityViewHolder>() {
|
||||
|
||||
inner class CityViewHolder(private val binding: ItemCityBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(city: City) {
|
||||
val isSelected = city.id == selectedCityId
|
||||
val context = binding.root.context
|
||||
|
||||
binding.tvCityName.text = city.name
|
||||
binding.tvCityDeliveryTime.text = "${city.deliveryTime} • ${city.department}"
|
||||
binding.tvShippingCost.text = city.formattedShipping()
|
||||
|
||||
if (isSelected) {
|
||||
binding.ivCheck.visibility = View.VISIBLE
|
||||
binding.cardCity.strokeColor = ContextCompat.getColor(context, R.color.drip_fuchsia)
|
||||
binding.cardCity.setCardBackgroundColor(ContextCompat.getColor(context, R.color.bg_card_elevated))
|
||||
} else {
|
||||
binding.ivCheck.visibility = View.INVISIBLE
|
||||
binding.cardCity.strokeColor = ContextCompat.getColor(context, R.color.bg_card_stroke)
|
||||
binding.cardCity.setCardBackgroundColor(ContextCompat.getColor(context, R.color.bg_card))
|
||||
}
|
||||
|
||||
binding.cardCity.setOnClickListener {
|
||||
selectedCityId = city.id
|
||||
notifyDataSetChanged()
|
||||
onCitySelected(city)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CityViewHolder {
|
||||
val binding = ItemCityBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return CityViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: CityViewHolder, position: Int) {
|
||||
holder.bind(cities[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = cities.size
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package co.dripzone.app.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import co.dripzone.app.databinding.BottomSheetCitySelectorBinding
|
||||
import co.dripzone.app.model.City
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
||||
class CitySelectorBottomSheet : BottomSheetDialogFragment() {
|
||||
|
||||
private var _binding: BottomSheetCitySelectorBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var cities: List<City> = emptyList()
|
||||
private var selectedCity: City? = null
|
||||
private var onCitySelectedListener: ((City) -> Unit)? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = BottomSheetCitySelectorBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.btnCloseCity.setOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
val adapter = CityAdapter(
|
||||
cities = cities,
|
||||
selectedCityId = selectedCity?.id ?: "cali"
|
||||
) { city ->
|
||||
onCitySelectedListener?.invoke(city)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
binding.rvCities.adapter = adapter
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "CitySelectorBottomSheet"
|
||||
|
||||
fun newInstance(
|
||||
cities: List<City>,
|
||||
selectedCity: City,
|
||||
onSelected: (City) -> Unit
|
||||
): CitySelectorBottomSheet {
|
||||
return CitySelectorBottomSheet().apply {
|
||||
this.cities = cities
|
||||
this.selectedCity = selectedCity
|
||||
this.onCitySelectedListener = onSelected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,6 +66,18 @@ class ProductDetailBottomSheet : BottomSheetDialogFragment() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
// Add to Cart Button
|
||||
binding.btnAddToCart.setOnClickListener {
|
||||
val sizeToOrder = if (selectedSize.isNotBlank()) selectedSize else sizeAdapter?.getSelectedSize() ?: "38-43"
|
||||
co.dripzone.app.model.CartManager.addItem(requireContext(), p, sizeToOrder, 1)
|
||||
android.widget.Toast.makeText(
|
||||
requireContext(),
|
||||
"¡Agregado al Carrito! (${p.name} - Talla $sizeToOrder)",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
// WhatsApp Order Checkout Button
|
||||
binding.btnOrderWhatsApp.setOnClickListener {
|
||||
val sizeToOrder = if (selectedSize.isNotBlank()) selectedSize else sizeAdapter?.getSelectedSize() ?: "Por coordinar"
|
||||
|
||||
|
After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 473 KiB After Width: | Height: | Size: 384 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 640 KiB |
|
Before Width: | Height: | Size: 990 KiB After Width: | Height: | Size: 640 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 515 KiB After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 705 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 705 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 2.1 MiB |
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="#D946EF"
|
||||
android:endColor="#EC4899"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/badge_drip_bg" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/badge_drip_border" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="#9333EA"
|
||||
android:centerColor="#D946EF"
|
||||
android:endColor="#EC4899"
|
||||
android:type="linear" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#40FFFFFF">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="#9333EA"
|
||||
android:endColor="#D946EF"
|
||||
android:type="linear" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@ -2,7 +2,11 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/accent_green" />
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="#9333EA"
|
||||
android:endColor="#D946EF"
|
||||
android:type="linear" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
@ -2,7 +2,11 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/accent_green" />
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="#9333EA"
|
||||
android:endColor="#D946EF"
|
||||
android:type="linear" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#EDEDED"
|
||||
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#D946EF"
|
||||
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zm0,9.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#EDEDED"
|
||||
android:pathData="M18,6h-2c0,-2.21 -1.79,-4 -4,-4S8,3.79 8,6H6c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zm-6,-2c1.1,0 2,0.9 2,2h-4c0,-1.1 0.9,-2 2,-2zm6,16H6V8h2v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1V8h4v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1V8h2v12z"/>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 515 KiB After Width: | Height: | Size: 1.0 MiB |
@ -14,39 +14,82 @@
|
||||
android:background="@color/bg_dark"
|
||||
app:elevation="0dp">
|
||||
|
||||
<!-- Top Header Bar -->
|
||||
<!-- Top Urgency Web Banner (Purple-Fuchsia-Pink Gradient) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_banner_gradient"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="6dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"
|
||||
android:src="@drawable/ic_sparkle"
|
||||
app:tint="#FFFFFF" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:letterSpacing="0.04"
|
||||
android:text="🔥 ENVÍOS CONTRAENTREGA EN TODO COLOMBIA"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="10sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Main Header Bar -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="8dp">
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="6dp">
|
||||
|
||||
<!-- Brand Logo / Title -->
|
||||
<!-- DripZone Logo (DRIP in White, ZONE in Fuchsia) -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:letterSpacing="0.08"
|
||||
android:text="DRIPZONE"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:letterSpacing="0.08"
|
||||
android:text="DRIP"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="22sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:letterSpacing="0.08"
|
||||
android:text="ZONE"
|
||||
android:textColor="@color/drip_fuchsia"
|
||||
android:textSize="22sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:letterSpacing="0.05"
|
||||
android:text="SNEAKERS & STREETWEAR"
|
||||
android:textColor="@color/accent_green"
|
||||
android:text="ROPA URBANA Y ZAPATILLAS"
|
||||
android:textColor="@color/badge_drip_text"
|
||||
android:textSize="10sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
@ -58,52 +101,130 @@
|
||||
android:layout_height="40dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Buscar"
|
||||
android:src="@drawable/ic_search" />
|
||||
android:src="@drawable/ic_search"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<!-- WhatsApp Direct Support -->
|
||||
<!-- Header Cart Icon with Dynamic Badge -->
|
||||
<FrameLayout
|
||||
android:id="@+id/btnHeaderCart"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_shopping_bag"
|
||||
app:tint="@color/text_primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHeaderCartBadge"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="@drawable/bg_badge_counter"
|
||||
android:gravity="center"
|
||||
android:text="0"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="9sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<!-- Header WhatsApp Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnHeaderWhatsApp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:text="WhatsApp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:text="Asesor"
|
||||
android:textAllCaps="false"
|
||||
android:textSize="12sp"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/accent_whatsapp"
|
||||
app:cornerRadius="12dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:icon="@drawable/ic_whatsapp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="6dp"
|
||||
app:iconSize="16dp" />
|
||||
app:iconPadding="4dp"
|
||||
app:iconSize="14dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Search Input Bar (Visible or Toggleable) -->
|
||||
<!-- City Selector Pill Bar (Matching Web Location Selector) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/btnChangeCityBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/bg_btn_secondary"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="7dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/ic_pin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHeaderCityInfo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:maxLines="1"
|
||||
android:text="Enviando a Cali • Mismo día / 24h • 1er Cambio Gratis"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:text="Cambiar"
|
||||
android:textColor="@color/drip_purple_glow"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Search Input Bar -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutSearchBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/bg_search_input"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp">
|
||||
android:paddingHorizontal="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_search" />
|
||||
android:src="@drawable/ic_search"
|
||||
app:tint="@color/drip_purple_light" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
@ -125,34 +246,6 @@
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Live Drops Banner -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/bg_btn_secondary"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/ic_sparkle" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:letterSpacing="0.04"
|
||||
android:text="⚡ DROPS SINCRONIZADOS • PAGO CONTRAENTREGA"
|
||||
android:textColor="@color/accent_green"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Categories Horizontal Filter -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvCategories"
|
||||
@ -186,7 +279,7 @@
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="80dp"
|
||||
android:paddingBottom="72dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:listitem="@layout/item_product" />
|
||||
@ -205,7 +298,8 @@
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/ic_search" />
|
||||
android:src="@drawable/ic_search"
|
||||
app:tint="@color/drip_fuchsia" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -233,7 +327,7 @@
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Ver todos los productos"
|
||||
android:textAllCaps="false"
|
||||
app:backgroundTint="@color/accent_green"
|
||||
app:backgroundTint="@color/drip_fuchsia"
|
||||
app:cornerRadius="12dp" />
|
||||
</LinearLayout>
|
||||
|
||||
@ -243,28 +337,188 @@
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminateTint="@color/accent_green"
|
||||
android:indeterminateTint="@color/drip_fuchsia"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<!-- Floating Action Button for Custom Encargos / Requests -->
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/fabCustomOrder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Encargos"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/accent_green"
|
||||
app:icon="@drawable/ic_encargos"
|
||||
app:iconPadding="8dp"
|
||||
app:iconTint="#FFFFFF" />
|
||||
<!-- Mobile Bottom Navigation Dock (Matching Web MobileBottomDock) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/bottomDock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="#FA0A0A0A"
|
||||
android:elevation="12dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="8dp">
|
||||
|
||||
<!-- 1. Inicio (Catálogo) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/navBtnHome"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivNavHome"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_home"
|
||||
app:tint="@color/drip_fuchsia" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNavHome"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:text="Inicio"
|
||||
android:textColor="@color/drip_fuchsia"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 2. Buscar -->
|
||||
<LinearLayout
|
||||
android:id="@+id/navBtnSearch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivNavSearch"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_search"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNavSearch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="Buscar"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 3. Carrito con Contador Dinámico -->
|
||||
<LinearLayout
|
||||
android:id="@+id/navBtnCart"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivNavCart"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_shopping_bag"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBottomCartBadge"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:layout_marginEnd="-6dp"
|
||||
android:background="@drawable/bg_badge_counter"
|
||||
android:gravity="center"
|
||||
android:text="0"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="9sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNavCart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="Carrito"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 4. Encargos Especiales -->
|
||||
<LinearLayout
|
||||
android:id="@+id/navBtnEncargos"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivNavEncargos"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_encargos"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNavEncargos"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="Encargos"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 5. WhatsApp Asesor -->
|
||||
<LinearLayout
|
||||
android:id="@+id/navBtnWhatsApp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_whatsapp"
|
||||
app:tint="@color/accent_whatsapp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="Asesor"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/bg_dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivSplash"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="DripZone Splash Screen"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/dripzone_splash_clean" />
|
||||
|
||||
<!-- Subtle bottom indicator -->
|
||||
<ProgressBar
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_marginBottom="48dp"
|
||||
android:indeterminateTint="@color/drip_fuchsia" />
|
||||
|
||||
</FrameLayout>
|
||||
@ -0,0 +1,302 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_bottom_sheet"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<View
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="#40FFFFFF" />
|
||||
|
||||
<!-- Header -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="🛍️ Tu Carrito • DripZone"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartCountSubtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="0 artículos seleccionados"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnCloseCart"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Cerrar"
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="@color/text_secondary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Empty Cart View -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutCartEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="32dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:src="@drawable/ic_truck"
|
||||
app:tint="@color/drip_fuchsia" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:text="Tu carrito está vacío"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Agrega los sneakers o streetwear que más te gusten"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Content when cart has items -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutCartContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Items List -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvCartItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_cart_item" />
|
||||
|
||||
<!-- City Destination Card (Clickable to change city) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardChangeCity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_btn_secondary"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_truck"
|
||||
app:tint="@color/drip_fuchsia" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartCityName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:text="Destino: Cali"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartCityInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Despacho en 24h • Toca para cambiar ciudad"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartShippingFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:text="GRATIS"
|
||||
android:textColor="@color/accent_emerald"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Separation / Contraentrega Policy Banner -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_badge_drip"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:text="⚡ Condición de Pago Contraentrega:"
|
||||
android:textColor="@color/drip_purple_glow"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSeparationBreakdown"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Separas tu pedido con $10.000 COP y cancelas el saldo restante en efectivo al recibir."
|
||||
android:textColor="@color/badge_drip_text"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Totals Breakdown -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Subtotal:"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSubtotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="$ 0 COP"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="13sp" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Envío a tu ciudad:"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvShippingTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="GRATIS"
|
||||
android:textColor="@color/accent_emerald"
|
||||
android:textSize="13sp" />
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:background="@color/bg_card_stroke" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="TOTAL A PAGAR:"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartTotalAmount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="$ 0 COP"
|
||||
android:textColor="@color/drip_purple_glow"
|
||||
android:textSize="18sp" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- WhatsApp Checkout Button with Tapjacking Protection -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnCheckoutWhatsApp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginTop="18dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Pedir Carrito por WhatsApp (Contraentrega)"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/accent_whatsapp"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@drawable/ic_whatsapp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="22dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_bottom_sheet"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<View
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="#40FFFFFF" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="📍 Selecciona tu Ciudad"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Tiempos oficiales y cobertura contraentrega"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnCloseCity"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Cerrar"
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="@color/text_secondary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvCities"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="320dp"
|
||||
android:clipToPadding="false"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_city" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -21,7 +21,7 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="#40FFFFFF" />
|
||||
|
||||
<!-- Header with brand & close button -->
|
||||
<!-- Header with brand badge & close button -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
@ -31,12 +31,12 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/bg_chip_category"
|
||||
android:background="@drawable/bg_badge_drip"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:text="NIKE AIR"
|
||||
android:textColor="@color/accent_green"
|
||||
android:textColor="@color/badge_drip_text"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<ImageButton
|
||||
@ -46,7 +46,8 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Cerrar"
|
||||
android:src="@drawable/ic_close" />
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="@color/text_secondary" />
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Large Product Image -->
|
||||
@ -75,11 +76,11 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="Nike SB Dunk Low Pro 'Chicago'"
|
||||
android:text="Nike SB Dunk Low Pro"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<!-- Price -->
|
||||
<!-- Price (Web Fuchsia/Purple Style) -->
|
||||
<TextView
|
||||
android:id="@+id/tvSheetPrice"
|
||||
android:layout_width="match_parent"
|
||||
@ -87,7 +88,7 @@
|
||||
android:layout_marginTop="6dp"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="$ 280.000 COP"
|
||||
android:textColor="@color/accent_green"
|
||||
android:textColor="@color/drip_purple_glow"
|
||||
android:textSize="22sp" />
|
||||
|
||||
<!-- Trust / Contraentrega Badge -->
|
||||
@ -103,7 +104,8 @@
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_truck" />
|
||||
android:src="@drawable/ic_truck"
|
||||
app:tint="@color/accent_gold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -123,7 +125,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Recibe tu pedido y paga en efectivo al recibir"
|
||||
android:text="Recibe en tu puerta y paga en efectivo al recibir"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
@ -149,14 +151,33 @@
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_size_chip" />
|
||||
|
||||
<!-- Add to Cart Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnAddToCart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Agregar al Carrito"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/drip_purple"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@drawable/ic_encargos"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="20dp" />
|
||||
|
||||
<!-- WhatsApp Checkout Action Button with Tapjacking Protection -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnOrderWhatsApp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginTop="22dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Pedir por WhatsApp (Contraentrega)"
|
||||
android:text="Pedir Directo por WhatsApp (Contraentrega)"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="15sp"
|
||||
@ -178,10 +199,10 @@
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="¿No ves tu talla? Pedir por Encargo"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/accent_green"
|
||||
android:textColor="@color/drip_purple_glow"
|
||||
android:textSize="13sp"
|
||||
app:cornerRadius="14dp"
|
||||
app:strokeColor="@color/accent_green"
|
||||
app:strokeColor="@color/drip_purple_light"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="¿Buscas un modelo exclusivo o una talla específica de nuestros proveedores? Nosotros te lo conseguimos."
|
||||
android:text="¿Buscas un modelo exclusivo o una talla específica de nuestros proveedores? Te lo conseguimos bajo pedido."
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="13sp" />
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusTopStart="12dp"
|
||||
app:boxStrokeColor="@color/accent_green">
|
||||
app:boxStrokeColor="@color/drip_fuchsia">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etCustomModel"
|
||||
@ -71,7 +71,7 @@
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusTopStart="12dp"
|
||||
app:boxStrokeColor="@color/accent_green">
|
||||
app:boxStrokeColor="@color/drip_fuchsia">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etCustomSize"
|
||||
@ -94,7 +94,7 @@
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusTopStart="12dp"
|
||||
app:boxStrokeColor="@color/accent_green">
|
||||
app:boxStrokeColor="@color/drip_fuchsia">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etCustomNotes"
|
||||
@ -112,7 +112,7 @@
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Consultar con Asesor WhatsApp"
|
||||
android:text="Consultar Encargo en WhatsApp"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="15sp"
|
||||
|
||||
130
dripzone-app/android/app/src/main/res/layout/item_cart_item.xml
Normal file
@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
app:cardBackgroundColor="@color/bg_card"
|
||||
app:cardCornerRadius="14dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeColor="@color/bg_card_stroke"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<!-- Product Image -->
|
||||
<ImageView
|
||||
android:id="@+id/ivCartThumb"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:background="#121216"
|
||||
android:contentDescription="Miniatura"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<!-- Info Column -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:maxLines="1"
|
||||
android:text="Nike SB Dunk Low"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartSize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Talla: 41"
|
||||
android:textColor="@color/drip_purple_glow"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCartPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="$ 280.000 COP"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="13sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Stepper + Delete -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnRemoveCartItem"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="end"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Eliminar"
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="@color/text_muted" />
|
||||
|
||||
<!-- Quantity Stepper -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:background="@drawable/bg_btn_secondary"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="4dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnMinus"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Menos"
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQuantity"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPlus"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Más"
|
||||
android:src="@drawable/ic_encargos"
|
||||
app:tint="@color/drip_fuchsia" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
72
dripzone-app/android/app/src/main/res/layout/item_city.xml
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/cardCity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardBackgroundColor="@color/bg_card"
|
||||
app:cardCornerRadius="14dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeColor="@color/bg_card_stroke"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivCheck"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_shield_check"
|
||||
app:tint="@color/drip_fuchsia" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCityName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="Cali"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCityDeliveryTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Despacho en 24h • Valle del Cauca"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Shipping Cost Badge -->
|
||||
<TextView
|
||||
android:id="@+id/tvShippingCost"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_badge_drip"
|
||||
android:fontFamily="sans-serif-bold"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:text="Envío GRATIS"
|
||||
android:textColor="@color/badge_drip_text"
|
||||
android:textSize="11sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@ -32,19 +32,19 @@
|
||||
android:contentDescription="Foto del producto"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<!-- Brand Badge -->
|
||||
<!-- Brand / Edition Badge (Web Purple Style) -->
|
||||
<TextView
|
||||
android:id="@+id/tvBrandBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|start"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/bg_chip_category"
|
||||
android:background="@drawable/bg_badge_drip"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="3dp"
|
||||
android:text="DRIP"
|
||||
android:textColor="@color/accent_green"
|
||||
android:textColor="@color/badge_drip_text"
|
||||
android:textSize="10sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="8dp"
|
||||
android:background="#99000000"
|
||||
android:background="#B3000000"
|
||||
android:paddingHorizontal="6dp"
|
||||
android:paddingVertical="3dp"
|
||||
android:text="CONTRAENTREGA"
|
||||
@ -109,7 +109,7 @@
|
||||
android:layout_weight="1"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="$ 280.000"
|
||||
android:textColor="@color/accent_green"
|
||||
android:textColor="@color/drip_purple_glow"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<!-- Quick Order Button with Tapjacking Protection -->
|
||||
|
||||
@ -1,31 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#10B981</color>
|
||||
<color name="colorPrimaryDark">#059669</color>
|
||||
<color name="colorAccent">#10B981</color>
|
||||
<!-- DripZone Web Brand Colors (from BRANDS.dripzone) -->
|
||||
<color name="colorPrimary">#A855F7</color>
|
||||
<color name="colorPrimaryDark">#7E22CE</color>
|
||||
<color name="colorAccent">#D946EF</color>
|
||||
|
||||
<color name="bg_dark">#0B0B0E</color>
|
||||
<color name="bg_card">#16161A</color>
|
||||
<color name="bg_card_elevated">#22222A</color>
|
||||
<color name="bg_card_stroke">#2D2D38</color>
|
||||
<color name="bg_input">#1A1A22</color>
|
||||
<!-- Web Neutrals and Backgrounds -->
|
||||
<color name="bg_dark">#0A0A0A</color>
|
||||
<color name="bg_card">#171717</color>
|
||||
<color name="bg_card_elevated">#212126</color>
|
||||
<color name="bg_card_stroke">#282830</color>
|
||||
<color name="bg_card_stroke_active">#A855F7</color>
|
||||
<color name="bg_input">#1A1A20</color>
|
||||
|
||||
<color name="accent_green">#10B981</color>
|
||||
<color name="accent_green_glow">#34D399</color>
|
||||
<!-- Web DripZone Purple & Fuchsia Accents -->
|
||||
<color name="drip_purple">#9333EA</color>
|
||||
<color name="drip_purple_light">#A855F7</color>
|
||||
<color name="drip_fuchsia">#D946EF</color>
|
||||
<color name="drip_pink">#EC4899</color>
|
||||
<color name="drip_purple_glow">#C084FC</color>
|
||||
|
||||
<!-- WhatsApp & Utility Colors -->
|
||||
<color name="accent_whatsapp">#25D366</color>
|
||||
<color name="accent_whatsapp_pressed">#1EBE5D</color>
|
||||
<color name="accent_orange">#F97316</color>
|
||||
<color name="accent_gold">#F59E0B</color>
|
||||
<color name="accent_emerald">#10B981</color>
|
||||
|
||||
<color name="text_primary">#FFFFFF</color>
|
||||
<color name="text_secondary">#9CA3AF</color>
|
||||
<color name="text_muted">#6B7280</color>
|
||||
<color name="divider">#27272A</color>
|
||||
<!-- Text Typography -->
|
||||
<color name="text_primary">#EDEDED</color>
|
||||
<color name="text_secondary">#A1A1AA</color>
|
||||
<color name="text_muted">#71717A</color>
|
||||
<color name="divider">#262626</color>
|
||||
|
||||
<color name="badge_exclusive_bg">#2E1065</color>
|
||||
<color name="badge_exclusive_text">#C084FC</color>
|
||||
<!-- Badges matching web config -->
|
||||
<color name="badge_drip_bg">#2E1065</color>
|
||||
<color name="badge_drip_border">#581C87</color>
|
||||
<color name="badge_drip_text">#D8B4FE</color>
|
||||
<color name="badge_stock_bg">#064E3B</color>
|
||||
<color name="badge_stock_text">#34D399</color>
|
||||
<color name="badge_pedido_bg">#1E3A8A</color>
|
||||
<color name="badge_pedido_text">#60A5FA</color>
|
||||
<color name="badge_pedido_text">#93C5FD</color>
|
||||
</resources>
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Base DripZone Native Theme -->
|
||||
<!-- Base DripZone Native Theme matching Web Style -->
|
||||
<style name="Theme.DripZone" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/accent_green</item>
|
||||
<item name="colorPrimary">@color/drip_purple</item>
|
||||
<item name="colorPrimaryVariant">@color/colorPrimaryDark</item>
|
||||
<item name="colorOnPrimary">@color/text_primary</item>
|
||||
|
||||
<item name="colorSecondary">@color/accent_whatsapp</item>
|
||||
<item name="colorSecondaryVariant">@color/accent_whatsapp_pressed</item>
|
||||
<item name="colorSecondary">@color/drip_fuchsia</item>
|
||||
<item name="colorSecondaryVariant">@color/drip_pink</item>
|
||||
<item name="colorOnSecondary">@color/text_primary</item>
|
||||
|
||||
<item name="android:statusBarColor">@color/bg_dark</item>
|
||||
@ -17,7 +17,7 @@
|
||||
<item name="bottomSheetDialogTheme">@style/DripBottomSheetDialogTheme</item>
|
||||
</style>
|
||||
|
||||
<!-- Bottom Sheet Dialog with Dark Rounded Corners -->
|
||||
<!-- Bottom Sheet Dialog with Web Dark Styling -->
|
||||
<style name="DripBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
|
||||
<item name="bottomSheetStyle">@style/DripBottomSheetStyle</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
@ -29,9 +29,12 @@
|
||||
</style>
|
||||
|
||||
<!-- Splash Theme -->
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.DripZone">
|
||||
<item name="android:windowBackground">@color/bg_dark</item>
|
||||
<style name="Theme.DripZone.Splash" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="android:windowBackground">@drawable/dripzone_splash_clean</item>
|
||||
<item name="android:statusBarColor">@color/bg_dark</item>
|
||||
<item name="android:navigationBarColor">@color/bg_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.DripZone.Splash" />
|
||||
<style name="AppTheme" parent="Theme.DripZone" />
|
||||
</resources>
|
||||
@ -64,30 +64,28 @@ def send_apk_whatsapp():
|
||||
print("[3/3] Enviando APK y notificación oficial al WhatsApp de Carlos (+57 316 745 5877)...")
|
||||
|
||||
message_text = (
|
||||
"📲 *DripZone - App Móvil Oficial en Kotlin Nativo*\n\n"
|
||||
"🔥 *¡Desarrollo Nativo en Kotlin Completado y Desplegado!*\n\n"
|
||||
"✨ *Características y Novedades de la App Nativa:*\n\n"
|
||||
"⚡ *1. Código 100% Nativo en Kotlin (Android):*\n"
|
||||
"• Reemplazo total de vistas genéricas por arquitectura nativa Jetpack con ViewBinding y Coroutines.\n"
|
||||
"• Interfaz ultra fluida, rápida y con estética dark streetwear moderna para DripZone.\n"
|
||||
"• Grid de productos a 2 columnas con tarjetas estilizadas, insignias de marca y precios en COP.\n"
|
||||
"• Filtros por categorías en chips horizontales (Todos, Sneakers, Chanclas, Bolsos, Gorras, Bajo Pedido).\n"
|
||||
"• Búsqueda instantánea en tiempo real integrada en el encabezado.\n"
|
||||
"• SwipeRefreshLayout (Pull-to-refresh) para actualizar en vivo con los nuevos drops de WhatsApp.\n\n"
|
||||
"🛡️ *2. Estándares de Seguridad Actuales Cumplidos:*\n"
|
||||
"• *Network Security Config:* Tráfico en texto claro (HTTP) bloqueado globalmente, enforcing estricto de HTTPS/TLS 1.2 y TLS 1.3 con certificados de sistema.\n"
|
||||
"• *Protección de Datos:* `allowBackup=\"false\"` y `dataExtractionRules` configurados para evitar extracción no autorizada vía adb o backups en la nube.\n"
|
||||
"• *Cifrado Local:* Almacenamiento seguro mediante `EncryptedSharedPreferences` con Android KeyStore y MasterKey AES-256 GCM.\n"
|
||||
"• *Protección Anti-Tapjacking:* `filterTouchesWhenObscured=\"true\"` en botones de compra y pedidos.\n"
|
||||
"• *Lanzamiento Seguro de Intents:* Validación rigurosa de URIs de WhatsApp y esquemas para prevenir Spoofing o Redirecciones maliciosas.\n"
|
||||
"• *Reglas Proguard/R8:* Ofuscación y protección de modelos JSON y dependencias.\n\n"
|
||||
"👟 *3. Experiencia de Compra y Encargos:* \n"
|
||||
"• Modal Bottom Sheet con vista ampliada, selección interactiva de tallas (chips) y detalles de despacho.\n"
|
||||
"• Botón directo *'Pedir por WhatsApp (Contraentrega)'* que pre-arma el mensaje exacto con foto, talla y precio al WhatsApp de Carlos.\n"
|
||||
"• Botón flotante y modal para *'Encargos Especiales / Pedidos'* de modelos no listados con proveedores mayoristas.\n\n"
|
||||
"📲 *DripZone - App Nativa Android v1.2 (100% Funcionalidad Web)*\n\n"
|
||||
"🔥 *¡Actualización Desplegada! Toda la funcionalidad y estética de la Web ahora en la App Nativa:*\n\n"
|
||||
"🎨 *1. Estilo Visual, Splash Screen y Paleta Web Idénticos:*\n"
|
||||
"• *Splash Screen:* Integrado con el recurso oficial solicitado `dripzone_splash_clean_1787718656035` con transición fluida.\n"
|
||||
"• *Paleta Dark Streetwear DripZone:* Fondo `#0A0A0A`, detalles en púrpura `#9333EA` / `#A855F7`, fucsia neón `#D946EF`, rosa `#EC4899`, badges violeta `#2E1065` y glow borders.\n"
|
||||
"• *Banner Web Superior:* Degradado en vivo 'ENVÍOS CONTRAENTREGA EN TODO COLOMBIA'.\n\n"
|
||||
"⚡ *2. Misma Funcionalidad que la Versión Web:*\n"
|
||||
"• *Selector de Ciudad:* Modal interactivo con ciudades colombianas (Cali, Bogotá, Medellín, Barranquilla, Bucaramanga, etc.), tiempos de entrega y garantía de 1er cambio gratis.\n"
|
||||
"• *Carrito de Compras Multi-Producto:* Drawer interactivo (`CartBottomSheet`), sumatoria automática, desglose de separación ('Separa con $10.000 COP y paga saldo al recibir') y checkout multi-item a WhatsApp.\n"
|
||||
"• *Detalle de Producto:* Selector interactivo de tallas en chips, botón 'Agregar al Carrito' y 'Comprar Directo por WhatsApp' con foto y formato de orden contraentrega.\n"
|
||||
"• *Dock de Navegación Móvil (5 Pestañas):* Inicio, Buscar, Carrito con badge dinámico en tiempo real, Encargos y Asesor WhatsApp.\n"
|
||||
"• *Módulo de Encargos / Pedidos Especiales:* Formulario para cotizar modelos no listados directamente con proveedores mayoristas.\n"
|
||||
"• *Filtros y Búsqueda en Vivo:* Categorías dinámicas (Sneakers, Chanclas, Bolsos, Gorras) y filtrado instantáneo por texto.\n"
|
||||
"• *Pull-to-Refresh:* Sincronización instantánea con los drops de WhatsApp.\n\n"
|
||||
"🛡️ *3. Seguridad de Nivel Corporativo:*\n"
|
||||
"• *Network Security Config:* Tráfico en texto claro (HTTP) bloqueado, TLS 1.2+ estricto.\n"
|
||||
"• *Cifrado de Datos:* `EncryptedSharedPreferences` respaldado por Android KeyStore (AES-256 GCM).\n"
|
||||
"• *Protección Anti-Tapjacking:* `filterTouchesWhenObscured=\"true\"` en botones clave.\n"
|
||||
"• *Lanzamiento Seguro de Intents:* Validación rigurosa de URLs y esquemas de WhatsApp.\n\n"
|
||||
f"⬇️ *Descarga directa del APK:* {APK_URL}\n"
|
||||
f"⬇️ *Enlace alternativo:* https://dripzone.almeira.com.co/DripZone.apk\n\n"
|
||||
"Adjunto el archivo APK instalador para que lo pruebes directamente en tu dispositivo."
|
||||
"Adjunto el instalador APK para su instalación y prueba inmediata."
|
||||
)
|
||||
|
||||
req_text = urllib.request.Request(
|
||||
|
||||