From bad456f842b2cf6bff814c7d5a4c07e1e9e5abd8 Mon Sep 17 00:00:00 2001 From: Gracia Hotmauli <95269134+hotmauligracia@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:23:39 +0700 Subject: [PATCH 1/2] store status, product fixed, file compression --- .idea/deviceManager.xml | 13 +++++ app/build.gradle.kts | 3 -- app/src/main/AndroidManifest.xml | 14 ++--- .../ui/profile/ProfileFragment.kt | 18 +++++-- .../profile/mystore/StoreSuspendedActivity.kt | 26 ++++++++++ .../product/DetailStoreProductActivity.kt | 25 ++++++--- .../alya/ecommerce_serang/utils/FileUtils.kt | 52 ++++++++++++++++++- .../utils/viewmodel/ProfileViewModel.kt | 8 +-- .../res/layout/activity_store_suspended.xml | 39 ++++++++++++++ gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 11 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 .idea/deviceManager.xml create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/StoreSuspendedActivity.kt create mode 100644 app/src/main/res/layout/activity_store_suspended.xml diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ce4e99b..c34c44b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -124,7 +124,4 @@ dependencies { implementation(platform("com.google.firebase:firebase-bom:33.13.0")) implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-messaging-ktx") - } - - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4455d19..610dd43 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,6 +29,9 @@ android:theme="@style/Theme.Ecommerce_serang" android:usesCleartextTraffic="true" tools:targetApi="31"> + @@ -82,12 +85,11 @@ - - - - - - + + + + + store?.let { when (store.storeStatus) { + "process" -> startActivity(Intent(requireContext(), StoreOnReviewActivity::class.java)) "active" -> startActivity(Intent(requireContext(), MyStoreActivity::class.java)) - else -> startActivity(Intent(requireContext(), StoreOnReviewActivity::class.java)) + "inactive" -> startActivity(Intent(requireContext(), MyStoreActivity::class.java)) + "suspended" -> startActivity(Intent(requireContext(), StoreSuspendedActivity::class.java)) + else -> startActivity(Intent(requireContext(), RegisterStoreActivity::class.java)) } } ?: run { Toast.makeText(requireContext(), "Gagal memuat data toko", Toast.LENGTH_SHORT).show() @@ -132,6 +134,12 @@ class ProfileFragment : Fragment() { } } + private fun observeStoreStatus() { + viewModel.checkStore.observe(viewLifecycleOwner) { hasStore -> + binding.tvBukaToko.text = if (hasStore) "Toko Saya" else "Buka Toko" + } + } + private fun updateUI(user: UserProfile) = with(binding){ val fullImageUrl = when (val img = user.image) { is String -> { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/StoreSuspendedActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/StoreSuspendedActivity.kt new file mode 100644 index 0000000..56a0c5c --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/StoreSuspendedActivity.kt @@ -0,0 +1,26 @@ +package com.alya.ecommerce_serang.ui.profile.mystore + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.databinding.ActivityStoreSuspendedBinding + +class StoreSuspendedActivity : AppCompatActivity() { + + private lateinit var binding: ActivityStoreSuspendedBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityStoreSuspendedBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.header.headerTitle.text = "Toko Dinonaktifkan" + binding.header.headerLeftIcon.setOnClickListener { + onBackPressedDispatcher.onBackPressed() + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt index 9e0bca5..1ced252 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt @@ -27,6 +27,8 @@ import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProductBinding import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.FileUtils.compressFile +import com.alya.ecommerce_serang.utils.ImageUtils.compressImage import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel import com.bumptech.glide.Glide @@ -40,7 +42,7 @@ class DetailStoreProductActivity : AppCompatActivity() { private lateinit var binding: ActivityDetailStoreProductBinding private lateinit var sessionManager: SessionManager - private lateinit var categoryList: List + private var categoryList: List = emptyList() private var imageUri: Uri? = null private var sppirtUri: Uri? = null private var halalUri: Uri? = null @@ -60,7 +62,10 @@ class DetailStoreProductActivity : AppCompatActivity() { if (result.resultCode == Activity.RESULT_OK) { imageUri = result.data?.data imageUri?.let { - binding.ivPreviewFoto.setImageURI(it) + compressImage(this, it, "productimg").let { compressedImageFile -> + binding.ivPreviewFoto.setImageURI(Uri.fromFile(compressedImageFile)) + imageUri = Uri.fromFile(compressedImageFile) + } binding.switcherFotoProduk.showNext() hasImage = true } @@ -70,17 +75,21 @@ class DetailStoreProductActivity : AppCompatActivity() { private val sppirtLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri != null && isValidFile(uri)) { - sppirtUri = uri - binding.tvSppirtName.text = getFileName(uri) - binding.switcherSppirt.showNext() + compressFile(this, uri).let { compressedFile -> + sppirtUri = compressedFile?.toUri() + binding.tvSppirtName.text = getFileName(sppirtUri!!) + binding.switcherSppirt.showNext() + } } } private val halalLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri != null && isValidFile(uri)) { - halalUri = uri - binding.tvHalalName.text = getFileName(uri) - binding.switcherHalal.showNext() + compressFile(this, uri).let { compressedFile -> + halalUri = compressedFile?.toUri() + binding.tvHalalName.text = getFileName(halalUri!!) + binding.switcherHalal.showNext() + } } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/FileUtils.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/FileUtils.kt index facbdfc..1194605 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/FileUtils.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/FileUtils.kt @@ -8,13 +8,46 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.asRequestBody import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream +import java.util.zip.GZIPOutputStream object FileUtils { private const val TAG = "FileUtils" /** - * Creates a temporary file from a URI in the app's cache directory + * Compress a file to GZIP format to reduce its size to below 1MB + * @param context The context + * @param uri The URI of the file to compress + * @param maxSize The target size limit in bytes (1MB = 1048576 bytes) + * @return The compressed file, or null if compression failed + */ + fun compressFile(context: Context, uri: Uri, maxSize: Long = 1048576L): File? { + try { + // Create a temporary file for compressed content + val originalFile = createTempFileFromUri(context, uri, "compressed") + val compressedFile = File(context.cacheDir, "compressed_${System.currentTimeMillis()}.gz") + + // Compress the original file into the GZIP file + compressToGZIP(originalFile, compressedFile) + + // Check if the compressed file is larger than the allowed size + if (compressedFile.length() <= maxSize) { + Log.d(TAG, "Compression successful. Compressed file size: ${compressedFile.length()} bytes.") + return compressedFile + } else { + // If the file is still too large, you can handle it by reducing quality or adjusting compression logic + Log.e(TAG, "Compressed file exceeds the size limit. Size: ${compressedFile.length()} bytes.") + return null + } + } catch (e: Exception) { + Log.e(TAG, "Error during file compression: ${e.message}", e) + return null + } + } + + /** + * Creates a temporary file from the URI in the app's cache directory. */ fun createTempFileFromUri(context: Context, uri: Uri, prefix: String = "temp"): File? { try { @@ -41,6 +74,23 @@ object FileUtils { } } + /** + * Compress the input file into a GZIP file. + */ + private fun compressToGZIP(inputFile: File?, outputFile: File) { + FileInputStream(inputFile).use { inputStream -> + FileOutputStream(outputFile).use { fileOutputStream -> + GZIPOutputStream(fileOutputStream).use { gzipOutputStream -> + val buffer = ByteArray(1024) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + gzipOutputStream.write(buffer, 0, bytesRead) + } + } + } + } + } + /** * Gets the file extension from a URI using ContentResolver */ diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProfileViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProfileViewModel.kt index e655986..6e56947 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProfileViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProfileViewModel.kt @@ -47,15 +47,15 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel() val response: HasStoreResponse = userRepository.checkStore() // Log and store success message - Log.d("RegisterViewModel", "OTP Response: ${response.hasStore}") - _checkStore.value = response.hasStore // Store the message for UI feedback + Log.d("ProfileViewModel", "Has store: ${response.hasStore}") + _checkStore.postValue(response.hasStore) // Store the message for UI feedback } catch (exception: Exception) { // Handle any errors and update state - _checkStore.value = false + _checkStore.postValue(false) // Log the error for debugging - Log.e("RegisterViewModel", "Error:", exception) + Log.e(":ProfileViewModel", "Error:", exception) } } } diff --git a/app/src/main/res/layout/activity_store_suspended.xml b/app/src/main/res/layout/activity_store_suspended.xml new file mode 100644 index 0000000..3ab2dbe --- /dev/null +++ b/app/src/main/res/layout/activity_store_suspended.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7ee4510..e09c11f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.9.2" +agp = "8.12.0" glide = "4.16.0" gson = "2.11.0" hiltAndroid = "2.56.2" # Updated from 2.44 for better compatibility diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 00bf192..7ba3b55 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Oct 16 14:37:43 ICT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 77ea5ed90a3db268edf3a35bbeb97f6756bae8a8 Mon Sep 17 00:00:00 2001 From: Gracia Hotmauli <95269134+hotmauligracia@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:51:19 +0700 Subject: [PATCH 2/2] top-up --- .../mystore/balance/BalanceTopUpActivity.kt | 106 +++++++++++++++++- .../res/layout/activity_balance_top_up.xml | 67 ++++++++++- 2 files changed, 167 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt index fd85cbb..8c9b45f 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt @@ -15,11 +15,15 @@ import android.widget.Spinner import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.lifecycleScope import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.response.store.profile.Payment import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.utils.ImageUtils.compressImage import com.alya.ecommerce_serang.utils.SessionManager import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -38,6 +42,8 @@ class BalanceTopUpActivity : AppCompatActivity() { private lateinit var spinnerPaymentMethod: Spinner private lateinit var edtTransactionDate: EditText private lateinit var datePickerIcon: ImageView + private lateinit var layoutMBankingInstructions: View + private lateinit var layoutATMInstructions: View private lateinit var btnSend: Button private lateinit var sessionManager: SessionManager @@ -52,7 +58,22 @@ class BalanceTopUpActivity : AppCompatActivity() { val imageUri = result.data?.data imageUri?.let { selectedImageUri = it - imgPreview.setImageURI(it) + + // Compress the image before displaying it + val compressedFile = compressImage( + context = this, + uri = it, + filename = "topup_img", + maxWidth = 1024, + maxHeight = 1024, + quality = 80 + ) + + // Display the compressed image + selectedImageUri = Uri.fromFile(compressedFile) + imgPreview.setImageURI(Uri.fromFile(compressedFile)) + + validateForm() } } } @@ -71,6 +92,8 @@ class BalanceTopUpActivity : AppCompatActivity() { spinnerPaymentMethod = findViewById(R.id.spinner_metode_bayar) edtTransactionDate = findViewById(R.id.edt_tgl_transaksi) datePickerIcon = findViewById(R.id.img_date_picker) + layoutMBankingInstructions = findViewById(R.id.layout_mbanking_instructions) + layoutATMInstructions = findViewById(R.id.layout_atm_instructions) btnSend = findViewById(R.id.btn_send) // Setup header title @@ -98,10 +121,27 @@ class BalanceTopUpActivity : AppCompatActivity() { // Fetch payment methods fetchPaymentMethods() + setupClickListeners("1234567890") + // Setup submit button btnSend.setOnClickListener { submitForm() } + + // Validate form when any input changes + edtNominal.doAfterTextChanged { validateForm() } + edtTransactionDate.doAfterTextChanged { validateForm() } + spinnerPaymentMethod.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + selectedPaymentId = paymentMethods[position].id + validateForm() + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + selectedPaymentId = -1 + validateForm() + } + } } private fun openGallery() { @@ -200,6 +240,24 @@ class BalanceTopUpActivity : AppCompatActivity() { } } + private fun validateForm() { + val isNominalFilled = edtNominal.text.toString().trim().isNotEmpty() + val isPaymentMethodSelected = selectedPaymentId != -1 + val isTransactionDateFilled = edtTransactionDate.text.toString().trim().isNotEmpty() + val isImageSelected = selectedImageUri != null + + val valid = isNominalFilled && isPaymentMethodSelected && isTransactionDateFilled && isImageSelected + btnSend.isEnabled = valid + btnSend.setTextColor( + if (valid) ContextCompat.getColor(this, R.color.white) + else ContextCompat.getColor(this, R.color.black_300) + ) + btnSend.setBackgroundResource( + if (valid) R.drawable.bg_button_active + else R.drawable.bg_button_disabled + ) + } + private fun submitForm() { // Prevent multiple clicks if (!btnSend.isEnabled) { @@ -316,7 +374,7 @@ class BalanceTopUpActivity : AppCompatActivity() { // Show a dialog with the success message runOnUiThread { - androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity) + AlertDialog.Builder(this@BalanceTopUpActivity) .setTitle("Berhasil") .setMessage(successMessage) .setPositiveButton("OK") { dialog, _ -> @@ -350,7 +408,7 @@ class BalanceTopUpActivity : AppCompatActivity() { // Show a dialog with the error message runOnUiThread { - androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity) + AlertDialog.Builder(this@BalanceTopUpActivity) .setTitle("Error Response") .setMessage(errorMessage) .setPositiveButton("OK") { dialog, _ -> @@ -392,4 +450,46 @@ class BalanceTopUpActivity : AppCompatActivity() { return tempFile } + + private fun setupClickListeners(bankAccountNumber: String) { + // Instructions clicks + layoutMBankingInstructions.setOnClickListener { + showInstructions("mBanking", bankAccountNumber) + } + + layoutATMInstructions.setOnClickListener { + showInstructions("ATM", bankAccountNumber) + } + } + + private fun showInstructions(type: String, bankAccountNumber: String) { + // Implementasi tampilkan instruksi + val instructions = when (type) { + "mBanking" -> listOf( + "1. Login ke aplikasi mobile banking", + "2. Pilih menu Transfer", + "3. Pilih menu Antar Rekening", + "4. Masukkan nomor rekening tujuan: $bankAccountNumber", + "5. Masukkan nominal saldo yang ingin diisi", + "6. Konfirmasi dan selesaikan transfer" + ) + "ATM" -> listOf( + "1. Masukkan kartu ATM dan PIN", + "2. Pilih menu Transfer", + "3. Pilih menu Antar Rekening", + "4. Masukkan kode bank dan nomor rekening tujuan: $bankAccountNumber", + "5. Masukkan nominal saldo yang ingin diisi", + "6. Konfirmasi dan selesaikan transfer" + ) + else -> emptyList() + } + + // Tampilkan instruksi dalam dialog + val dialog = AlertDialog.Builder(this) + .setTitle("Petunjuk Transfer $type") + .setItems(instructions.toTypedArray(), null) + .setPositiveButton("Tutup", null) + .create() + dialog.show() + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_balance_top_up.xml b/app/src/main/res/layout/activity_balance_top_up.xml index d3753bc..05e1098 100644 --- a/app/src/main/res/layout/activity_balance_top_up.xml +++ b/app/src/main/res/layout/activity_balance_top_up.xml @@ -26,14 +26,14 @@ android:paddingHorizontal="@dimen/horizontal_safe_area" android:layout_marginTop="19dp"> - + - + @@ -275,12 +275,73 @@ + + + + + + + + + + + + + + + + + + + + + +