Compare commits
4 Commits
9273e01324
...
57f3c463cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f3c463cb | |||
| 96f6e8c251 | |||
| 3627cdd151 | |||
| 442f9fc10c |
@ -1,4 +1,5 @@
|
|||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.jetbrains.kotlin.android)
|
alias(libs.plugins.jetbrains.kotlin.android)
|
||||||
@ -127,4 +128,8 @@ dependencies {
|
|||||||
|
|
||||||
//Splash screen
|
//Splash screen
|
||||||
implementation("androidx.core:core-splashscreen:1.0.0")
|
implementation("androidx.core:core-splashscreen:1.0.0")
|
||||||
|
|
||||||
|
//pdf compression
|
||||||
|
implementation("com.tom-roush:pdfbox-android:2.0.27.0")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 182 KiB |
@ -194,7 +194,7 @@ class ProductRepository(private val apiService: ApiService) {
|
|||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
Result.Success(response.body()!!)
|
Result.Success(response.body()!!)
|
||||||
} else {
|
} else {
|
||||||
Result.Error(Exception("Failed to create product: ${response.code()}"))
|
Result.Error(Exception("Failed to create product: ${response.code()} message:${response.message()}"))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Result.Error(e)
|
Result.Error(e)
|
||||||
|
|||||||
@ -1,18 +1,26 @@
|
|||||||
package com.alya.ecommerce_serang.ui.product
|
package com.alya.ecommerce_serang.ui.product
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.SwitchCompat
|
import androidx.appcompat.widget.SwitchCompat
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
@ -36,6 +44,9 @@ import com.alya.ecommerce_serang.ui.product.storeDetail.StoreDetailActivity
|
|||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -51,6 +62,8 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
private var isWholesaleSelected: Boolean = false
|
private var isWholesaleSelected: Boolean = false
|
||||||
private var minOrder: Int = 0
|
private var minOrder: Int = 0
|
||||||
|
|
||||||
|
private var TAG = "DetailProductActivity"
|
||||||
|
|
||||||
private val viewModel: ProductUserViewModel by viewModels {
|
private val viewModel: ProductUserViewModel by viewModels {
|
||||||
BaseViewModelFactory {
|
BaseViewModelFactory {
|
||||||
val apiService = ApiConfig.getApiService(sessionManager)
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
@ -292,6 +305,16 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
.placeholder(R.drawable.placeholder_image)
|
.placeholder(R.drawable.placeholder_image)
|
||||||
.into(binding.ivProductImage)
|
.into(binding.ivProductImage)
|
||||||
|
|
||||||
|
binding.ivProductImage.setOnClickListener {
|
||||||
|
val img = product.image
|
||||||
|
if (!img.isNullOrEmpty()){
|
||||||
|
showDetailProduct(img)
|
||||||
|
}else {
|
||||||
|
Toast.makeText(this, "Gambar tidak tersedia", Toast.LENGTH_SHORT).show()
|
||||||
|
Log.e(TAG, "There is no photo product")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val ratingStr = product.rating
|
val ratingStr = product.rating
|
||||||
val ratingValue = ratingStr?.toFloatOrNull()
|
val ratingValue = ratingStr?.toFloatOrNull()
|
||||||
|
|
||||||
@ -533,6 +556,59 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showDetailProduct(photoProduct: String) {
|
||||||
|
val dialog = Dialog(this)
|
||||||
|
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
dialog.setContentView(R.layout.dialog_image_viewer)
|
||||||
|
dialog.setCancelable(true)
|
||||||
|
|
||||||
|
// Set dialog to fullscreen
|
||||||
|
val window = dialog.window
|
||||||
|
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
window?.setBackgroundDrawable(Color.WHITE.toDrawable())
|
||||||
|
|
||||||
|
// Get views from dialog
|
||||||
|
val imageView = dialog.findViewById<ImageView>(R.id.iv_payment_evidence)
|
||||||
|
val btnClose = dialog.findViewById<ImageButton>(R.id.btn_close)
|
||||||
|
val tvTitle = dialog.findViewById<TextView>(R.id.tv_title)
|
||||||
|
val progressBar = dialog.findViewById<ProgressBar>(R.id.progress_bar)
|
||||||
|
|
||||||
|
tvTitle.text = "Gambar Produk"
|
||||||
|
val fullImageUrl =
|
||||||
|
if (photoProduct.startsWith("/")) BASE_URL + photoProduct.substring(1)
|
||||||
|
else photoProduct
|
||||||
|
|
||||||
|
progressBar.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
Glide.with(this)
|
||||||
|
.load(fullImageUrl)
|
||||||
|
.placeholder(R.drawable.placeholder_image)
|
||||||
|
.error(R.drawable.placeholder_image)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
|
.into(object : CustomTarget<Drawable>() {
|
||||||
|
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||||
|
progressBar.visibility = View.GONE
|
||||||
|
imageView.setImageDrawable(resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
|
progressBar.visibility = View.GONE
|
||||||
|
imageView.setImageDrawable(placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||||
|
progressBar.visibility = View.GONE
|
||||||
|
imageView.setImageDrawable(errorDrawable)
|
||||||
|
Toast.makeText(this@DetailProductActivity, "Gagal memuat gambar", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
btnClose.setOnClickListener { dialog.dismiss() }
|
||||||
|
imageView.setOnClickListener { dialog.dismiss() }
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
loadData()
|
loadData()
|
||||||
|
|||||||
@ -27,7 +27,8 @@ import com.alya.ecommerce_serang.data.repository.ProductRepository
|
|||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProductBinding
|
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProductBinding
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.FileUtils.compressFile
|
import com.alya.ecommerce_serang.utils.CompressionResult
|
||||||
|
import com.alya.ecommerce_serang.utils.FileUtils.compressFileToMax1MB
|
||||||
import com.alya.ecommerce_serang.utils.ImageUtils.compressImage
|
import com.alya.ecommerce_serang.utils.ImageUtils.compressImage
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
|
import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
|
||||||
@ -49,6 +50,8 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
|||||||
private var productId: Int? = null
|
private var productId: Int? = null
|
||||||
private var hasImage: Boolean = false
|
private var hasImage: Boolean = false
|
||||||
|
|
||||||
|
private var TAG="DetailStoreProduct"
|
||||||
|
|
||||||
private var isEditing = false
|
private var isEditing = false
|
||||||
private var hasExistingImage = false
|
private var hasExistingImage = false
|
||||||
|
|
||||||
@ -78,20 +81,32 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private val sppirtLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
private val sppirtLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
if (uri != null && isValidFile(uri)) {
|
if (uri != null && isValidFile(uri)) {
|
||||||
compressFile(this, uri).let { compressedFile ->
|
when (val result = compressFileToMax1MB(this, uri)) {
|
||||||
sppirtUri = compressedFile?.toUri()
|
is CompressionResult.Success -> {
|
||||||
binding.tvSppirtName.text = getFileName(sppirtUri!!)
|
sppirtUri = result.file.toUri()
|
||||||
binding.switcherSppirt.showNext()
|
binding.tvSppirtName.text = getFileName(sppirtUri!!)
|
||||||
|
binding.switcherSppirt.showNext()
|
||||||
|
}
|
||||||
|
is CompressionResult.Error -> {
|
||||||
|
Toast.makeText(this, result.reason, Toast.LENGTH_LONG).show()
|
||||||
|
Log.e(TAG, "Compression failed: ${result.reason}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val halalLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
private val halalLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
if (uri != null && isValidFile(uri)) {
|
if (uri != null && isValidFile(uri)) {
|
||||||
compressFile(this, uri).let { compressedFile ->
|
when (val result = compressFileToMax1MB(this, uri)) {
|
||||||
halalUri = compressedFile?.toUri()
|
is CompressionResult.Success -> {
|
||||||
binding.tvHalalName.text = getFileName(halalUri!!)
|
halalUri = result.file.toUri()
|
||||||
binding.switcherHalal.showNext()
|
binding.tvHalalName.text = getFileName(halalUri!!)
|
||||||
|
binding.switcherHalal.showNext()
|
||||||
|
}
|
||||||
|
is CompressionResult.Error -> {
|
||||||
|
Toast.makeText(this, result.reason, Toast.LENGTH_LONG).show()
|
||||||
|
Log.e(TAG, "Compression failed: ${result.reason}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,12 +275,12 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun togglePreOrderVisibility(isChecked: Boolean) {
|
private fun togglePreOrderVisibility(isChecked: Boolean) {
|
||||||
Log.d("DEBUG", "togglePreOrderVisibility: $isChecked")
|
Log.d(TAG, "togglePreOrderVisibility: $isChecked")
|
||||||
binding.layoutDurasi.visibility = if (isChecked) View.VISIBLE else View.GONE
|
binding.layoutDurasi.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleWholesaleVisibility(isChecked: Boolean) {
|
private fun toggleWholesaleVisibility(isChecked: Boolean) {
|
||||||
Log.d("DEBUG", "toggleWholesaleVisibility: $isChecked")
|
Log.d(TAG, "toggleWholesaleVisibility: $isChecked")
|
||||||
binding.layoutMinPesanGrosir.visibility = if (isChecked) View.VISIBLE else View.GONE
|
binding.layoutMinPesanGrosir.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||||
binding.layoutHargaGrosir.visibility = if (isChecked) View.VISIBLE else View.GONE
|
binding.layoutHargaGrosir.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
@ -401,8 +416,10 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
|||||||
val sppirtFile = sppirtUri?.let { uriToNamedFile(it, this, "sppirt") }
|
val sppirtFile = sppirtUri?.let { uriToNamedFile(it, this, "sppirt") }
|
||||||
val halalFile = halalUri?.let { uriToNamedFile(it, this, "halal") }
|
val halalFile = halalUri?.let { uriToNamedFile(it, this, "halal") }
|
||||||
|
|
||||||
Log.d("File URI", "SPPIRT URI: ${sppirtUri.toString()}")
|
Log.d(TAG, "SPPIRT URI: ${sppirtUri.toString()}")
|
||||||
Log.d("File URI", "Halal URI: ${halalUri.toString()}")
|
Log.d(TAG, "Halal URI: ${halalUri.toString()}")
|
||||||
|
logFileInfo("Sppirt Size", sppirtFile!!)
|
||||||
|
logFileInfo("Halal Size", halalFile!!)
|
||||||
|
|
||||||
val imagePart = imageFile?.let { createPartFromFile("productimg", it) }
|
val imagePart = imageFile?.let { createPartFromFile("productimg", it) }
|
||||||
val sppirtPart = sppirtFile?.let { createPartFromFile("sppirt", it) }
|
val sppirtPart = sppirtFile?.let { createPartFromFile("sppirt", it) }
|
||||||
@ -428,7 +445,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
is Result.Error -> {
|
is Result.Error -> {
|
||||||
Log.e("ProductDetailActivity", "Error: ${result.exception.message}")
|
Log.e(TAG, "Error: ${result.exception.message}")
|
||||||
binding.btnSaveProduct.isEnabled = true
|
binding.btnSaveProduct.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -506,7 +523,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
is Result.Error -> {
|
is Result.Error -> {
|
||||||
Log.e("ProductDetailActivity", "Error: ${result.exception.message}")
|
Log.e(TAG, "Error: ${result.exception.message}")
|
||||||
binding.btnSaveProduct.isEnabled = true
|
binding.btnSaveProduct.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -515,4 +532,10 @@ class DetailStoreProductActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun toRequestBody(value: String): RequestBody =
|
private fun toRequestBody(value: String): RequestBody =
|
||||||
RequestBody.create("text/plain".toMediaTypeOrNull(), value)
|
RequestBody.create("text/plain".toMediaTypeOrNull(), value)
|
||||||
|
|
||||||
|
private fun logFileInfo(tag: String, file: File) {
|
||||||
|
val sizeKb = file.length() / 1024.0
|
||||||
|
val sizeMb = sizeKb / 1024.0
|
||||||
|
Log.d(TAG, "$tag → Name: ${file.name}, Size: ${"%.2f".format(sizeKb)} KB (${String.format("%.2f", sizeMb)} MB)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +1,17 @@
|
|||||||
package com.alya.ecommerce_serang.utils
|
package com.alya.ecommerce_serang.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
|
import com.tom_roush.pdfbox.pdmodel.PDDocument
|
||||||
|
import com.tom_roush.pdfbox.rendering.PDFRenderer
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@ -135,4 +140,115 @@ object FileUtils {
|
|||||||
else -> "application/octet-stream"
|
else -> "application/octet-stream"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for Uri format
|
||||||
|
// fun compressFileToMax1MB(context: Context, uri: Uri): File? {
|
||||||
|
// val mimeType = context.contentResolver.getType(uri)
|
||||||
|
//
|
||||||
|
// return if (mimeType?.startsWith("image/") == true) {
|
||||||
|
// // Handle images (jpg/png)
|
||||||
|
// compressImageToMax1MB(context, uri)
|
||||||
|
// } else if (mimeType == "application/pdf") {
|
||||||
|
// // Handle PDFs
|
||||||
|
// val file = createTempFileFromUri(context, uri, "pdf")
|
||||||
|
// return if (file != null && file.length() <= 1_048_576) {
|
||||||
|
// file
|
||||||
|
// } else {
|
||||||
|
// // 🚨 Without a PDF compression lib, you can only reject if > 1 MB
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // Unsupported type
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun compressFileToMax1MB(context: Context, uri: Uri): CompressionResult {
|
||||||
|
val mimeType = context.contentResolver.getType(uri) ?: return CompressionResult.Error("Tipe file tidak diketahui")
|
||||||
|
|
||||||
|
return if (mimeType.startsWith("image/")) {
|
||||||
|
val compressed = compressImageToMax1MB(context, uri)
|
||||||
|
if (compressed != null) {
|
||||||
|
CompressionResult.Success(compressed)
|
||||||
|
} else {
|
||||||
|
CompressionResult.Error("Ukuran gambar terlalu besar. Max 1MB.")
|
||||||
|
}
|
||||||
|
} else if (mimeType == "application/pdf") {
|
||||||
|
val file = createTempFileFromUri(context, uri, "pdf")
|
||||||
|
if (file == null) {
|
||||||
|
return CompressionResult.Error("Tidak bisa membaca file pdf")
|
||||||
|
}
|
||||||
|
if (file.length() <= 1_048_576) {
|
||||||
|
return CompressionResult.Success(file)
|
||||||
|
}
|
||||||
|
val compressed = compressPdfToMax1MB(context, file)
|
||||||
|
if (compressed != null) {
|
||||||
|
CompressionResult.Success(compressed)
|
||||||
|
} else {
|
||||||
|
CompressionResult.Error("Ukuran pdf terlalu besar. Max 1MB.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CompressionResult.Error("Tipe file tidak didukung: $mimeType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compressImageToMax1MB(context: Context, uri: Uri): File? {
|
||||||
|
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
|
||||||
|
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
|
||||||
|
var quality = 100
|
||||||
|
var compressedFile: File
|
||||||
|
var outputStream: ByteArrayOutputStream
|
||||||
|
|
||||||
|
do {
|
||||||
|
outputStream = ByteArrayOutputStream()
|
||||||
|
originalBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
|
||||||
|
|
||||||
|
val compressedBytes = outputStream.toByteArray()
|
||||||
|
compressedFile = File(context.cacheDir, "compressed_${System.currentTimeMillis()}.jpg")
|
||||||
|
FileOutputStream(compressedFile).use { it.write(compressedBytes) }
|
||||||
|
|
||||||
|
quality -= 5
|
||||||
|
} while (compressedFile.length() > 1_048_576 && quality > 10)
|
||||||
|
|
||||||
|
return if (compressedFile.length() <= 1_048_576) compressedFile else null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compressPdfToMax1MB(context: Context, inputFile: File): File? {
|
||||||
|
return try {
|
||||||
|
val document = PDDocument.load(inputFile)
|
||||||
|
val renderer = PDFRenderer(document)
|
||||||
|
|
||||||
|
val compressedDoc = PDDocument()
|
||||||
|
|
||||||
|
for (pageIndex in 0 until document.numberOfPages) {
|
||||||
|
val bitmap = renderer.renderImageWithDPI(pageIndex, 72f) // low DPI → smaller size
|
||||||
|
val outPage = com.tom_roush.pdfbox.pdmodel.PDPage(
|
||||||
|
com.tom_roush.pdfbox.pdmodel.common.PDRectangle(bitmap.width.toFloat(), bitmap.height.toFloat())
|
||||||
|
)
|
||||||
|
compressedDoc.addPage(outPage)
|
||||||
|
|
||||||
|
val pdImage = com.tom_roush.pdfbox.pdmodel.graphics.image.LosslessFactory.createFromImage(compressedDoc, bitmap)
|
||||||
|
val contentStream = com.tom_roush.pdfbox.pdmodel.PDPageContentStream(compressedDoc, outPage)
|
||||||
|
contentStream.drawImage(pdImage, 0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat())
|
||||||
|
contentStream.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
val compressedFile = File(context.cacheDir, "compressed_${System.currentTimeMillis()}.pdf")
|
||||||
|
compressedDoc.save(compressedFile)
|
||||||
|
compressedDoc.close()
|
||||||
|
document.close()
|
||||||
|
|
||||||
|
// Check size
|
||||||
|
return if (compressedFile.length() <= 1_048_576) compressedFile else null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class CompressionResult {
|
||||||
|
data class Success(val file: File) : CompressionResult()
|
||||||
|
data class Error(val reason: String) : CompressionResult()
|
||||||
}
|
}
|
||||||
10
app/src/main/res/drawable/logo_home.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@color/white" />
|
||||||
|
<!-- Logo with controlled size -->
|
||||||
|
<item
|
||||||
|
android:width="48dp"
|
||||||
|
android:height="48dp"
|
||||||
|
android:drawable="@drawable/logo_psb_only"
|
||||||
|
android:gravity="center" />
|
||||||
|
</layer-list>
|
||||||
BIN
app/src/main/res/drawable/logo_psb_crop.jpeg
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
app/src/main/res/drawable/logo_psb_only.png
Normal file
|
After Width: | Height: | Size: 1000 KiB |
BIN
app/src/main/res/drawable/logo_psb_small.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
11
app/src/main/res/drawable/splash_drawable.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@color/white" />
|
||||||
|
|
||||||
|
<!-- Logo with controlled size -->
|
||||||
|
<item
|
||||||
|
android:width="120dp"
|
||||||
|
android:height="120dp"
|
||||||
|
android:drawable="@drawable/logo_psb_crop"
|
||||||
|
android:gravity="center" />
|
||||||
|
</layer-list>
|
||||||
@ -31,8 +31,9 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ivProductImage"
|
android:id="@+id/ivProductImage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="200dp"
|
android:layout_height="360dp"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
android:contentDescription="@string/product_image"
|
android:contentDescription="@string/product_image"
|
||||||
tools:src="@drawable/placeholder_image" />
|
tools:src="@drawable/placeholder_image" />
|
||||||
|
|
||||||
@ -218,7 +219,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/ulasan_pembeli"
|
android:text="@string/ulasan_pembeli"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="14sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -276,7 +277,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/detail_produk"
|
android:text="@string/detail_produk"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="14sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TableLayout
|
<TableLayout
|
||||||
@ -357,7 +358,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/deskripsi_produk"
|
android:text="@string/deskripsi_produk"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="14sp"
|
android:textSize="16sp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
@ -421,7 +422,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="14sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="SnackEnak" />
|
tools:text="SnackEnak" />
|
||||||
|
|
||||||
@ -484,7 +485,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/produk_lainnya"
|
android:text="@string/produk_lainnya"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="14sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:textSize="14sp"
|
||||||
android:text="Bukti Pembayaran"
|
android:text="Bukti Pembayaran"
|
||||||
style="@style/title_large" />
|
style="@style/title_large" />
|
||||||
|
|
||||||
|
|||||||
@ -7,12 +7,28 @@
|
|||||||
android:theme="@style/Theme.Ecommerce_serang"
|
android:theme="@style/Theme.Ecommerce_serang"
|
||||||
tools:context=".ui.home.HomeFragment">
|
tools:context=".ui.home.HomeFragment">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/logo_psb_home"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:src="@drawable/logo_home"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/searchContainer" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/searchContainer"
|
android:id="@+id/searchContainer"
|
||||||
layout="@layout/view_search"
|
layout="@layout/view_search"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/logo_psb_home"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
@ -21,6 +37,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
android:overScrollMode="never"
|
android:overScrollMode="never"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/searchContainer"
|
app:layout_constraintTop_toBottomOf="@id/searchContainer"
|
||||||
app:layout_constraintBottom_toBottomOf="parent">
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 8.6 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 20 KiB |
@ -1,6 +1,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">PasarKlik</string>
|
<string name="app_name">Plaza Serang Bahagia</string>
|
||||||
|
|
||||||
|
|
||||||
<!--Placeholder-->
|
<!--Placeholder-->
|
||||||
<string name="fragment_home_search">Cari produk</string>
|
<string name="fragment_home_search">Cari produk</string>
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
<item name="colorSurface">@color/white</item>
|
<item name="colorSurface">@color/white</item>
|
||||||
<item name="colorOnSurface">@color/black</item>
|
<item name="colorOnSurface">@color/black</item>
|
||||||
<item name="android:colorBackground">@color/white</item>
|
<item name="android:colorBackground">@color/white</item>
|
||||||
<!-- <item name="colorBackground">@color/white</item>-->
|
|
||||||
|
|
||||||
<!-- Container Colors -->
|
<!-- Container Colors -->
|
||||||
<item name="colorPrimaryContainer">@color/blue_50</item>
|
<item name="colorPrimaryContainer">@color/blue_50</item>
|
||||||
@ -39,8 +38,6 @@
|
|||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
<!-- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>-->
|
|
||||||
|
|
||||||
<item name="android:textViewStyle">@style/body_medium</item>
|
<item name="android:textViewStyle">@style/body_medium</item>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -319,7 +316,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
|
|
||||||
<style name="circular_image">
|
<style name="circular_image">
|
||||||
<item name="cornerFamily">rounded</item>
|
<item name="cornerFamily">rounded</item>
|
||||||
<item name="cornerSize">50%</item>
|
<item name="cornerSize">50%</item>
|
||||||
@ -330,12 +326,21 @@
|
|||||||
<item name="cornerSize">5dp</item>
|
<item name="cornerSize">5dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!--Splash screen themes-->
|
||||||
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
|
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
|
||||||
<item name="windowSplashScreenBackground">@color/white</item>
|
<item name="windowSplashScreenBackground">@color/white</item>
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/logo_bisa_umkm</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_drawable</item>
|
||||||
<item name="windowSplashScreenAnimationDuration">1000</item>
|
<item name="windowSplashScreenAnimationDuration">1000</item>
|
||||||
<item name="android:windowSplashScreenBehavior" tools:targetApi="33">icon_preferred</item>
|
<item name="android:windowSplashScreenBehavior" tools:targetApi="33">icon_preferred</item>
|
||||||
<item name="postSplashScreenTheme">@style/Theme.Ecommerce_serang</item>
|
<item name="postSplashScreenTheme">@style/Theme.Ecommerce_serang</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ThemeOverlay.MyApp.AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
||||||
|
<!-- Rounded corners -->
|
||||||
|
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.MyApp.MediumComponent</item>
|
||||||
|
<!-- Dialog background color -->
|
||||||
|
<item name="materialAlertDialogBodyTextStyle">@font/dmsans_regular</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ShapeAppearance.MyApp.MediumComponent" parent="ShapeAppearance.Material3.Corner.Medium" />
|
||||||
</resources>
|
</resources>
|
||||||