Compare commits

..

36 Commits

Author SHA1 Message Date
ad08e80ae0 MID & TID Settlement Detail 2025-08-11 13:21:14 +07:00
ccfd3a09eb settlement 2025-08-11 07:55:58 +07:00
dd57975908 history 2025-08-08 15:10:48 +07:00
a49aab14f8 modif settlement 2025-08-08 14:36:52 +07:00
72b39fd9c8 tes notif backend 2025-08-07 00:22:23 +07:00
40d0fc2402 update data info di main menu card 2025-08-06 14:30:47 +07:00
78f9e95c3f implement .env to QrisAcctivity 2025-08-06 13:47:26 +07:00
69fd69ac3a set interval ke 15 menit 2025-08-06 10:00:08 +07:00
0e86870b8b solve history 2025-08-05 14:13:12 +07:00
1c1d580a38 implement .env 2025-08-05 11:03:08 +07:00
6d519d96cf fix: intterval 1 2025-08-04 11:04:09 +07:00
64b666869e set ke 1 menit 2025-08-04 10:53:44 +07:00
a674574031 Adopted commit 8cef8fd: refactor code + generate ... 2025-08-04 10:49:19 +07:00
8cef8fdb22 refactor code + generate qr (bug pending) 2025-08-02 11:43:46 +07:00
e0aec6e840 menambah interval waktu ke 15 menit 2025-08-01 09:48:52 +07:00
538249fc57 penambahan button di QRIS 2025-07-30 17:20:41 +07:00
a38cea065f Create Transaction bug reset State 2025-07-07 11:56:16 +07:00
671b585fe5 Implement UI Info Toko 2025-07-04 18:47:44 +07:00
c18fd2d831 Implement API Info Toko 2025-07-02 17:45:41 +07:00
c033a26516 Implement API ke Form Bantuan 2025-07-02 12:47:09 +07:00
f64779755a Improve BatuanActivitiy 2025-07-01 17:37:55 +07:00
22d0409c0a Implement API ke Form Bantuan 2025-07-01 17:06:02 +07:00
2803182a02 Implement Banner UI 2025-07-01 16:11:40 +07:00
960f64ee81 Implement Integrasi Prduction key midtrans 2025-07-01 13:07:14 +07:00
9dac55d07a Implement BantuanFormActivity API 2025-07-01 11:44:30 +07:00
ddf76d2540 update ResultTransaction integrasi dengan printer 2025-06-30 22:36:06 +07:00
b2442ada48 Implement ReceiptActivity dengan printer 2025-06-30 21:43:06 +07:00
a52f56e154 Implement BantuanFormActivity UI 2025-06-30 15:22:59 +07:00
4209b193d7 implement login API dan BantuanActivity Riwayaat API 2025-06-30 14:50:03 +07:00
44225f1d67 backup bantuan activity with api 2025-06-30 10:18:57 +07:00
88069c0b56 Implement BantuanActivity UI 2025-06-29 20:33:23 +07:00
6f98a91372 Implement BantuanActivity 2025-06-29 15:25:34 +07:00
597921e32b Implement HistoryActivity 2025-06-29 02:54:08 +07:00
3ac3598359 implement History 2025-06-28 21:42:32 +07:00
6660fca373 Implement PaymentActivity dan ReprintActivity 2025-06-28 00:03:27 +07:00
edb1c6d09b layouting cetak ulang 2025-06-27 21:04:29 +07:00
58 changed files with 9871 additions and 2957 deletions

27
.env.example Normal file
View File

@@ -0,0 +1,27 @@
# ==============================================
# QRIS PAYMENT CONFIGURATION - ENVIRONMENT VARIABLES
# ==============================================
# Copy this file to .env and fill in the values
# ==============================================
# Midtrans API Configuration
MIDTRANS_SANDBOX_AUTH=your_midtrans_sandbox_auth_here
MIDTRANS_PRODUCTION_AUTH=your_midtrans_production_auth_here
MIDTRANS_CHARGE_URL=https://api.sandbox.midtrans.com/v2/charge
MIDTRANS_STATUS_BASE_URL=https://api.sandbox.midtrans.com/v2/
MIDTRANS_SIMULATOR_URL=https://simulator.sandbox.midtrans.com/v2/qris/index
# Backend Configuration
BACKEND_BASE_URL=your_backend_base_url_here
WEBHOOK_URL=your_webhook_url_here
# Application Settings
MAX_REFRESH_ATTEMPTS=5
DEFAULT_QR_EXPIRATION_MINUTES=1
# ==============================================
# INSTRUCTIONS:
# 1. Copy this file to .env in the same directory
# 2. Fill in the actual values
# 3. NEVER commit .env to version control!
# ==============================================

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
*.iml
.gradle
.env
*.env
/local.properties
/.idea/caches
/.idea/libraries

View File

@@ -42,6 +42,22 @@ android {
jniLibs.srcDirs = ['libs']
}
}
buildFeatures {
buildConfig true // Ini yang mengaktifkan fitur BuildConfig
}
defaultConfig {
// Tambahkan semua buildConfigField yang dibutuhkan
buildConfigField "int", "MAX_REFRESH_ATTEMPTS", "3"
buildConfigField "int", "DEFAULT_QR_EXPIRATION_MINUTES", "15"
buildConfigField "String", "MIDTRANS_SANDBOX_AUTH", "\"Basic U0ItTWlkLXNlcnZlci1PM2t1bXkwVDl4M1VvYnVvVTc3NW5QbXc=\""
buildConfigField "String", "MIDTRANS_PRODUCTION_AUTH", "\"TWlkLXNlcnZlci1sMlZPalotdVlVanpvNnU4VzAtYmF1a2o=\""
buildConfigField "String", "MIDTRANS_CHARGE_URL", "\"https://api.sandbox.midtrans.com/v2/charge\""
buildConfigField "String", "MIDTRANS_STATUS_BASE_URL", "\"https://api.sandbox.midtrans.com/v2/\""
buildConfigField "String", "BACKEND_BASE_URL", "\"https://be-edc.msvc.app\""
buildConfigField "String", "WEBHOOK_URL", "\"https://be-edc.msvc.app/webhooks/midtrans\""
}
}
dependencies {
@@ -52,6 +68,9 @@ dependencies {
implementation libs.constraintlayout
implementation libs.cardview
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.sunmi:printerlibrary:1.0.15'
// Test dependencies

View File

@@ -59,8 +59,9 @@
android:name=".QrisActivity"
android:exported="false" />
<!-- FIXED: Updated to correct package path -->
<activity
android:name=".QrisResultActivity"
android:name=".qris.view.QrisResultActivity"
android:exported="false" />
<activity
@@ -68,11 +69,15 @@
android:exported="false" />
<activity
android:name=".HistoryActivity"
android:name=".LoginActivity"
android:exported="false" />
<activity
android:name=".HistoryDetailActivity"
android:name=".histori.HistoryActivity"
android:exported="false" />
<activity
android:name=".histori.HistoryListActivity"
android:exported="false" />
<activity
@@ -83,7 +88,29 @@
android:name=".transaction.ResultTransactionActivity"
android:exported="false" />
<activity
android:name=".settlement.SettlementActivity"
android:exported="false" />
<activity
android:name=".settlement.SettlementDetailActivity"
android:exported="false" />
<activity
android:name=".bantuan.BantuanActivity"
android:exported="false" />
<activity
android:name=".bantuan.BantuanFormActivity"
android:exported="false" />
<activity
android:name=".infotoko.InfoTokoActivity"
android:exported="false" />
<activity android:name="com.sunmi.emv.l2.view.AppSelectActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,323 @@
package com.example.bdkipoc;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.button.MaterialButton;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LoginActivity extends AppCompatActivity {
private static final String TAG = "LoginActivity";
private static final String PREFS_NAME = "LoginPrefs";
private static final String KEY_TOKEN = "token";
private static final String KEY_USER_DATA = "user_data";
private static final String KEY_IS_LOGGED_IN = "is_logged_in";
private EditText etIdentifier, etPassword;
private MaterialButton btnLogin;
private ProgressBar progressBar;
private ExecutorService executor;
private String currentPassword;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// Enable hardware acceleration
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Check if user is already logged in
if (isUserLoggedIn()) {
navigateToMainActivity();
return;
}
initializeViews();
setupListeners();
executor = Executors.newSingleThreadExecutor();
}
private void initializeViews() {
etIdentifier = findViewById(R.id.et_identifier);
etPassword = findViewById(R.id.et_password);
btnLogin = findViewById(R.id.btn_login);
progressBar = findViewById(R.id.progress_bar);
}
private void setupListeners() {
btnLogin.setOnClickListener(v -> {
String identifier = etIdentifier.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (validateInput(identifier, password)) {
performLogin(identifier, password);
}
});
}
private boolean validateInput(String identifier, String password) {
if (TextUtils.isEmpty(identifier)) {
etIdentifier.setError("Email/Username tidak boleh kosong");
etIdentifier.requestFocus();
return false;
}
if (TextUtils.isEmpty(password)) {
etPassword.setError("Password tidak boleh kosong");
etPassword.requestFocus();
return false;
}
if (password.length() < 6) {
etPassword.setError("Password minimal 6 karakter");
etPassword.requestFocus();
return false;
}
return true;
}
private void performLogin(String identifier, String password) {
setLoadingState(true);
currentPassword = password;
executor.execute(() -> {
try {
// Create JSON payload
JSONObject jsonPayload = new JSONObject();
jsonPayload.put("identifier", identifier);
jsonPayload.put("password", password);
// Setup HTTP connection using BuildConfig
URL url = new URL(BuildConfig.BACKEND_BASE_URL + "/users/auth");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("accept", "*/*");
connection.setDoOutput(true);
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
// Send request
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonPayload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
// Get response
int responseCode = connection.getResponseCode();
Log.d(TAG, "Response Code: " + responseCode);
BufferedReader reader;
if (responseCode >= 200 && responseCode < 300) {
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
} else {
reader = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
}
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
connection.disconnect();
Log.d(TAG, "Response: " + response.toString());
// Parse response on main thread
runOnUiThread(() -> handleLoginResponse(responseCode, response.toString()));
} catch (Exception e) {
Log.e(TAG, "Login error: " + e.getMessage(), e);
runOnUiThread(() -> {
setLoadingState(false);
Toast.makeText(this, "Koneksi gagal: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
}
});
}
private void handleLoginResponse(int responseCode, String responseBody) {
setLoadingState(false);
try {
JSONObject jsonResponse = new JSONObject(responseBody);
if (responseCode >= 200 && responseCode < 300) {
// Login successful
String message = jsonResponse.optString("message", "");
int status = jsonResponse.optInt("status", 0);
if (status == 200) {
JSONObject result = jsonResponse.getJSONObject("result");
String token = result.getString("token");
JSONObject userData = result.getJSONObject("user");
// Log user data to console
logUserData(userData);
// Save login data
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_TOKEN, token);
editor.putString(KEY_USER_DATA, userData.toString());
editor.putBoolean(KEY_IS_LOGGED_IN, true);
editor.putString("current_password", currentPassword);
editor.apply();
Toast.makeText(this, "Login berhasil! " + message, Toast.LENGTH_SHORT).show();
// Navigate to MainActivity
navigateToMainActivity();
} else {
Toast.makeText(this, "Login gagal: " + message, Toast.LENGTH_LONG).show();
}
} else {
// Login failed
String errorMessage = jsonResponse.optString("message", "Login gagal");
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
Log.e(TAG, "Login failed: " + errorMessage);
}
} catch (Exception e) {
Log.e(TAG, "Error parsing response: " + e.getMessage(), e);
Toast.makeText(this, "Error parsing response", Toast.LENGTH_LONG).show();
}
}
// Method to log user data to console
private void logUserData(JSONObject userData) {
try {
StringBuilder userInfo = new StringBuilder();
userInfo.append("\n=== USER LOGIN DETAILS ===");
userInfo.append("\nID: ").append(userData.optString("id", "N/A"));
userInfo.append("\nName: ").append(userData.optString("name", "N/A"));
userInfo.append("\nEmail: ").append(userData.optString("email", "N/A"));
userInfo.append("\nRole: ").append(userData.optString("role", "N/A"));
userInfo.append("\nPhone: ").append(userData.optString("phone", "N/A"));
userInfo.append("\nPosition: ").append(userData.optString("position", "N/A"));
userInfo.append("\nMID: ").append(userData.optString("mid", "N/A"));
userInfo.append("\nTID: ").append(userData.optString("tid", "N/A"));
userInfo.append("\nLast Login: ").append(userData.optString("last_login", "N/A"));
userInfo.append("\n==========================");
Log.i(TAG, userInfo.toString());
} catch (Exception e) {
Log.e(TAG, "Error logging user data: " + e.getMessage());
}
}
private void setLoadingState(boolean isLoading) {
btnLogin.setEnabled(!isLoading);
etIdentifier.setEnabled(!isLoading);
etPassword.setEnabled(!isLoading);
progressBar.setVisibility(isLoading ? View.VISIBLE : View.GONE);
if (isLoading) {
btnLogin.setText("Memproses...");
} else {
btnLogin.setText("MASUK");
}
}
private boolean isUserLoggedIn() {
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
return prefs.getBoolean(KEY_IS_LOGGED_IN, false);
}
private void navigateToMainActivity() {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
// Public static methods untuk mengakses data login dari activity lain
public static String getToken(android.content.Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
return prefs.getString(KEY_TOKEN, "");
}
public static String getUserData(android.content.Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
return prefs.getString(KEY_USER_DATA, "");
}
public static JSONObject getUserDataAsJson(android.content.Context context) {
try {
String userData = getUserData(context);
if (!TextUtils.isEmpty(userData)) {
return new JSONObject(userData);
}
} catch (Exception e) {
Log.e("LoginActivity", "Error parsing user data: " + e.getMessage());
}
return null;
}
public static void logout(android.content.Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.clear();
editor.apply();
// Navigate back to login
Intent intent = new Intent(context, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
}
public static boolean isLoggedIn(android.content.Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
return prefs.getBoolean(KEY_IS_LOGGED_IN, false);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (executor != null && !executor.isShutdown()) {
executor.shutdown();
}
}
}

View File

@@ -6,8 +6,12 @@ import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ImageView;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.WindowManager;
import android.view.Menu;
import android.view.MenuItem;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
@@ -18,18 +22,34 @@ import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.button.MaterialButton;
import com.example.bdkipoc.R;
import com.example.bdkipoc.cetakulang.ReprintActivity;
import com.example.bdkipoc.cetakulang.ReprintAdapterActivity;
import com.example.bdkipoc.R;
import com.example.bdkipoc.histori.HistoryActivity;
import com.example.bdkipoc.transaction.CreateTransactionActivity;
import com.example.bdkipoc.transaction.ResultTransactionActivity;
import com.example.bdkipoc.settlement.SettlementActivity;
import com.example.bdkipoc.bantuan.BantuanActivity;
import com.example.bdkipoc.infotoko.InfoTokoActivity;
import org.json.JSONObject;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private boolean isExpanded = false; // False = showing only 9 main menus, True = showing all 15 menus
private MaterialButton btnLainnya;
private MaterialButton logoutButton;
private TextView tvUserName, tvUserRole;
private LinearLayout userInfoSection;
private String authToken;
private JSONObject userData;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
@@ -54,6 +74,17 @@ public class MainActivity extends AppCompatActivity {
);
super.onCreate(savedInstanceState);
// Check if user is logged in
if (!LoginActivity.isLoggedIn(this)) {
// User is not logged in, redirect to login
Intent intent = new Intent(this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
return;
}
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
@@ -63,8 +94,20 @@ public class MainActivity extends AppCompatActivity {
return insets;
});
// Load user data
loadUserData();
// Initialize views
btnLainnya = findViewById(R.id.btn_lainnya);
logoutButton = findViewById(R.id.logout_button);
tvUserName = findViewById(R.id.tv_user_name);
tvUserRole = findViewById(R.id.tv_user_role);
userInfoSection = findViewById(R.id.user_info_section);
// Setup logout button
if (logoutButton != null) {
logoutButton.setOnClickListener(v -> performLogout());
}
// Check if we're returning from a completed transaction
checkTransactionCompletion();
@@ -74,6 +117,144 @@ public class MainActivity extends AppCompatActivity {
// Setup menu listeners
setupMenuListeners();
// Display user info
displayUserInfo();
}
private void loadUserData() {
// Get authentication token
authToken = LoginActivity.getToken(this);
// Get user data
userData = LoginActivity.getUserDataAsJson(this);
if (userData != null) {
StringBuilder userInfo = new StringBuilder();
userInfo.append("\n=== CURRENT USER DETAILS ===");
userInfo.append("\nID: ").append(userData.optString("id", "N/A"));
userInfo.append("\nName: ").append(userData.optString("name", "N/A"));
userInfo.append("\nEmail: ").append(userData.optString("email", "N/A"));
userInfo.append("\nRole: ").append(userData.optString("role", "N/A"));
userInfo.append("\nPhone: ").append(userData.optString("phone", "N/A"));
userInfo.append("\nPosition: ").append(userData.optString("position", "N/A"));
userInfo.append("\nMID: ").append(userData.optString("mid", "N/A"));
userInfo.append("\nTID: ").append(userData.optString("tid", "N/A"));
userInfo.append("\nLast Login: ").append(userData.optString("last_login", "N/A"));
userInfo.append("\n==========================");
Log.i(TAG, userInfo.toString());
}
}
private void displayUserInfo() {
if (userData != null) {
String userName = userData.optString("name", "User");
String userRole = userData.optString("role", "");
String mid = userData.optString("mid", "");
String tid = userData.optString("tid", "");
// Display welcome message
String welcomeMessage = "Selamat datang, " + userName;
if (!userRole.isEmpty()) {
welcomeMessage += " (" + userRole + ")";
}
// Show welcome toast
Toast.makeText(this, welcomeMessage, Toast.LENGTH_LONG).show();
// Update merchant card with user info
if (tvUserName != null) {
tvUserName.setText(userName);
}
if (tvUserRole != null && !userRole.isEmpty()) {
tvUserRole.setText("(" + userRole + ")");
tvUserRole.setVisibility(View.VISIBLE);
} else if (tvUserRole != null) {
tvUserRole.setVisibility(View.GONE);
}
// Update MID and TID
TextView tvStoreName = findViewById(R.id.tv_store_name);
TextView tvStoreAddress = findViewById(R.id.tv_store_address);
TextView tvMid = findViewById(R.id.tv_mid);
TextView tvTid = findViewById(R.id.tv_tid);
if (tvStoreName != null) {
String storeName = userData.optString("store_name", "TOKO KLONTONG PAK EKO");
tvStoreName.setText(storeName);
}
if (tvStoreAddress != null) {
String storeAddress = userData.optString("store_address", "Ciputat Baru, Tangsel");
tvStoreAddress.setText(storeAddress);
}
if (tvMid != null && !mid.isEmpty()) {
tvMid.setText("MID: " + mid);
}
if (tvTid != null && !tid.isEmpty()) {
tvTid.setText("TID: " + tid);
}
// Show user info section in merchant card
if (userInfoSection != null) {
userInfoSection.setVisibility(View.VISIBLE);
}
}
}
// Method to get auth token for use in other activities
public static String getAuthToken(android.content.Context context) {
return LoginActivity.getToken(context);
}
// Method to get user data for use in other activities
public static JSONObject getUserData(android.content.Context context) {
return LoginActivity.getUserDataAsJson(context);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Menu logout sudah ada di custom toolbar, tidak perlu action bar menu
return false; // Return false to not show action bar menu
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// No longer needed since logout is handled by custom toolbar button
return super.onOptionsItemSelected(item);
}
private void performLogout() {
// Show confirmation dialog with red theme
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this);
builder.setTitle("Logout");
builder.setMessage("Apakah Anda yakin ingin keluar dari aplikasi?");
builder.setIcon(android.R.drawable.ic_dialog_alert);
// Set positive button (Ya)
builder.setPositiveButton("Ya", (dialog, which) -> {
// Show logout progress
Toast.makeText(this, "Logging out...", Toast.LENGTH_SHORT).show();
// Perform logout
LoginActivity.logout(this);
});
// Set negative button (Batal)
builder.setNegativeButton("Batal", (dialog, which) -> {
dialog.dismiss();
});
// Create and show dialog
androidx.appcompat.app.AlertDialog dialog = builder.create();
dialog.show();
// Customize button colors
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE)
.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE)
.setTextColor(getResources().getColor(android.R.color.black));
}
private void setupInitialMenuState() {
@@ -157,32 +338,32 @@ public class MainActivity extends AppCompatActivity {
CardView cardView = findViewById(cardId);
if (cardView != null) {
cardView.setOnClickListener(v -> {
// ✅ ENHANCED: Navigate with payment type information
// ✅ ENHANCED: Navigate with payment type information and auth token
if (cardId == R.id.card_kartu_kredit) {
navigateToCreateTransaction("credit_card", cardId, "Kartu Kredit");
} else if (cardId == R.id.card_kartu_debit) {
navigateToCreateTransaction("debit_card", cardId, "Kartu Debit");
} else if (cardId == R.id.card_qris) {
startActivity(new Intent(MainActivity.this, QrisActivity.class));
startActivityWithAuth(new Intent(MainActivity.this, QrisActivity.class));
// Col-2
} else if (cardId == R.id.card_transfer) {
navigateToCreateTransaction("transfer", cardId, "Transfer");
Toast.makeText(this, "Transfer - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_uang_elektronik) {
navigateToCreateTransaction("e_money", cardId, "Uang Elektronik");
} else if (cardId == R.id.card_cetak_ulang) {
startActivity(new Intent(MainActivity.this, ReprintActivity.class));
startActivityWithAuth(new Intent(MainActivity.this, ReprintActivity.class));
// Col-3
} else if (cardId == R.id.card_refund) {
navigateToCreateTransaction("refund", cardId, "Refund");
Toast.makeText(this, "Refund - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_settlement) {
Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show();
startActivityWithAuth(new Intent(MainActivity.this, SettlementActivity.class));
} else if (cardId == R.id.card_histori) {
startActivity(new Intent(MainActivity.this, HistoryActivity.class));
startActivityWithAuth(new Intent(MainActivity.this, HistoryActivity.class));
// Col-4
} else if (cardId == R.id.card_bantuan) {
Toast.makeText(this, "Bantuan - Coming Soon", Toast.LENGTH_SHORT).show();
startActivityWithAuth(new Intent(MainActivity.this, BantuanActivity.class));
} else if (cardId == R.id.card_info_toko) {
Toast.makeText(this, "Info Toko - Coming Soon", Toast.LENGTH_SHORT).show();
startActivityWithAuth(new Intent(MainActivity.this, InfoTokoActivity.class));
} else if (cardId == R.id.card_pengaturan) {
Toast.makeText(this, "Pengaturan - Coming Soon", Toast.LENGTH_SHORT).show();
} else {
@@ -234,17 +415,18 @@ public class MainActivity extends AppCompatActivity {
}
});
// Set up scan dan bayar card click listener
LinearLayout scanBayarContent = findViewById(R.id.scan_bayar_content);
if (scanBayarContent != null) {
scanBayarContent.setOnClickListener(v -> {
// Navigate to QRIS payment activity
startActivity(new Intent(MainActivity.this, QrisActivity.class));
// Setup Banner Image
ImageView bannerQris = findViewById(R.id.banner_qris);
if (bannerQris != null) {
bannerQris.setOnClickListener(v -> {
// Pindah ke halaman QRIS
startActivityWithAuth(new Intent(MainActivity.this, QrisActivity.class));
});
}
}
// ✅ NEW: Enhanced navigation method with payment type information
// ✅ NEW: Enhanced navigation method with payment type information and auth token
private void navigateToCreateTransaction(String paymentType, int cardMenuId, String cardName) {
try {
Intent intent = new Intent(MainActivity.this, CreateTransactionActivity.class);
@@ -255,21 +437,45 @@ public class MainActivity extends AppCompatActivity {
intent.putExtra("CARD_NAME", cardName);
intent.putExtra("CALLING_ACTIVITY", "MainActivity");
// ✅ NEW: Pass authentication data
intent.putExtra("AUTH_TOKEN", authToken);
if (userData != null) {
intent.putExtra("USER_DATA", userData.toString());
}
// ✅ DEBUG: Log navigation details
android.util.Log.d("MainActivity", "=== NAVIGATING TO CREATE TRANSACTION ===");
android.util.Log.d("MainActivity", "Payment Type: " + paymentType);
android.util.Log.d("MainActivity", "Card Menu ID: " + cardMenuId);
android.util.Log.d("MainActivity", "Card Name: " + cardName);
android.util.Log.d("MainActivity", "========================================");
Log.d(TAG, "=== NAVIGATING TO CREATE TRANSACTION ===");
Log.d(TAG, "Payment Type: " + paymentType);
Log.d(TAG, "Card Menu ID: " + cardMenuId);
Log.d(TAG, "Card Name: " + cardName);
Log.d(TAG, "Auth Token: " + (authToken != null ? "" : ""));
Log.d(TAG, "========================================");
startActivity(intent);
} catch (Exception e) {
android.util.Log.e("MainActivity", "Error navigating to CreateTransaction: " + e.getMessage(), e);
Log.e(TAG, "Error navigating to CreateTransaction: " + e.getMessage(), e);
Toast.makeText(this, "Error opening transaction: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
// ✅ NEW: Helper method to start activity with authentication data
private void startActivityWithAuth(Intent intent) {
try {
// Add authentication data to intent
intent.putExtra("AUTH_TOKEN", authToken);
if (userData != null) {
intent.putExtra("USER_DATA", userData.toString());
}
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "Error starting activity: " + e.getMessage(), e);
Toast.makeText(this, "Error opening activity: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
// ✅ NEW: Helper method to get payment type from card ID (for backward compatibility)
private String getPaymentTypeFromCardId(int cardId) {
if (cardId == R.id.card_kartu_kredit) {
@@ -285,7 +491,7 @@ public class MainActivity extends AppCompatActivity {
} else if (cardId == R.id.card_refund) {
return "refund";
} else {
android.util.Log.w("MainActivity", "Unknown card ID: " + cardId + ", defaulting to credit_card");
Log.w(TAG, "Unknown card ID: " + cardId + ", defaulting to credit_card");
return "credit_card";
}
}
@@ -374,24 +580,35 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onResume() {
super.onResume();
// Check if user is still logged in
if (!LoginActivity.isLoggedIn(this)) {
// User is not logged in, redirect to login
Intent intent = new Intent(this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
return;
}
// Clear any transaction completion flags to avoid repeated messages
getIntent().removeExtra("transaction_completed");
getIntent().removeExtra("transaction_amount");
// ✅ NEW: Log resume for debugging
android.util.Log.d("MainActivity", "MainActivity resumed");
Log.d(TAG, "MainActivity resumed");
}
@Override
protected void onPause() {
super.onPause();
android.util.Log.d("MainActivity", "MainActivity paused");
Log.d(TAG, "MainActivity paused");
}
@Override
protected void onDestroy() {
super.onDestroy();
android.util.Log.d("MainActivity", "MainActivity destroyed");
Log.d(TAG, "MainActivity destroyed");
}
// ✅ NEW: Method to handle direct payment type launch (for external calls)
@@ -400,6 +617,14 @@ public class MainActivity extends AppCompatActivity {
intent.putExtra("PAYMENT_TYPE", paymentType);
intent.putExtra("CARD_NAME", cardName);
intent.putExtra("CALLING_ACTIVITY", "External");
// Add authentication data
intent.putExtra("AUTH_TOKEN", LoginActivity.getToken(context));
String userData = LoginActivity.getUserData(context);
if (!userData.isEmpty()) {
intent.putExtra("USER_DATA", userData);
}
return intent;
}
@@ -409,7 +634,7 @@ public class MainActivity extends AppCompatActivity {
if (cardView != null) {
cardView.performClick();
} else {
android.util.Log.w("MainActivity", "Card not found for ID: " + cardId);
Log.w(TAG, "Card not found for ID: " + cardId);
}
}
@@ -431,7 +656,7 @@ public class MainActivity extends AppCompatActivity {
// ✅ NEW: Debug method to log all card IDs and their payment types
private void debugCardMappings() {
android.util.Log.d("MainActivity", "=== CARD PAYMENT TYPE MAPPINGS ===");
Log.d(TAG, "=== CARD PAYMENT TYPE MAPPINGS ===");
int[] cardIds = {
R.id.card_kartu_kredit, R.id.card_kartu_debit, R.id.card_qris,
@@ -441,10 +666,10 @@ public class MainActivity extends AppCompatActivity {
for (int cardId : cardIds) {
String paymentType = getPaymentTypeFromCardId(cardId);
String cardName = getCardNameFromCardId(cardId);
android.util.Log.d("MainActivity",
Log.d(TAG,
"Card ID: " + cardId + " -> Payment Type: " + paymentType + " -> Name: " + cardName);
}
android.util.Log.d("MainActivity", "==================================");
Log.d(TAG, "==================================");
}
}

View File

@@ -1,9 +1,12 @@
package com.example.bdkipoc;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -13,7 +16,13 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import com.sunmi.peripheral.printer.InnerResultCallback;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -29,32 +38,6 @@ import java.util.Map;
import com.example.bdkipoc.cetakulang.ReprintActivity;
public class ReceiptActivity extends AppCompatActivity {
// Views
private LinearLayout backNavigation;
private ImageView backArrow;
private TextView toolbarTitle;
// Receipt details
private TextView merchantName;
private TextView merchantLocation;
private TextView midText;
private TextView tidText;
private TextView transactionNumber;
private TextView transactionDate;
private TextView paymentMethod;
private TextView cardType;
private TextView transactionTotal;
private TextView taxPercentage;
private TextView serviceFee;
private TextView finalTotal;
// Action buttons
private LinearLayout printButton;
private LinearLayout emailButton;
private Button finishButton;
// ✅ ENHANCED: Mapping dari technical issuer ke display name
private static final Map<String, String> ISSUER_DISPLAY_MAP = new HashMap<String, String>() {{
put("airpay shopee", "ShopeePay");
put("shopeepay", "ShopeePay");
@@ -76,6 +59,61 @@ public class ReceiptActivity extends AppCompatActivity {
put("qris", "QRIS");
}};
// Views
private LinearLayout backNavigation;
private ImageView backArrow;
private TextView toolbarTitle;
private CardView receiptCard;
// Receipt details
private TextView merchantName;
private TextView merchantLocation;
private TextView midText;
private TextView tidText;
private TextView transactionNumber;
private TextView transactionDate;
private TextView paymentMethod;
private TextView cardType;
private TextView transactionTotal;
private TextView taxPercentage;
private TextView serviceFee;
private TextView finalTotal;
// Action buttons
private LinearLayout printButton;
private LinearLayout emailButton;
private Button finishButton;
// Printer callback
private final InnerResultCallback printCallback = new InnerResultCallback() {
@Override
public void onRunResult(boolean isSuccess) throws RemoteException {
runOnUiThread(() -> {
if (isSuccess) {
showToast("Struk berhasil dicetak");
} else {
showToast("Gagal mencetak struk");
}
});
}
@Override
public void onReturnString(String result) throws RemoteException {
Log.d("ReceiptActivity", "Print result: " + result);
}
@Override
public void onRaiseException(int code, String msg) throws RemoteException {
runOnUiThread(() -> showToast("Printer error: " + msg));
Log.e("ReceiptActivity", "Printer exception: " + code + " - " + msg);
}
@Override
public void onPrintResult(int code, String msg) throws RemoteException {
Log.d("ReceiptActivity", "Print result code: " + code + ", msg: " + msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -90,26 +128,15 @@ public class ReceiptActivity extends AppCompatActivity {
loadTransactionData();
}
private void setStatusBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
// Make status bar icons white (for dark red background)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
decorView.setSystemUiVisibility(0); // Clear light status bar flag
}
}
}
private void initializeViews() {
// Navigation
backNavigation = findViewById(R.id.back_navigation);
backArrow = findViewById(R.id.backArrow);
toolbarTitle = findViewById(R.id.toolbarTitle);
// Receipt card view that will be printed
receiptCard = findViewById(R.id.receipt_card);
// Receipt details
merchantName = findViewById(R.id.merchant_name);
merchantLocation = findViewById(R.id.merchant_location);
@@ -130,6 +157,129 @@ public class ReceiptActivity extends AppCompatActivity {
finishButton = findViewById(R.id.finish_button);
}
private void handlePrintReceipt() {
try {
// Check if printer service is available
if (MyApplication.app.sunmiPrinterService == null) {
showToast("Printer tidak tersedia");
return;
}
// Get all the data from the views
String merchantNameText = merchantName.getText().toString();
String merchantLocationText = merchantLocation.getText().toString();
String midTextValue = midText.getText().toString();
String tidTextValue = tidText.getText().toString();
String transactionNumberText = transactionNumber.getText().toString();
String transactionDateText = transactionDate.getText().toString();
String paymentMethodText = paymentMethod.getText().toString();
String cardTypeText = cardType.getText().toString();
String transactionTotalText = transactionTotal.getText().toString();
String taxPercentageText = taxPercentage.getText().toString();
String serviceFeeText = serviceFee.getText().toString();
String finalTotalText = finalTotal.getText().toString();
showToast("Mencetak struk...");
try {
// Start printing
MyApplication.app.sunmiPrinterService.enterPrinterBuffer(true);
// Set alignment to center
MyApplication.app.sunmiPrinterService.setAlignment(1, null);
// Print header
MyApplication.app.sunmiPrinterService.printText("# Payvora PRO\n\n", null);
// Set alignment to left
MyApplication.app.sunmiPrinterService.setAlignment(0, null);
// Print merchant info
MyApplication.app.sunmiPrinterService.printText(merchantNameText + "\n", null);
MyApplication.app.sunmiPrinterService.printText(merchantLocationText + "\n\n", null);
// Print MID/TID
MyApplication.app.sunmiPrinterService.printText(midTextValue + " | " + tidTextValue + "\n\n", null);
// Print transaction details
MyApplication.app.sunmiPrinterService.printText("Nomor transaksi " + transactionNumberText + "\n", null);
MyApplication.app.sunmiPrinterService.printText("Tanggal transaksi " + transactionDateText + "\n", null);
MyApplication.app.sunmiPrinterService.printText("Metode pembayaran " + paymentMethodText + "\n", null);
MyApplication.app.sunmiPrinterService.printText("Jenis Kartu " + cardTypeText + "\n\n", null);
// Print amounts
MyApplication.app.sunmiPrinterService.printText("Total transaksi " + transactionTotalText + "\n", null);
MyApplication.app.sunmiPrinterService.printText("Pajak (%) " + taxPercentageText + "\n", null);
MyApplication.app.sunmiPrinterService.printText("Biaya Layanan " + serviceFeeText + "\n\n", null);
// Print total in bold
MyApplication.app.sunmiPrinterService.printText("**TOTAL** **" + finalTotalText + "**\n", null);
// Add some line feeds
MyApplication.app.sunmiPrinterService.lineWrap(4, null);
// Exit buffer mode
MyApplication.app.sunmiPrinterService.exitPrinterBuffer(true);
} catch (RemoteException e) {
e.printStackTrace();
showToast("Error printer: " + e.getMessage());
}
} catch (Exception e) {
Log.e("ReceiptActivity", "Print error", e);
showToast("Error saat mencetak: " + e.getMessage());
}
}
private void setStatusBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
// Make status bar icons white (for dark red background)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
decorView.setSystemUiVisibility(0); // Clear light status bar flag
}
}
}
/** Create Bitmap from View */
private Bitmap createBitmapFromView(View view) {
try {
// Enable drawing cache
view.setDrawingCacheEnabled(true);
view.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
// Measure and layout the view to ensure it has correct dimensions
view.measure(
View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
// Create bitmap
Bitmap bitmap = Bitmap.createBitmap(
view.getWidth(),
view.getHeight(),
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
} catch (Exception e) {
Log.e("ReceiptActivity", "Error creating bitmap", e);
return null;
} finally {
view.setDrawingCacheEnabled(false);
}
}
private void setupClickListeners() {
// Back navigation - Goes back to previous activity
backNavigation.setOnClickListener(v -> handleBackNavigation());
@@ -279,20 +429,17 @@ public class ReceiptActivity extends AppCompatActivity {
return getCurrentDateTime();
}
/**
* Format date specifically for EMV transactions
*/
private String formatDateForEmvTransaction(String dateString) {
try {
// EMV transactions might use different date formats
String[] inputFormats = {
"dd MMMM yyyy HH:mm",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"dd/MM/yyyy HH:mm"
};
SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss", new Locale("id", "ID"));
for (String format : inputFormats) {
try {
@@ -319,11 +466,16 @@ public class ReceiptActivity extends AppCompatActivity {
private String formatDateFromCreatedAt(String createdAt) {
try {
// Input format from database: "yyyy-MM-dd HH:mm:ss"
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// Input format from database: "yyyy-MM-dd HH:mm:ss" atau "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
SimpleDateFormat inputFormat;
if (createdAt.contains("T")) {
inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
} else {
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
}
// Output format for receipt: "dd/MM/yyyy HH:mm"
SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
// Output format for receipt: "dd/MM/yy HH:mm:ss"
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss", new Locale("id", "ID"));
Date date = inputFormat.parse(createdAt);
String formatted = outputFormat.format(date);
@@ -333,7 +485,7 @@ public class ReceiptActivity extends AppCompatActivity {
} catch (Exception e) {
Log.e("ReceiptActivity", "Error formatting date: " + createdAt, e);
// Fallback: try alternative formats or return as-is
// Fallback: return original string if parsing fails
return createdAt;
}
}
@@ -343,6 +495,20 @@ public class ReceiptActivity extends AppCompatActivity {
*/
private String getDisplayPaymentMethod(String channelCode, String fallbackPaymentMethod,
boolean isEmvTransaction, boolean emvMode) {
// ✅ NEW: Check if this is from ReprintActivity
String callingActivity = getIntent().getStringExtra("calling_activity");
boolean isFromReprintActivity = "ReprintActivity".equals(callingActivity);
if (isFromReprintActivity) {
// For ReprintActivity, use the payment_method that was already processed
if (fallbackPaymentMethod != null && !fallbackPaymentMethod.isEmpty()) {
Log.d("ReceiptActivity", "✅ REPRINT: Using processed payment method: " + fallbackPaymentMethod);
return fallbackPaymentMethod;
}
}
// Continue with existing logic for other sources
if (isEmvTransaction) {
// For EMV transactions, be more specific
if (emvMode) {
@@ -430,9 +596,20 @@ public class ReceiptActivity extends AppCompatActivity {
Log.d("ReceiptActivity", "🔍 ENHANCED CARD TYPE DETECTION:");
Log.d("ReceiptActivity", " Input Card Type: " + cardTypeStr);
Log.d("ReceiptActivity", " Input Acquirer: " + acquirer);
Log.d("ReceiptActivity", " Channel Code: " + channelCode);
Log.d("ReceiptActivity", " Is EMV Transaction: " + isEmvTransaction);
Log.d("ReceiptActivity", " Is QRIS Transaction: " + isQrisTransaction);
// ✅ NEW: Check if this is from ReprintActivity
String callingActivity = getIntent().getStringExtra("calling_activity");
boolean isFromReprintActivity = "ReprintActivity".equals(callingActivity);
Log.d("ReceiptActivity", " Is from ReprintActivity: " + isFromReprintActivity);
if (isFromReprintActivity) {
return handleReprintActivityCardType(cardTypeStr, acquirer, channelCode, referenceId);
}
// Continue with existing logic for other sources
if (isEmvTransaction) {
// ✅ FOR EMV TRANSACTIONS: Priority to cardTypeStr from ResultTransactionActivity
if (cardTypeStr != null && !cardTypeStr.isEmpty() && !cardTypeStr.equalsIgnoreCase("unknown")) {
@@ -492,6 +669,98 @@ public class ReceiptActivity extends AppCompatActivity {
}
}
private String handleReprintActivityCardType(String cardTypeStr, String acquirer, String channelCode, String referenceId) {
Log.d("ReceiptActivity", "🔍 HANDLING REPRINT ACTIVITY CARD TYPE:");
Log.d("ReceiptActivity", " Provided Card Type: " + cardTypeStr);
Log.d("ReceiptActivity", " Channel Code: " + channelCode);
Log.d("ReceiptActivity", " Acquirer: " + acquirer);
// Priority 1: If channelCode indicates QRIS, search for real acquirer
if ("QRIS".equalsIgnoreCase(channelCode) || "RETAIL_OUTLET".equalsIgnoreCase(channelCode)) {
Log.d("ReceiptActivity", "🔍 QRIS detected from ReprintActivity");
if (referenceId != null && !referenceId.isEmpty()) {
String realAcquirer = fetchRealAcquirerSync(referenceId);
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
String mappedAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);
Log.d("ReceiptActivity", "✅ REPRINT QRIS: Found real acquirer: " + realAcquirer + " -> " + mappedAcquirer);
return mappedAcquirer;
} else {
Log.w("ReceiptActivity", "⚠️ REPRINT QRIS: No real acquirer found, using generic QRIS");
// Start async search for better results
fetchRealAcquirerFromWebhook(referenceId);
return "QRIS";
}
} else {
return "QRIS";
}
}
// Priority 2: Use cardTypeStr if it's meaningful
if (cardTypeStr != null && !cardTypeStr.isEmpty() &&
!cardTypeStr.equalsIgnoreCase("unknown") &&
!cardTypeStr.equalsIgnoreCase("retail_outlet")) {
Log.d("ReceiptActivity", "✅ REPRINT: Using provided card type: " + cardTypeStr);
return cardTypeStr;
}
// Priority 3: Map from channelCode
if (channelCode != null && !channelCode.isEmpty()) {
String mappedFromChannel = mapChannelCodeToCardType(channelCode);
Log.d("ReceiptActivity", "✅ REPRINT: Mapped from channel code: " + channelCode + " -> " + mappedFromChannel);
return mappedFromChannel;
}
// Priority 4: Use acquirer if available
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
String mappedAcquirer = getCardTypeFromAcquirer(acquirer, channelCode, null);
Log.d("ReceiptActivity", "✅ REPRINT: Using mapped acquirer: " + mappedAcquirer);
return mappedAcquirer;
}
// Final fallback
Log.w("ReceiptActivity", "⚠️ REPRINT: Using final fallback: Unknown");
return "Unknown";
}
private String mapChannelCodeToCardType(String channelCode) {
if (channelCode == null || channelCode.isEmpty()) {
return "Unknown";
}
String code = channelCode.toUpperCase().trim();
switch (code) {
case "QRIS":
case "RETAIL_OUTLET":
return "QRIS";
case "DEBIT":
case "DEBIT_CARD":
return "Visa"; // Default for debit
case "CREDIT":
case "CREDIT_CARD":
return "Mastercard"; // Default for credit
case "BCA":
return "BCA";
case "MANDIRI":
return "Mandiri";
case "BNI":
return "BNI";
case "BRI":
return "BRI";
case "PERMATA":
return "Permata";
case "CIMB":
return "CIMB Niaga";
case "DANAMON":
return "Danamon";
case "BSI":
return "BSI";
default:
return capitalizeFirstLetter(channelCode.replace("_", " "));
}
}
private String getCardTypeFromAcquirer(String acquirer, String channelCode, String fallbackCardType) {
// STEP 1: If we have a valid acquirer that's not generic "qris", use it
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
@@ -1107,12 +1376,6 @@ public class ReceiptActivity extends AppCompatActivity {
return sdf.format(new Date());
}
private void handlePrintReceipt() {
// Handle print receipt action
// In real app, this would integrate with printer
showToast("Mencetak struk...");
}
private void handleEmailReceipt() {
// Handle email receipt action
// In real app, this would open email intent

View File

@@ -0,0 +1,574 @@
package com.example.bdkipoc.bantuan;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.example.bdkipoc.R;
import com.example.bdkipoc.LoginActivity;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BantuanActivity extends AppCompatActivity {
// UI Components
private LinearLayout tabUmum, tabRiwayat;
private TextView textUmum, textRiwayat;
private ScrollView contentUmum, contentRiwayat;
private LinearLayout riwayatContainer;
private LinearLayout btnForm, btnWhatsApp, backNavigation;
// State
private boolean isUmumTabActive = true;
private List<TicketData> ticketList = new ArrayList<>();
private ExecutorService executor = Executors.newSingleThreadExecutor();
private Handler mainHandler = new Handler(Looper.getMainLooper());
// ✅ Enhanced TicketData class with date parsing
public static class TicketData {
public String createdAt, ticketCode, issueName, status;
public Date parsedDate; // Added for sorting
public TicketData(String createdAt, String ticketCode, String issueName, String status) {
this.createdAt = createdAt;
this.ticketCode = ticketCode;
this.issueName = issueName;
this.status = status;
this.parsedDate = parseDate(createdAt);
}
// Parse date from ISO format to Date object
private Date parseDate(String dateString) {
try {
// Try different date formats
SimpleDateFormat[] formats = {
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()),
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()),
new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
};
for (SimpleDateFormat format : formats) {
try {
return format.parse(dateString);
} catch (Exception e) {
// Continue to next format
}
}
// If all parsing fails, return current date
return new Date();
} catch (Exception e) {
return new Date(); // Fallback to current date
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ✅ Check authentication
if (!LoginActivity.isLoggedIn(this)) {
LoginActivity.logout(this);
return;
}
setContentView(R.layout.activity_bantuan);
initViews();
setupListeners();
showUmumTab();
loadTicketData();
}
private void initViews() {
// Tabs
tabUmum = findViewById(R.id.tab_umum);
tabRiwayat = findViewById(R.id.tab_riwayat);
textUmum = findViewById(R.id.text_umum);
textRiwayat = findViewById(R.id.text_riwayat);
// Content
contentUmum = findViewById(R.id.content_umum);
contentRiwayat = findViewById(R.id.content_riwayat);
// Riwayat container
if (contentRiwayat != null && contentRiwayat.getChildCount() > 0) {
View child = contentRiwayat.getChildAt(0);
if (child instanceof LinearLayout) {
riwayatContainer = (LinearLayout) child;
}
}
// Buttons
btnForm = findViewById(R.id.btn_form);
btnWhatsApp = findViewById(R.id.btn_whatsapp);
backNavigation = findViewById(R.id.back_navigation);
}
private void setupListeners() {
if (backNavigation != null) {
backNavigation.setOnClickListener(v -> onBackPressed());
}
if (tabUmum != null) {
tabUmum.setOnClickListener(v -> showUmumTab());
}
if (tabRiwayat != null) {
tabRiwayat.setOnClickListener(v -> showRiwayatTab());
}
if (btnForm != null) {
btnForm.setOnClickListener(v -> {
Intent intent = new Intent(this, BantuanFormActivity.class);
startActivity(intent);
});
}
if (btnWhatsApp != null) {
btnWhatsApp.setOnClickListener(v -> openWhatsAppCS());
}
}
private void showUmumTab() {
if (isUmumTabActive) return;
isUmumTabActive = true;
if (contentUmum != null) contentUmum.setVisibility(View.VISIBLE);
if (contentRiwayat != null) contentRiwayat.setVisibility(View.GONE);
updateTabAppearance();
}
private void showRiwayatTab() {
if (!isUmumTabActive) return;
isUmumTabActive = false;
if (contentUmum != null) contentUmum.setVisibility(View.GONE);
if (contentRiwayat != null) contentRiwayat.setVisibility(View.VISIBLE);
updateTabAppearance();
// Load data if available
if (!ticketList.isEmpty()) {
populateRiwayatContent();
} else {
showLoadingMessage();
}
}
private void updateTabAppearance() {
if (isUmumTabActive) {
// Umum active
if (tabUmum != null) tabUmum.setBackgroundResource(R.drawable.tab_active_bg);
if (textUmum != null) textUmum.setTextColor(ContextCompat.getColor(this, android.R.color.white));
// Riwayat inactive
if (tabRiwayat != null) tabRiwayat.setBackgroundResource(R.drawable.tab_inactive_bg);
if (textRiwayat != null) textRiwayat.setTextColor(Color.parseColor("#DE0701"));
} else {
// Riwayat active
if (tabRiwayat != null) tabRiwayat.setBackgroundResource(R.drawable.tab_active_bg);
if (textRiwayat != null) textRiwayat.setTextColor(ContextCompat.getColor(this, android.R.color.white));
// Umum inactive
if (tabUmum != null) tabUmum.setBackgroundResource(R.drawable.tab_inactive_bg);
if (textUmum != null) textUmum.setTextColor(Color.parseColor("#DE0701"));
}
}
// ✅ Simplified API call with new endpoint
private void loadTicketData() {
String authToken = LoginActivity.getToken(this);
if (authToken == null || authToken.isEmpty()) {
LoginActivity.logout(this);
return;
}
executor.execute(() -> {
HttpURLConnection connection = null;
try {
// ✅ Updated API endpoint
URL url = new URL("https://be-edc.msvc.app/tickets/list");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("accept", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + authToken);
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
parseTicketData(response.toString());
} else if (responseCode == 401) {
mainHandler.post(() -> {
android.widget.Toast.makeText(this, "Session expired. Please login again.",
android.widget.Toast.LENGTH_LONG).show();
LoginActivity.logout(this);
});
} else {
mainHandler.post(() -> {
android.widget.Toast.makeText(this, "Failed to load data. Error: " + responseCode,
android.widget.Toast.LENGTH_LONG).show();
});
}
} catch (Exception e) {
mainHandler.post(() -> {
android.widget.Toast.makeText(this, "Network error: " + e.getMessage(),
android.widget.Toast.LENGTH_LONG).show();
});
} finally {
if (connection != null) {
connection.disconnect();
}
}
});
}
// ✅ Enhanced JSON parsing with sorting
private void parseTicketData(String jsonResponse) {
try {
JSONObject jsonObject = new JSONObject(jsonResponse);
// Check for different possible response structures
JSONArray dataArray = null;
if (jsonObject.has("results") && jsonObject.getJSONObject("results").has("data")) {
// Structure: { "results": { "data": [...] } }
dataArray = jsonObject.getJSONObject("results").getJSONArray("data");
} else if (jsonObject.has("data")) {
// Structure: { "data": [...] }
dataArray = jsonObject.getJSONArray("data");
} else if (jsonObject.has("tickets")) {
// Structure: { "tickets": [...] }
dataArray = jsonObject.getJSONArray("tickets");
} else {
// Assume the response is directly an array
dataArray = new JSONArray(jsonResponse);
}
List<TicketData> newTicketList = new ArrayList<>();
for (int i = 0; i < dataArray.length(); i++) {
JSONObject ticket = dataArray.getJSONObject(i);
String createdAt = ticket.optString("createdAt", ticket.optString("created_at", ""));
String ticketCode = ticket.optString("ticket_code", ticket.optString("ticketCode", ""));
String status = ticket.optString("status", "");
String issueName = "Tidak ada keterangan";
if (ticket.has("issue") && !ticket.isNull("issue")) {
JSONObject issue = ticket.getJSONObject("issue");
issueName = issue.optString("name", issueName);
} else if (ticket.has("issue_name")) {
issueName = ticket.optString("issue_name", issueName);
} else if (ticket.has("title")) {
issueName = ticket.optString("title", issueName);
}
if (!createdAt.isEmpty() && !ticketCode.isEmpty()) {
newTicketList.add(new TicketData(createdAt, ticketCode, issueName, status));
}
}
// ✅ Sort by date - newest first
sortTicketsByDate(newTicketList);
mainHandler.post(() -> {
ticketList.clear();
ticketList.addAll(newTicketList);
if (!isUmumTabActive) {
populateRiwayatContent();
}
android.widget.Toast.makeText(this, "Data riwayat berhasil dimuat (" +
ticketList.size() + " item)", android.widget.Toast.LENGTH_SHORT).show();
});
} catch (Exception e) {
mainHandler.post(() -> {
android.widget.Toast.makeText(this, "Error parsing data: " + e.getMessage(),
android.widget.Toast.LENGTH_LONG).show();
});
}
}
// ✅ New method to sort tickets by date (newest first)
private void sortTicketsByDate(List<TicketData> tickets) {
Collections.sort(tickets, new Comparator<TicketData>() {
@Override
public int compare(TicketData ticket1, TicketData ticket2) {
// Sort by parsedDate in descending order (newest first)
if (ticket1.parsedDate == null && ticket2.parsedDate == null) {
return 0;
} else if (ticket1.parsedDate == null) {
return 1; // null dates go to the end
} else if (ticket2.parsedDate == null) {
return -1; // null dates go to the end
} else {
// Compare dates in descending order (newest first)
return ticket2.parsedDate.compareTo(ticket1.parsedDate);
}
}
});
}
private void populateRiwayatContent() {
if (riwayatContainer == null) return;
riwayatContainer.removeAllViews();
if (ticketList.isEmpty()) {
showEmptyMessage();
return;
}
// ✅ Data is already sorted, just populate the UI
for (int i = 0; i < ticketList.size(); i++) {
TicketData ticket = ticketList.get(i);
LinearLayout historyItem = createHistoryItem(ticket, i);
if (historyItem != null) {
riwayatContainer.addView(historyItem);
// Add separator except for last item
if (i < ticketList.size() - 1) {
View separator = new View(this);
separator.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 1));
separator.setBackgroundColor(Color.parseColor("#e0e0e0"));
riwayatContainer.addView(separator);
}
}
}
}
// ✅ Enhanced createHistoryItem with position indicator
private LinearLayout createHistoryItem(TicketData ticket, int position) {
LinearLayout mainLayout = new LinearLayout(this);
mainLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setPadding(dpToPx(16), dpToPx(16), dpToPx(16), dpToPx(16));
mainLayout.setBackgroundColor(Color.WHITE);
// Header (date and status)
LinearLayout headerLayout = new LinearLayout(this);
headerLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
headerLayout.setOrientation(LinearLayout.HORIZONTAL);
// Date with "Terbaru" indicator for first item
TextView dateText = new TextView(this);
LinearLayout.LayoutParams dateParams = new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
dateText.setLayoutParams(dateParams);
String dateDisplay = formatDate(ticket.createdAt);
if (position == 0) {
dateDisplay += " (Terbaru)";
dateText.setTextColor(Color.parseColor("#DE0701"));
}
dateText.setText(dateDisplay);
dateText.setTextSize(16);
dateText.setTypeface(null, android.graphics.Typeface.BOLD);
// Status
TextView statusText = new TextView(this);
statusText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
statusText.setText(formatStatus(ticket.status));
statusText.setTextSize(14);
statusText.setTextColor(getStatusColor(ticket.status));
headerLayout.addView(dateText);
headerLayout.addView(statusText);
// Ticket code
TextView ticketCodeText = new TextView(this);
LinearLayout.LayoutParams ticketParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
ticketParams.setMargins(0, dpToPx(4), 0, 0);
ticketCodeText.setLayoutParams(ticketParams);
ticketCodeText.setText("Nomor tiket: " + ticket.ticketCode);
ticketCodeText.setTextSize(14);
ticketCodeText.setTextColor(ContextCompat.getColor(this, android.R.color.darker_gray));
// Issue name
TextView issueText = new TextView(this);
LinearLayout.LayoutParams issueParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
issueParams.setMargins(0, dpToPx(8), 0, 0);
issueText.setLayoutParams(issueParams);
issueText.setText(ticket.issueName);
issueText.setTextSize(16);
mainLayout.addView(headerLayout);
mainLayout.addView(ticketCodeText);
mainLayout.addView(issueText);
return mainLayout;
}
private void showLoadingMessage() {
if (riwayatContainer == null) return;
riwayatContainer.removeAllViews();
TextView loadingText = new TextView(this);
loadingText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
loadingText.setText("Memuat data...");
loadingText.setTextSize(16);
loadingText.setGravity(android.view.Gravity.CENTER);
loadingText.setPadding(dpToPx(16), dpToPx(32), dpToPx(16), dpToPx(32));
loadingText.setTextColor(ContextCompat.getColor(this, android.R.color.darker_gray));
riwayatContainer.addView(loadingText);
}
private void showEmptyMessage() {
TextView emptyText = new TextView(this);
emptyText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
emptyText.setText("Belum ada data riwayat");
emptyText.setTextSize(16);
emptyText.setGravity(android.view.Gravity.CENTER);
emptyText.setPadding(dpToPx(16), dpToPx(32), dpToPx(16), dpToPx(32));
emptyText.setTextColor(ContextCompat.getColor(this, android.R.color.darker_gray));
riwayatContainer.addView(emptyText);
}
private void openWhatsAppCS() {
try {
JSONObject userData = LoginActivity.getUserDataAsJson(this);
String userName = "User";
String userEmail = "";
if (userData != null) {
userName = userData.optString("name", "User");
userEmail = userData.optString("email", "");
}
String phoneNumber = "+6281234567890"; // Update with actual CS number
String message = "Halo, saya " + userName;
if (!userEmail.isEmpty()) {
message += " (" + userEmail + ")";
}
message += " butuh bantuan terkait Payvora PRO";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(android.net.Uri.parse("https://wa.me/" + phoneNumber + "?text=" +
android.net.Uri.encode(message)));
startActivity(intent);
} catch (Exception e) {
android.widget.Toast.makeText(this, "Error opening WhatsApp: " + e.getMessage(),
android.widget.Toast.LENGTH_SHORT).show();
}
}
// ✅ Utility methods
private String formatDate(String isoDate) {
try {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault());
Date date = inputFormat.parse(isoDate);
return outputFormat.format(date);
} catch (Exception e) {
return isoDate.length() > 10 ? isoDate.substring(0, 10) : isoDate;
}
}
private String formatStatus(String status) {
switch (status.toLowerCase()) {
case "new": return "Pengajuan";
case "on_progres": return "Proses";
case "done": return "Selesai";
case "cancel": return "Cancel";
default: return status;
}
}
private int getStatusColor(String status) {
switch (status.toLowerCase()) {
case "new": return ContextCompat.getColor(this, android.R.color.holo_blue_light);
case "on_progres": return ContextCompat.getColor(this, android.R.color.holo_orange_light);
case "done": return ContextCompat.getColor(this, android.R.color.holo_green_dark);
case "cancel": return ContextCompat.getColor(this, android.R.color.holo_red_dark);
default: return ContextCompat.getColor(this, android.R.color.black);
}
}
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
@Override
protected void onResume() {
super.onResume();
if (!LoginActivity.isLoggedIn(this)) {
finish();
}
// ✅ Refresh data when returning from form
loadTicketData();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (executor != null && !executor.isShutdown()) {
executor.shutdown();
}
}
}

View File

@@ -0,0 +1,950 @@
package com.example.bdkipoc.bantuan;
import android.app.DatePickerDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import com.example.bdkipoc.R;
import com.example.bdkipoc.LoginActivity;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BantuanFormActivity extends AppCompatActivity {
private static final String TAG = "BantuanFormActivity";
// UI Components
private EditText etTicketCode;
private Spinner spinnerSource, spinnerIssue, spinnerMerchant, spinnerAssign;
private TextView tvStatus, tvResolvedDate;
private LinearLayout llResolvedDate;
private Button btnKirim;
private LinearLayout backNavigation;
private LinearLayout successScreen;
private LinearLayout mainContent;
// Data
private String selectedResolvedDate = "";
private ExecutorService executor = Executors.newSingleThreadExecutor();
private Handler mainHandler = new Handler(Looper.getMainLooper());
// Dynamic data lists
private List<ParameterDetail> sourceList = new ArrayList<>();
private List<ParameterDetail> issueList = new ArrayList<>();
private List<ParameterDetail> assignList = new ArrayList<>();
private List<ParameterDetail> merchantList = new ArrayList<>();
// Inner class for parameter details
public static class ParameterDetail {
public int id;
public String name;
public ParameterDetail(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return name;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check authentication
if (!LoginActivity.isLoggedIn(this)) {
LoginActivity.logout(this);
return;
}
setContentView(R.layout.activity_bantuan_form);
initViews();
// Load dynamic data first, then setup spinners
loadHeaderParams();
loadUsers();
loadMerchants();
setupListeners();
// Initialize button state
updateButtonState();
}
private void initViews() {
// Main content containers
mainContent = findViewById(R.id.main_content);
successScreen = findViewById(R.id.success_screen);
// Form fields
etTicketCode = findViewById(R.id.et_ticket_code);
// Spinners
spinnerSource = findViewById(R.id.spinner_source);
spinnerIssue = findViewById(R.id.spinner_issue);
spinnerMerchant = findViewById(R.id.spinner_merchant);
spinnerAssign = findViewById(R.id.spinner_assign);
// Resolved Date components
llResolvedDate = findViewById(R.id.ll_resolved_date);
tvResolvedDate = findViewById(R.id.tv_resolved_date);
// Status (read-only)
tvStatus = findViewById(R.id.tv_status);
// Buttons
btnKirim = findViewById(R.id.btn_kirim);
backNavigation = findViewById(R.id.back_navigation);
}
private void loadHeaderParams() {
String authToken = LoginActivity.getToken(this);
if (authToken == null || authToken.isEmpty()) {
LoginActivity.logout(this);
return;
}
executor.execute(() -> {
HttpURLConnection connection = null;
try {
URL url = new URL("https://be-edc.msvc.app/header-params/list");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer " + authToken);
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// Parse response
JSONObject jsonResponse = new JSONObject(response.toString());
JSONArray dataArray = jsonResponse.getJSONArray("data");
// Clear existing lists
sourceList.clear();
issueList.clear();
// Process each header parameter
for (int i = 0; i < dataArray.length(); i++) {
JSONObject headerParam = dataArray.getJSONObject(i);
String code = headerParam.getString("code");
String name = headerParam.getString("name");
// Check if this is ticket_sources (code "01")
if ("01".equals(code) && "ticket_sources".equals(name)) {
JSONArray details = headerParam.getJSONArray("details");
for (int j = 0; j < details.length(); j++) {
JSONObject detail = details.getJSONObject(j);
int id = detail.getInt("id");
String detailName = detail.getString("name");
String status = detail.getString("status");
// Only add active items
if ("1".equals(status)) {
sourceList.add(new ParameterDetail(id, detailName));
}
}
}
// Check if this is issue_categories (code "02")
else if ("02".equals(code) && "issue_categories".equals(name)) {
JSONArray details = headerParam.getJSONArray("details");
for (int j = 0; j < details.length(); j++) {
JSONObject detail = details.getJSONObject(j);
int id = detail.getInt("id");
String detailName = detail.getString("name");
String status = detail.getString("status");
// Only add active items
if ("1".equals(status)) {
issueList.add(new ParameterDetail(id, detailName));
}
}
}
}
// Update UI on main thread
mainHandler.post(() -> {
setupSpinners();
updateButtonState();
});
} else if (responseCode == 401) {
mainHandler.post(() -> {
Toast.makeText(this, "Session expired. Please login again.", Toast.LENGTH_LONG).show();
LoginActivity.logout(this);
});
} else {
mainHandler.post(() -> {
Toast.makeText(this, "Failed to load data. Error: " + responseCode, Toast.LENGTH_LONG).show();
// Setup with empty data
setupSpinners();
});
}
} catch (Exception e) {
mainHandler.post(() -> {
Toast.makeText(this, "Network error: " + e.getMessage(), Toast.LENGTH_LONG).show();
// Setup with empty data
setupSpinners();
});
} finally {
if (connection != null) {
connection.disconnect();
}
}
});
}
private void loadMerchants() {
String authToken = LoginActivity.getToken(this);
if (authToken == null || authToken.isEmpty()) {
LoginActivity.logout(this);
return;
}
executor.execute(() -> {
HttpURLConnection connection = null;
try {
URL url = new URL("https://be-edc.msvc.app/merchants/list?location_id=0");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer " + authToken);
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// Parse response
JSONObject jsonResponse = new JSONObject(response.toString());
JSONArray dataArray = jsonResponse.getJSONArray("data");
// Clear existing list
merchantList.clear();
// Process each merchant
for (int i = 0; i < dataArray.length(); i++) {
JSONObject merchant = dataArray.getJSONObject(i);
String status = merchant.getString("status");
// Only add active merchants
if ("1".equals(status)) {
int id = merchant.getInt("id");
String name = merchant.getString("name");
String merchantCode = merchant.optString("merchant_code", "");
// Only add merchants that have merchant_code
if (!merchantCode.isEmpty()) {
merchantList.add(new ParameterDetail(id, name));
}
}
}
// Update UI on main thread
mainHandler.post(() -> {
setupSpinners();
updateButtonState();
});
} else if (responseCode == 401) {
mainHandler.post(() -> {
Toast.makeText(this, "Session expired. Please login again.", Toast.LENGTH_LONG).show();
LoginActivity.logout(this);
});
} else {
mainHandler.post(() -> {
Toast.makeText(this, "Failed to load merchants. Error: " + responseCode, Toast.LENGTH_LONG).show();
// Setup with empty data
setupSpinners();
});
}
} catch (Exception e) {
mainHandler.post(() -> {
Toast.makeText(this, "Network error loading merchants: " + e.getMessage(), Toast.LENGTH_LONG).show();
// Setup with empty data
setupSpinners();
});
} finally {
if (connection != null) {
connection.disconnect();
}
}
});
}
private void showSuccessScreen() {
Log.d(TAG, "Showing success screen");
// Hide main content and show success screen
if (mainContent != null) {
mainContent.setVisibility(View.GONE);
}
if (successScreen != null) {
successScreen.setVisibility(View.VISIBLE);
}
// Navigate back to BantuanActivity after 2 seconds
new Handler(Looper.getMainLooper()).postDelayed(() -> {
navigateToBantuanActivity();
}, 2000);
}
private void navigateToBantuanActivity() {
Log.d(TAG, "Navigating back to BantuanActivity");
try {
// Create intent to BantuanActivity
Intent intent = new Intent(this, Class.forName("com.example.bdkipoc.bantuan.BantuanActivity"));
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
} catch (ClassNotFoundException e) {
Log.e(TAG, "BantuanActivity class not found", e);
// Fallback: just finish current activity
finish();
}
}
private void loadUsers() {
String authToken = LoginActivity.getToken(this);
if (authToken == null || authToken.isEmpty()) {
LoginActivity.logout(this);
return;
}
executor.execute(() -> {
HttpURLConnection connection = null;
try {
URL url = new URL("https://be-edc.msvc.app/users");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer " + authToken);
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// Parse response
JSONArray usersArray = new JSONArray(response.toString());
// Clear existing list
assignList.clear();
// Process each user
for (int i = 0; i < usersArray.length(); i++) {
JSONObject user = usersArray.getJSONObject(i);
String role = user.getString("role");
boolean isActive = user.getBoolean("is_active");
// Only add superadmin users who are active
if ("superadmin".equals(role) && isActive) {
int id = user.getInt("id");
String name = user.getString("name");
assignList.add(new ParameterDetail(id, name));
}
}
// Update UI on main thread
mainHandler.post(() -> {
setupSpinners();
updateButtonState();
});
} else if (responseCode == 401) {
mainHandler.post(() -> {
Toast.makeText(this, "Session expired. Please login again.", Toast.LENGTH_LONG).show();
LoginActivity.logout(this);
});
} else {
mainHandler.post(() -> {
Toast.makeText(this, "Failed to load users. Error: " + responseCode, Toast.LENGTH_LONG).show();
// Setup with empty data
setupSpinners();
});
}
} catch (Exception e) {
mainHandler.post(() -> {
Toast.makeText(this, "Network error loading users: " + e.getMessage(), Toast.LENGTH_LONG).show();
// Setup with empty data
setupSpinners();
});
} finally {
if (connection != null) {
connection.disconnect();
}
}
});
}
private void setupSpinners() {
// Setup Source Spinner (Dynamic)
List<String> sourceOptions = new ArrayList<>();
sourceOptions.add("Pilih Source");
for (ParameterDetail source : sourceList) {
sourceOptions.add(source.name);
}
ArrayAdapter<String> sourceAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, sourceOptions);
sourceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerSource.setAdapter(sourceAdapter);
// Setup Issue Spinner (Dynamic)
List<String> issueOptions = new ArrayList<>();
issueOptions.add("Pilih Issue");
for (ParameterDetail issue : issueList) {
issueOptions.add(issue.name);
}
ArrayAdapter<String> issueAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, issueOptions);
issueAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerIssue.setAdapter(issueAdapter);
// Setup Merchant Spinner (Dynamic)
List<String> merchantOptions = new ArrayList<>();
merchantOptions.add("Pilih Merchant");
for (ParameterDetail merchant : merchantList) {
merchantOptions.add(merchant.name);
}
ArrayAdapter<String> merchantAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, merchantOptions);
merchantAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerMerchant.setAdapter(merchantAdapter);
// Setup Assign Spinner (Dynamic)
List<String> assignOptions = new ArrayList<>();
assignOptions.add("Pilih Assign");
for (ParameterDetail assign : assignList) {
assignOptions.add(assign.name);
}
ArrayAdapter<String> assignAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, assignOptions);
assignAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerAssign.setAdapter(assignAdapter);
// Setup Resolved Date components (no spinner setup needed)
// Set status to fixed value
if (tvStatus != null) {
tvStatus.setText("Pengajuan");
}
}
private void setupListeners() {
// Back navigation
if (backNavigation != null) {
backNavigation.setOnClickListener(v -> onBackPressed());
}
// Source spinner listener
spinnerSource.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
updateButtonState();
}
@Override
public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
// Issue spinner listener
spinnerIssue.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
updateButtonState();
}
@Override
public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
// Merchant spinner listener
spinnerMerchant.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
updateButtonState();
}
@Override
public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
// Assign spinner listener
spinnerAssign.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
updateButtonState();
}
@Override
public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
// Resolved Date click listener
if (llResolvedDate != null) {
llResolvedDate.setOnClickListener(v -> showDatePicker());
}
// Text watcher for EditText field
setupTextWatcher();
// Submit button listener
if (btnKirim != null) {
btnKirim.setOnClickListener(v -> {
if (btnKirim.isEnabled()) {
submitForm();
}
});
}
}
private void setupTextWatcher() {
android.text.TextWatcher textWatcher = new android.text.TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(android.text.Editable s) {
updateButtonState();
}
};
if (etTicketCode != null) {
etTicketCode.addTextChangedListener(textWatcher);
}
}
private void updateButtonState() {
if (btnKirim == null) return;
boolean isFormValid = checkFormValidity();
if (isFormValid) {
// Active state - Red background
btnKirim.setBackgroundColor(0xFFDE0701); // Red color matching theme
btnKirim.setTextColor(getResources().getColor(android.R.color.white));
btnKirim.setEnabled(true);
btnKirim.setAlpha(1.0f);
} else {
// Inactive state - Gray background
btnKirim.setBackgroundColor(getResources().getColor(android.R.color.darker_gray));
btnKirim.setTextColor(getResources().getColor(android.R.color.white));
btnKirim.setEnabled(false);
btnKirim.setAlpha(0.6f);
}
}
private boolean checkFormValidity() {
// Check if all required fields have values
boolean hasTicketCode = etTicketCode != null && !etTicketCode.getText().toString().trim().isEmpty();
boolean hasSource = spinnerSource != null && spinnerSource.getSelectedItemPosition() > 0;
boolean hasIssue = spinnerIssue != null && spinnerIssue.getSelectedItemPosition() > 0;
boolean hasMerchant = spinnerMerchant != null && spinnerMerchant.getSelectedItemPosition() > 0;
boolean hasAssign = spinnerAssign != null && spinnerAssign.getSelectedItemPosition() > 0;
boolean hasResolvedDate = !selectedResolvedDate.isEmpty();
return hasTicketCode && hasSource && hasIssue && hasMerchant && hasAssign && hasResolvedDate;
}
private void showDatePicker() {
Calendar calendar = Calendar.getInstance();
DatePickerDialog datePickerDialog = new DatePickerDialog(
this,
(view, year, month, dayOfMonth) -> {
Calendar selectedCalendar = Calendar.getInstance();
selectedCalendar.set(year, month, dayOfMonth);
// Format for API (ISO 8601)
SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault());
selectedResolvedDate = apiFormat.format(selectedCalendar.getTime());
// Format for display
SimpleDateFormat displayFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault());
String displayDate = displayFormat.format(selectedCalendar.getTime());
// Update TextView to show selected date
if (tvResolvedDate != null) {
tvResolvedDate.setText(displayDate);
tvResolvedDate.setTextColor(0xFF000000); // Black color for selected date
}
Log.d(TAG, "Date selected: " + displayDate + " (API format: " + selectedResolvedDate + ")");
// Update button state after date selection
updateButtonState();
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
);
// Set minimum date to today to prevent past dates
datePickerDialog.getDatePicker().setMinDate(System.currentTimeMillis());
datePickerDialog.show();
}
private int getSourceId(int position) {
if (position > 0 && position <= sourceList.size()) {
return sourceList.get(position - 1).id;
}
return 0;
}
private int getIssueId(int position) {
if (position > 0 && position <= issueList.size()) {
return issueList.get(position - 1).id;
}
return 0;
}
private int getMerchantId(int position) {
if (position > 0 && position <= merchantList.size()) {
return merchantList.get(position - 1).id;
}
return 0;
}
private int getAssignId(int position) {
if (position > 0 && position <= assignList.size()) {
return assignList.get(position - 1).id;
}
return 0;
}
private void submitForm() {
if (!validateForm()) {
Log.w(TAG, "Form validation failed");
return;
}
Log.d(TAG, "Form validation passed, preparing data...");
// Disable button and show loading
btnKirim.setEnabled(false);
btnKirim.setText("Mengirim...");
// Prepare form data
String ticketCode = etTicketCode.getText().toString().trim();
int sourceId = getSourceId(spinnerSource.getSelectedItemPosition());
int issueId = getIssueId(spinnerIssue.getSelectedItemPosition());
int merchantId = getMerchantId(spinnerMerchant.getSelectedItemPosition());
int assignId = getAssignId(spinnerAssign.getSelectedItemPosition());
// Validate IDs
if (sourceId == 0) {
Log.e(TAG, "Invalid source ID: " + sourceId + ", position: " + spinnerSource.getSelectedItemPosition());
Toast.makeText(this, "Error: Source tidak valid", Toast.LENGTH_SHORT).show();
btnKirim.setEnabled(true);
btnKirim.setText("Kirim Sekarang");
return;
}
if (issueId == 0) {
Log.e(TAG, "Invalid issue ID: " + issueId + ", position: " + spinnerIssue.getSelectedItemPosition());
Toast.makeText(this, "Error: Issue tidak valid", Toast.LENGTH_SHORT).show();
btnKirim.setEnabled(true);
btnKirim.setText("Kirim Sekarang");
return;
}
if (merchantId == 0) {
Log.e(TAG, "Invalid merchant ID: " + merchantId + ", position: " + spinnerMerchant.getSelectedItemPosition());
Toast.makeText(this, "Error: Merchant tidak valid", Toast.LENGTH_SHORT).show();
btnKirim.setEnabled(true);
btnKirim.setText("Kirim Sekarang");
return;
}
if (assignId == 0) {
Log.e(TAG, "Invalid assign ID: " + assignId + ", position: " + spinnerAssign.getSelectedItemPosition());
Toast.makeText(this, "Error: Assign To tidak valid", Toast.LENGTH_SHORT).show();
btnKirim.setEnabled(true);
btnKirim.setText("Kirim Sekarang");
return;
}
Log.d(TAG, "All IDs validated successfully");
submitToAPI(ticketCode, sourceId, issueId, merchantId, assignId, selectedResolvedDate);
}
private boolean validateForm() {
boolean isValid = true;
// Validate Ticket Code
if (etTicketCode.getText().toString().trim().isEmpty()) {
etTicketCode.setError("Ticket Code wajib diisi");
isValid = false;
}
// Validate Source
if (spinnerSource.getSelectedItemPosition() == 0) {
Toast.makeText(this, "Pilih Source", Toast.LENGTH_SHORT).show();
isValid = false;
}
// Validate Issue
if (spinnerIssue.getSelectedItemPosition() == 0) {
Toast.makeText(this, "Pilih Issue", Toast.LENGTH_SHORT).show();
isValid = false;
}
// Validate Merchant
if (spinnerMerchant.getSelectedItemPosition() == 0) {
Toast.makeText(this, "Pilih Merchant", Toast.LENGTH_SHORT).show();
isValid = false;
}
// Validate Assign
if (spinnerAssign.getSelectedItemPosition() == 0) {
Toast.makeText(this, "Pilih Assign", Toast.LENGTH_SHORT).show();
isValid = false;
}
// Validate Resolved Date
if (selectedResolvedDate.isEmpty()) {
Toast.makeText(this, "Pilih tanggal resolved", Toast.LENGTH_SHORT).show();
isValid = false;
}
return isValid;
}
private void submitToAPI(String ticketCode, int sourceId, int issueId, int merchantId,
int assignId, String resolvedAt) {
Log.d(TAG, "Starting API submission...");
Log.d(TAG, "Ticket Code: " + ticketCode);
Log.d(TAG, "Source ID: " + sourceId);
Log.d(TAG, "Issue ID: " + issueId);
Log.d(TAG, "Merchant ID: " + merchantId);
Log.d(TAG, "Assign ID: " + assignId);
Log.d(TAG, "Resolved At: " + resolvedAt);
String authToken = LoginActivity.getToken(this);
if (authToken == null || authToken.isEmpty()) {
Log.e(TAG, "Auth token is null or empty");
mainHandler.post(() -> {
btnKirim.setEnabled(true);
btnKirim.setText("Kirim Sekarang");
LoginActivity.logout(this);
});
return;
}
Log.d(TAG, "Auth token obtained: " + authToken.substring(0, Math.min(authToken.length(), 10)) + "...");
executor.execute(() -> {
HttpURLConnection connection = null;
try {
URL url = new URL("https://be-edc.msvc.app/tickets");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + authToken);
connection.setDoOutput(true);
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
// Create JSON payload
JSONObject payload = new JSONObject();
payload.put("ticket_code", ticketCode);
payload.put("source_id", sourceId);
payload.put("issue_id", issueId);
payload.put("merchant_id", merchantId);
payload.put("status", "new");
payload.put("is_sla_violated", true);
payload.put("assigned_to", assignId);
payload.put("resolved_at", resolvedAt);
String jsonPayload = payload.toString();
Log.d(TAG, "JSON Payload: " + jsonPayload);
// Send request
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonPayload.getBytes("utf-8");
os.write(input, 0, input.length);
Log.d(TAG, "Request sent successfully");
}
int responseCode = connection.getResponseCode();
Log.d(TAG, "Response Code: " + responseCode);
// Read response
BufferedReader reader;
if (responseCode >= 200 && responseCode < 300) {
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
} else {
reader = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
}
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
String responseBody = response.toString();
Log.d(TAG, "Response Body: " + responseBody);
// Handle response
mainHandler.post(() -> {
btnKirim.setEnabled(true);
btnKirim.setText("Kirim Sekarang");
if (responseCode >= 200 && responseCode < 300) {
// Success
Log.d(TAG, "Request successful");
try {
JSONObject responseJson = new JSONObject(responseBody);
String message = responseJson.optString("message", "Tiket berhasil dibuat");
// Show success screen instead of toast
showSuccessScreen();
} catch (Exception e) {
Log.e(TAG, "Error parsing success response", e);
// Show success screen even if parsing fails
showSuccessScreen();
}
} else if (responseCode == 401) {
Log.e(TAG, "Unauthorized - token expired");
Toast.makeText(this, "Session expired. Please login again.",
Toast.LENGTH_LONG).show();
LoginActivity.logout(this);
} else {
// Error
Log.e(TAG, "Request failed with code: " + responseCode);
Log.e(TAG, "Error response: " + responseBody);
try {
JSONObject errorJson = new JSONObject(responseBody);
String errorMessage = errorJson.optString("message", "Gagal mengirim tiket");
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e(TAG, "Error parsing error response", e);
Toast.makeText(this, "Gagal mengirim tiket. Error: " + responseCode,
Toast.LENGTH_LONG).show();
}
}
});
} catch (Exception e) {
Log.e(TAG, "Network error during submission", e);
mainHandler.post(() -> {
btnKirim.setEnabled(true);
btnKirim.setText("Kirim Sekarang");
Toast.makeText(this, "Network error: " + e.getMessage(),
Toast.LENGTH_LONG).show();
});
} finally {
if (connection != null) {
connection.disconnect();
}
}
});
}
private void clearForm() {
if (etTicketCode != null) etTicketCode.setText("");
if (spinnerSource != null) spinnerSource.setSelection(0);
if (spinnerIssue != null) spinnerIssue.setSelection(0);
if (spinnerMerchant != null) spinnerMerchant.setSelection(0);
if (spinnerAssign != null) spinnerAssign.setSelection(0);
// Reset resolved date
if (tvResolvedDate != null) {
tvResolvedDate.setText("Pilih Tanggal Resolved");
tvResolvedDate.setTextColor(0xFFAAAAAA); // Light gray color
}
selectedResolvedDate = "";
// Update button state after clearing form
updateButtonState();
}
@Override
protected void onResume() {
super.onResume();
if (!LoginActivity.isLoggedIn(this)) {
finish();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (executor != null && !executor.isShutdown()) {
executor.shutdown();
}
}
}

View File

@@ -102,7 +102,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
initViews();
// Setup toolbar
setupToolbar();
setupAppbar();
// Setup RecyclerView
setupRecyclerView();
@@ -144,19 +144,18 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
StyleHelper.applyPaginationButtonStyle(btnLastPage, this, false);
}
private void setupToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Hide default title since we're using custom layout
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
private void setupAppbar() {
// Setup back navigation click listener
LinearLayout backNavigation = findViewById(R.id.back_navigation);
if (backNavigation != null) {
backNavigation.setOnClickListener(v -> finish());
}
// Setup custom back button
ImageView backButton = findViewById(R.id.backButton);
backButton.setOnClickListener(v -> finish());
// Alternative: You can also set click listener on the back arrow directly
ImageView backArrow = findViewById(R.id.backArrow);
if (backArrow != null) {
backArrow.setOnClickListener(v -> finish());
}
}
private void setupRecyclerView() {
@@ -615,7 +614,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
int apiPage = pageToLoad - 1; // API uses 0-based indexing
String urlString = "https://be-edc.msvc.app/transactions?page=" + apiPage +
"&limit=" + itemsPerPage + "&sortOrder=DESC&from_date=" + fromDate + "&to_date=" + toDate + "&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at";
"&limit=" + itemsPerPage + "&sortOrder=DESC&from_date=" + fromDate + "&to_date=" + toDate + "&location_id=0&merchant_id=0&tid=&mid=&sortColumn=created_at";
Log.d("ReprintActivity", "🔍 Fetching transactions page " + pageToLoad +
" (API page " + apiPage + ") with limit " + itemsPerPage + " - SORT: DESC by created_at" +
@@ -1002,93 +1001,196 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
// ✅ REST OF THE CLASS: Existing methods remain the same
@Override
public void onPrintClick(Transaction transaction) {
// Open ReceiptActivity with transaction data
Log.d("ReprintActivity", "=== OPENING RECEIPT FOR TRANSACTION ===");
Log.d("ReprintActivity", "📋 Transaction details:");
Log.d("ReprintActivity", " Reference ID: " + transaction.referenceId);
Log.d("ReprintActivity", " Channel Code: " + transaction.channelCode);
Log.d("ReprintActivity", " Channel Category: " + transaction.channelCategory);
Log.d("ReprintActivity", " Amount: " + transaction.amount);
Log.d("ReprintActivity", " Status: " + transaction.status);
// Open ReceiptActivity with enhanced transaction data
Intent intent = new Intent(this, ReceiptActivity.class);
// Add calling activity information for proper back navigation
intent.putExtra("calling_activity", "ReprintActivity");
// Extract and send raw amount properly
// ✅ ENHANCED: Extract and send raw amount properly
String rawAmount = extractRawAmount(transaction.amount);
Log.d("ReprintActivity", "💰 Amount processing: '" + transaction.amount + "' -> '" + rawAmount + "'");
Log.d("ReprintActivity", "Opening receipt for transaction: " + transaction.referenceId +
", channel: " + transaction.channelCode + ", original amount: '" + transaction.amount + "'");
// ✅ ENHANCED: Get payment method using new improved logic
String displayPaymentMethod = getPaymentMethodName(transaction.channelCode, transaction.channelCategory);
Log.d("ReprintActivity", "💳 Payment method: " + displayPaymentMethod);
// Send transaction data to ReceiptActivity
// ✅ ENHANCED: Determine card type for receipt using improved logic
String cardTypeForReceipt = determineCardTypeForReceipt(transaction.channelCode, transaction.channelCategory, transaction.referenceId);
Log.d("ReprintActivity", "🏷️ Card type for receipt: " + cardTypeForReceipt);
// ✅ ENHANCED: Get acquirer using improved logic
String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode);
Log.d("ReprintActivity", "🎯 Determined acquirer: " + acquirer);
// Send comprehensive transaction data to ReceiptActivity
intent.putExtra("transaction_id", transaction.referenceId);
intent.putExtra("reference_id", transaction.referenceId); // Nomor Transaksi
intent.putExtra("transaction_amount", rawAmount); // Total transaksi
intent.putExtra("gross_amount", rawAmount); // Consistent with transaction_amount
intent.putExtra("created_at", transaction.createdAt); // Tanggal transaksi (will be formatted)
intent.putExtra("transaction_date", formatDate(transaction.createdAt)); // Backup formatted date
intent.putExtra("payment_method", getPaymentMethodName(transaction.channelCode, transaction.channelCategory));
intent.putExtra("channel_code", transaction.channelCode); // Metode Pembayaran
intent.putExtra("channel_category", transaction.channelCategory);
intent.putExtra("card_type", transaction.channelCategory);
intent.putExtra("merchant_name", transaction.merchantName);
// ✅ ENHANCED: Send improved payment method and card type
intent.putExtra("payment_method", displayPaymentMethod); // User-friendly payment method
intent.putExtra("channel_code", transaction.channelCode); // Original channel code for processing
intent.putExtra("channel_category", transaction.channelCategory); // Original category for processing
intent.putExtra("card_type", cardTypeForReceipt); // Enhanced card type for display
// Merchant information
intent.putExtra("merchant_name", transaction.merchantName != null ? transaction.merchantName : "Marcel Panjaitan");
intent.putExtra("merchant_location", "Jakarta, Indonesia");
// ✅ ENHANCED: Send acquirer for proper card type detection in ReceiptActivity
intent.putExtra("acquirer", acquirer);
// Add MID and TID (default values since not available in Transaction model)
intent.putExtra("mid", "71000026521"); // MID
intent.putExtra("tid", "73001500"); // TID
// ✅ ENHANCED: Use improved acquirer determination
String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode);
intent.putExtra("acquirer", acquirer); // Jenis Kartu
// ✅ NEW: Add transaction status for receipt processing
intent.putExtra("transaction_status", transaction.status);
Log.d("ReprintActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode);
// ✅ NEW: Add transaction type information
intent.putExtra("transaction_type", transaction.type);
Log.d("ReprintActivity", "🚀 LAUNCHING RECEIPT ACTIVITY");
Log.d("ReprintActivity", "📤 Sent data summary:");
Log.d("ReprintActivity", " Payment Method: " + displayPaymentMethod);
Log.d("ReprintActivity", " Card Type: " + cardTypeForReceipt);
Log.d("ReprintActivity", " Acquirer: " + acquirer);
Log.d("ReprintActivity", " Amount: " + rawAmount);
Log.d("ReprintActivity", "========================================");
startActivity(intent);
}
/**
* ✅ ENHANCED: Dynamic acquirer determination instead of defaulting to GoPay
*/
private String determineAcquirerFromChannelCode(String channelCode) {
if (channelCode == null) return "unknown"; // ✅ CHANGED: unknown instead of gopay
private String determineCardTypeForReceipt(String channelCode, String channelCategory, String referenceId) {
Log.d("ReprintActivity", "🔍 Determining card type for receipt:");
Log.d("ReprintActivity", " channelCode: " + channelCode);
Log.d("ReprintActivity", " channelCategory: " + channelCategory);
Log.d("ReprintActivity", " referenceId: " + referenceId);
switch (channelCode.toLowerCase()) {
case "qris":
// ✅ IMPROVED: For QRIS, try to get real acquirer or return generic
return "qris"; // Will be processed by receipt activity to find real acquirer
case "bca":
return "bca";
case "mandiri":
return "mandiri";
case "bni":
return "bni";
case "bri":
return "bri";
case "permata":
return "permata";
case "cimb":
return "cimb";
case "danamon":
return "danamon";
case "bsi":
return "bsi";
case "debit":
return "visa"; // Default for debit cards
case "credit":
return "mastercard"; // Default for credit cards
// Priority 1: Direct channel code mapping
if (channelCode != null && !channelCode.isEmpty()) {
String code = channelCode.toUpperCase().trim();
switch (code) {
case "QRIS":
case "RETAIL_OUTLET":
// For QRIS, return the channel code so ReceiptActivity can detect real acquirer
Log.d("ReprintActivity", "✅ QRIS detected, returning for real acquirer detection");
return "QRIS";
case "DEBIT":
case "DEBIT_CARD":
return "Debit";
case "CREDIT":
case "CREDIT_CARD":
return "Credit";
case "BCA":
case "MANDIRI":
case "BNI":
case "BRI":
case "PERMATA":
case "CIMB":
case "DANAMON":
case "BSI":
return code;
default:
return "unknown"; // ✅ CHANGED: unknown instead of gopay for unknown channels
Log.d("ReprintActivity", "🔍 Unknown channelCode, checking channelCategory");
break;
}
}
// Priority 2: Channel category mapping
if (channelCategory != null && !channelCategory.isEmpty()) {
String category = channelCategory.toUpperCase().trim();
switch (category) {
case "RETAIL_OUTLET":
return "QRIS";
case "DEBIT":
case "DEBIT_CARD":
return "Debit";
case "CREDIT":
case "CREDIT_CARD":
return "Credit";
case "E_MONEY":
case "EMONEY":
return "E-Money";
default:
return capitalizeFirstLetter(channelCategory);
}
}
// Final fallback
Log.w("ReprintActivity", "⚠️ No valid card type found, defaulting to Unknown");
return "Unknown";
}
private String determineAcquirerFromChannelCode(String channelCode) {
if (channelCode == null) return "unknown";
String code = channelCode.toLowerCase().trim();
Log.d("ReprintActivity", "🔍 Determining acquirer from channelCode: " + channelCode);
switch (code) {
case "qris":
case "retail_outlet":
// ✅ For QRIS/RETAIL_OUTLET, return qris to trigger real acquirer detection
Log.d("ReprintActivity", "🔍 QRIS/RETAIL_OUTLET detected, will search for real acquirer");
return "qris";
case "bca":
return "BCA";
case "mandiri":
return "Mandiri";
case "bni":
return "BNI";
case "bri":
return "BRI";
case "permata":
return "Permata";
case "cimb":
return "CIMB Niaga";
case "danamon":
return "Danamon";
case "bsi":
return "BSI";
case "debit":
case "debit_card":
return "Visa"; // Default for debit cards
case "credit":
case "credit_card":
return "Mastercard"; // Default for credit cards
default:
Log.d("ReprintActivity", "🔍 Unknown channelCode: " + channelCode + ", returning as-is");
return capitalizeFirstLetter(channelCode);
}
}
/**
* ✅ NEW METHOD: Try to get real acquirer for QRIS transactions from current transaction data
* This method can be enhanced to query webhook API for real acquirer
*/
private String getRealAcquirerForQris(String referenceId, String channelCode) {
// If not QRIS, return channel code
if (!"QRIS".equalsIgnoreCase(channelCode)) {
return determineAcquirerFromChannelCode(channelCode);
Log.d("ReprintActivity", "🔍 Getting real acquirer for referenceId: " + referenceId + ", channelCode: " + channelCode);
// If not QRIS-related, use standard mapping
if (!"QRIS".equalsIgnoreCase(channelCode) && !"RETAIL_OUTLET".equalsIgnoreCase(channelCode)) {
String acquirer = determineAcquirerFromChannelCode(channelCode);
Log.d("ReprintActivity", "🔍 Non-QRIS transaction, mapped to: " + acquirer);
return acquirer;
}
// For QRIS, we could implement real-time acquirer lookup here
// For QRIS transactions, try to get real acquirer
Log.d("ReprintActivity", "🔍 QRIS transaction detected, attempting real acquirer lookup");
// Try to get from cache first (if implemented)
// For now, return "qris" and let ReceiptActivity handle the detection
Log.d("ReprintActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity");
return "qris";
}
@@ -1138,24 +1240,141 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
}
private String getPaymentMethodName(String channelCode, String channelCategory) {
if (channelCode == null) return "Unknown";
Log.d("ReprintActivity", "🔍 Enhanced payment method mapping:");
Log.d("ReprintActivity", " channelCode: " + channelCode);
Log.d("ReprintActivity", " channelCategory: " + channelCategory);
switch (channelCode.toUpperCase()) {
case "QRIS": return "QRIS";
case "DEBIT": return "Kartu Debit";
case "CREDIT": return "Kartu Kredit";
// Priority 1: Use channelCode for specific mapping
if (channelCode != null && !channelCode.isEmpty()) {
String code = channelCode.toUpperCase().trim();
switch (code) {
case "QRIS":
Log.d("ReprintActivity", "✅ Mapped to: QRIS");
return "QRIS";
case "DEBIT":
case "DEBIT_CARD":
Log.d("ReprintActivity", "✅ Mapped to: Kartu Debit");
return "Kartu Debit";
case "CREDIT":
case "CREDIT_CARD":
Log.d("ReprintActivity", "✅ Mapped to: Kartu Kredit");
return "Kartu Kredit";
case "BCA":
return "BCA";
case "MANDIRI":
return "Mandiri";
case "BNI":
case "BRI": return "Kartu " + channelCode.toUpperCase();
case "CASH": return "Tunai";
case "EDC": return "EDC";
return "BNI";
case "BRI":
return "BRI";
case "PERMATA":
return "Permata";
case "CIMB":
return "CIMB Niaga";
case "DANAMON":
return "Danamon";
case "BSI":
return "BSI";
case "CASH":
return "Tunai";
case "EDC":
return "EDC";
case "RETAIL_OUTLET":
// ✅ ENHANCED: For RETAIL_OUTLET, use intelligent detection
String detectedMethod = detectPaymentMethodFromRetailOutlet(channelCategory);
Log.d("ReprintActivity", "🔍 RETAIL_OUTLET detected as: " + detectedMethod);
return detectedMethod;
default:
Log.d("ReprintActivity", "🔍 Unknown channelCode: " + code + ", checking channelCategory");
break;
}
}
// Priority 2: Use channelCategory as fallback
if (channelCategory != null && !channelCategory.isEmpty()) {
return channelCategory.toUpperCase();
String mappedFromCategory = mapChannelCategoryToPaymentMethod(channelCategory);
Log.d("ReprintActivity", "✅ Mapped from channelCategory: " + mappedFromCategory);
return mappedFromCategory;
}
return channelCode.toUpperCase();
// Final fallback
Log.w("ReprintActivity", "⚠️ No valid payment method found, defaulting to Unknown");
return "Unknown";
}
private String detectPaymentMethodFromRetailOutlet(String channelCategory) {
if (channelCategory == null || channelCategory.isEmpty()) {
return "QRIS"; // Most RETAIL_OUTLET are QRIS
}
String category = channelCategory.toUpperCase().trim();
switch (category) {
case "RETAIL_OUTLET":
return "QRIS"; // RETAIL_OUTLET + RETAIL_OUTLET = QRIS
case "DEBIT":
case "DEBIT_CARD":
return "Kartu Debit";
case "CREDIT":
case "CREDIT_CARD":
return "Kartu Kredit";
case "E_MONEY":
case "EMONEY":
return "E-Money";
default:
return "QRIS"; // Default assumption
}
}
private String mapChannelCategoryToPaymentMethod(String channelCategory) {
if (channelCategory == null || channelCategory.isEmpty()) {
return "Unknown";
}
String category = channelCategory.toUpperCase().trim();
switch (category) {
case "RETAIL_OUTLET":
return "QRIS";
case "DEBIT":
case "DEBIT_CARD":
return "Kartu Debit";
case "CREDIT":
case "CREDIT_CARD":
return "Kartu Kredit";
case "E_MONEY":
case "EMONEY":
return "E-Money";
case "BANK_TRANSFER":
return "Transfer Bank";
case "VIRTUAL_ACCOUNT":
return "Virtual Account";
case "QRIS":
return "QRIS";
default:
return capitalizeFirstLetter(channelCategory);
}
}
private String capitalizeFirstLetter(String text) {
if (text == null || text.isEmpty()) {
return text;
}
String cleaned = text.trim().replace("_", " ");
String[] words = cleaned.split("\\s+");
StringBuilder result = new StringBuilder();
for (String word : words) {
if (word.length() > 0) {
result.append(Character.toUpperCase(word.charAt(0)))
.append(word.substring(1).toLowerCase())
.append(" ");
}
}
return result.toString().trim();
}
private String formatDate(String rawDate) {
@@ -1169,15 +1388,6 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
// Transaction model class
static class Transaction {
int id;

View File

@@ -528,15 +528,20 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
}
private String getPaymentMethodName(String channelCode, String channelCategory) {
// Convert channel code to readable payment method name
if (channelCode == null) return "Unknown";
Log.d("ReprintAdapterActivity", "🔍 Mapping payment method - channelCode: " + channelCode + ", channelCategory: " + channelCategory);
switch (channelCode.toUpperCase()) {
// Priority 1: Use channelCode for specific mapping
if (channelCode != null && !channelCode.isEmpty()) {
String code = channelCode.toUpperCase().trim();
switch (code) {
case "QRIS":
return "QRIS";
case "DEBIT":
case "DEBIT_CARD":
return "Kartu Debit";
case "CREDIT":
case "CREDIT_CARD":
return "Kartu Kredit";
case "BCA":
return "BCA";
@@ -546,17 +551,103 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
return "BNI";
case "BRI":
return "BRI";
case "PERMATA":
return "Permata";
case "CIMB":
return "CIMB Niaga";
case "DANAMON":
return "Danamon";
case "BSI":
return "BSI";
case "CASH":
return "Tunai";
case "EDC":
return "EDC";
case "RETAIL_OUTLET":
// ✅ SPECIAL HANDLING: For RETAIL_OUTLET, determine by context
return determinePaymentMethodFromCategory(channelCategory);
default:
// If channel category is available, use it as fallback
Log.d("ReprintAdapterActivity", "🔍 Unknown channelCode: " + code + ", trying channelCategory");
break;
}
}
// Priority 2: Use channelCategory as fallback
if (channelCategory != null && !channelCategory.isEmpty()) {
return channelCategory.toUpperCase();
return mapChannelCategoryToPaymentMethod(channelCategory);
}
return channelCode.toUpperCase();
// Final fallback
Log.w("ReprintAdapterActivity", "⚠️ No valid payment method found, using default");
return "Unknown";
}
private String determinePaymentMethodFromCategory(String channelCategory) {
if (channelCategory == null || channelCategory.isEmpty()) {
return "QRIS"; // Default assumption for RETAIL_OUTLET
}
String category = channelCategory.toUpperCase().trim();
Log.d("ReprintAdapterActivity", "🔍 Mapping channelCategory: " + category);
switch (category) {
case "RETAIL_OUTLET":
return "QRIS"; // Most RETAIL_OUTLET transactions are QRIS
case "DEBIT":
case "DEBIT_CARD":
return "Kartu Debit";
case "CREDIT":
case "CREDIT_CARD":
return "Kartu Kredit";
case "E_MONEY":
case "EMONEY":
return "E-Money";
case "BANK_TRANSFER":
return "Transfer Bank";
case "VIRTUAL_ACCOUNT":
return "Virtual Account";
default:
Log.d("ReprintAdapterActivity", "🔍 Unknown channelCategory: " + category + ", defaulting to QRIS");
return "QRIS";
}
}
private String mapChannelCategoryToPaymentMethod(String channelCategory) {
if (channelCategory == null || channelCategory.isEmpty()) {
return "Unknown";
}
String category = channelCategory.toUpperCase().trim();
switch (category) {
case "RETAIL_OUTLET":
return "QRIS";
case "DEBIT":
case "DEBIT_CARD":
return "Kartu Debit";
case "CREDIT":
case "CREDIT_CARD":
return "Kartu Kredit";
case "E_MONEY":
case "EMONEY":
return "E-Money";
case "BANK_TRANSFER":
return "Transfer Bank";
case "VIRTUAL_ACCOUNT":
return "Virtual Account";
case "QRIS":
return "QRIS";
default:
// Capitalize first letter for unknown categories
return capitalizeFirstLetter(channelCategory);
}
}
private String capitalizeFirstLetter(String text) {
if (text == null || text.isEmpty()) {
return text;
}
return text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
}
@Override

View File

@@ -1,4 +1,5 @@
package com.example.bdkipoc;
package com.example.bdkipoc.histori;
import com.example.bdkipoc.BuildConfig;
import android.content.Intent;
import android.os.AsyncTask;
@@ -8,8 +9,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.graphics.Color;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -29,6 +32,9 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import com.example.bdkipoc.R;
public class HistoryActivity extends AppCompatActivity {
@@ -39,12 +45,13 @@ public class HistoryActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private HistoryAdapter adapter;
private List<HistoryItem> historyList;
private ImageView btnBack;
private LinearLayout backNavigation;
// Store full data for detail view
private static List<HistoryItem> fullHistoryData = new ArrayList<>();
private String API_URL = "https://be-edc.msvc.app/transactions?page=0&limit=50&sortOrder=DESC&from_date=2025-05-10&to_date=2025-05-21&location_id=0&merchant_id=0&sortColumn=id";
private String API_URL;
private String SUMMARY_API_URL;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -53,17 +60,31 @@ public class HistoryActivity extends AppCompatActivity {
initViews();
setupRecyclerView();
buildApiUrl();
fetchApiData();
fetchSummaryData(); // Add this line to fetch summary data
setupClickListeners();
}
private void buildApiUrl() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
String todayDate = dateFormat.format(new Date());
// Gunakan BuildConfig untuk base URL
API_URL = BuildConfig.BACKEND_BASE_URL + "/transactions?page=0&limit=10&sortOrder=DESC&from_date="
+ todayDate + "&to_date=" + todayDate + "&location_id=0&merchant_id=0&tid=&mid=&sortColumn=id";
SUMMARY_API_URL = BuildConfig.BACKEND_BASE_URL + "/transactions/list?from_date="
+ todayDate + "&to_date=" + todayDate + "&location_id=0&merchant_id=0";
}
private void initViews() {
tvTotalAmount = findViewById(R.id.tv_total_amount);
tvTotalTransactions = findViewById(R.id.tv_total_transactions);
btnLihatDetailTop = findViewById(R.id.btn_lihat_detail);
btnLihatDetailBottom = findViewById(R.id.btn_lihat_detail_bottom);
recyclerView = findViewById(R.id.recycler_view);
btnBack = findViewById(R.id.btn_back);
backNavigation = findViewById(R.id.back_navigation);
historyList = new ArrayList<>();
}
@@ -75,7 +96,7 @@ public class HistoryActivity extends AppCompatActivity {
}
private void setupClickListeners() {
btnBack.setOnClickListener(new View.OnClickListener() {
backNavigation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
@@ -86,7 +107,7 @@ public class HistoryActivity extends AppCompatActivity {
@Override
public void onClick(View v) {
try {
Intent intent = new Intent(HistoryActivity.this, HistoryDetailActivity.class);
Intent intent = new Intent(HistoryActivity.this, HistoryListActivity.class);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
@@ -103,14 +124,15 @@ public class HistoryActivity extends AppCompatActivity {
new ApiTask().execute(API_URL);
}
private void fetchSummaryData() {
new SummaryApiTask().execute(SUMMARY_API_URL);
}
private void processApiData(JSONArray dataArray) {
try {
historyList.clear();
fullHistoryData.clear(); // Clear static data
final long[] totalAmountArray = {0};
final int[] totalTransactionsArray = {0};
for (int i = 0; i < dataArray.length(); i++) {
JSONObject item = dataArray.getJSONObject(i);
@@ -134,27 +156,19 @@ public class HistoryActivity extends AppCompatActivity {
historyItem.setDate(formatDate(transactionDate));
historyItem.setAmount((long) amountValue);
historyItem.setChannelName(formatChannelName(channelCode));
historyItem.setStatus(status);
historyItem.setStatus(status.toUpperCase()); // Ensure uppercase for consistency
historyItem.setReferenceId(referenceId);
historyItem.setFullDate(transactionDate);
historyItem.setChannelCode(channelCode);
// Add to full data
fullHistoryData.add(historyItem);
// Add first 10 to display list
if (i < 10) {
// Add to both lists (since we're limiting to 10 in API call)
historyList.add(historyItem);
}
totalAmountArray[0] += (long) amountValue;
totalTransactionsArray[0]++;
fullHistoryData.add(historyItem);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
updateSummary(totalAmountArray[0], totalTransactionsArray[0]);
adapter.notifyDataSetChanged();
}
});
@@ -171,6 +185,55 @@ public class HistoryActivity extends AppCompatActivity {
}
}
private void processSummaryData(JSONArray dataArray) {
try {
long totalAmount = 0;
int totalTransactions = 0;
for (int i = 0; i < dataArray.length(); i++) {
JSONObject item = dataArray.getJSONObject(i);
String amount = item.getString("amount");
String status = item.getString("status");
// Only count if status is settlement or success
if ("SETTLEMENT".equalsIgnoreCase(status) || "SUCCESS".equalsIgnoreCase(status)) {
// Parse amount safely
double amountValue = 0;
try {
amountValue = Double.parseDouble(amount);
} catch (NumberFormatException e) {
amountValue = 0;
}
totalAmount += (long) amountValue;
totalTransactions++;
}
}
final long finalTotalAmount = totalAmount;
final int finalTotalTransactions = totalTransactions;
runOnUiThread(new Runnable() {
@Override
public void run() {
updateSummary(finalTotalAmount, finalTotalTransactions);
}
});
} catch (JSONException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(HistoryActivity.this, "Error parsing summary data", Toast.LENGTH_SHORT).show();
// Set default values if summary fails
updateSummary(0, 0);
}
});
}
}
private void updateSummary(long totalAmount, int totalTransactions) {
tvTotalAmount.setText("RP " + formatCurrency(totalAmount));
tvTotalTransactions.setText(String.valueOf(totalTransactions));
@@ -180,24 +243,35 @@ public class HistoryActivity extends AppCompatActivity {
historyList.clear();
fullHistoryData.clear();
// Create sample data
// Get today's date for sample data
SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault());
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
Date now = new Date();
String todayDate = dateFormat.format(now);
String currentTime = timeFormat.format(now);
// Create sample data with today's date - limit to 10 items
HistoryItem[] sampleData = {
new HistoryItem("03:44", "11-05-2025", 2018619, "Kredit", "FAILED", "197870"),
new HistoryItem("03:10", "12-05-2025", 3974866, "QRIS", "SUCCESS", "053059"),
new HistoryItem("15:17", "13-05-2025", 2418167, "QRIS", "FAILED", "668320"),
new HistoryItem("12:09", "11-05-2025", 3429230, "Debit", "FAILED", "454790"),
new HistoryItem("08:39", "10-05-2025", 4656447, "QRIS", "FAILED", "454248"),
new HistoryItem("00:35", "12-05-2025", 3507704, "QRIS", "FAILED", "301644"),
new HistoryItem("22:43", "13-05-2025", 4277904, "Debit", "SUCCESS", "388709"),
new HistoryItem("18:16", "11-05-2025", 4456904, "Debit", "FAILED", "986861")
new HistoryItem("08:30", todayDate, 1500000, "QRIS", "SUCCESS", "TXN001"),
new HistoryItem("09:15", todayDate, 750000, "Debit", "SUCCESS", "TXN002"),
new HistoryItem("10:20", todayDate, 2250000, "QRIS", "FAILED", "TXN003"),
new HistoryItem("11:45", todayDate, 980000, "Kredit", "SUCCESS", "TXN004"),
new HistoryItem("12:30", todayDate, 1800000, "QRIS", "SUCCESS", "TXN005"),
new HistoryItem("13:15", todayDate, 650000, "Debit", "FAILED", "TXN006"),
new HistoryItem("14:00", todayDate, 3200000, "QRIS", "SUCCESS", "TXN007"),
new HistoryItem("15:30", todayDate, 1100000, "Kredit", "SUCCESS", "TXN008"),
new HistoryItem("16:45", todayDate, 890000, "Debit", "FAILED", "TXN009"),
new HistoryItem("17:20", todayDate, 2100000, "QRIS", "SUCCESS", "TXN010")
};
long totalAmount = 0;
for (HistoryItem item : sampleData) {
historyList.add(item);
fullHistoryData.add(item);
totalAmount += item.getAmount();
}
tvTotalAmount.setText("RP 36.166.829");
tvTotalAmount.setText("RP " + formatCurrency(totalAmount));
tvTotalTransactions.setText("10");
adapter.notifyDataSetChanged();
@@ -205,12 +279,16 @@ public class HistoryActivity extends AppCompatActivity {
private String formatChannelName(String channelCode) {
switch (channelCode) {
case "DEBIT":
case "DEBIT_CARD":
return "Debit";
case "CREDIT_CARD":
return "Kredit";
case "QRIS":
return "QRIS";
case "E_MONEY":
return "E-Money";
case "OTHER":
return "Kredit";
return "Lainnya";
default:
return channelCode.substring(0, 1).toUpperCase() +
channelCode.substring(1).toLowerCase();
@@ -220,22 +298,31 @@ public class HistoryActivity extends AppCompatActivity {
private String formatTime(String isoDate) {
try {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
SimpleDateFormat outputFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
inputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
SimpleDateFormat outputFormat = new SimpleDateFormat("HH.mm.ss", Locale.getDefault());
outputFormat.setTimeZone(TimeZone.getDefault());
Date date = inputFormat.parse(isoDate);
return outputFormat.format(date);
} catch (ParseException e) {
return "00:00";
return "00.00.00";
}
}
private String formatDate(String isoDate) {
try {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
inputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault());
outputFormat.setTimeZone(TimeZone.getDefault());
Date date = inputFormat.parse(isoDate);
return outputFormat.format(date);
} catch (ParseException e) {
return "01-01-2025";
SimpleDateFormat fallbackFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault());
return fallbackFormat.format(new Date());
}
}
@@ -249,7 +336,7 @@ public class HistoryActivity extends AppCompatActivity {
return new ArrayList<>(fullHistoryData);
}
// AsyncTask for API call
// AsyncTask for main API call (transaction list with limit)
private class ApiTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
@@ -302,6 +389,59 @@ public class HistoryActivity extends AppCompatActivity {
}
}
}
// AsyncTask for summary API call (all transactions for totals)
private class SummaryApiTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
try {
JSONObject jsonResponse = new JSONObject(result);
if (jsonResponse.getInt("status") == 200) {
JSONArray dataArray = jsonResponse.getJSONArray("data");
processSummaryData(dataArray);
} else {
Toast.makeText(HistoryActivity.this, "Summary API Error", Toast.LENGTH_SHORT).show();
updateSummary(0, 0);
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(HistoryActivity.this, "Summary JSON Parse Error", Toast.LENGTH_SHORT).show();
updateSummary(0, 0);
}
} else {
Toast.makeText(HistoryActivity.this, "Summary Network Error", Toast.LENGTH_SHORT).show();
updateSummary(0, 0);
}
}
}
}
// HistoryItem class - enhanced with more fields
@@ -366,21 +506,30 @@ class HistoryAdapter extends RecyclerView.Adapter<HistoryAdapter.HistoryViewHold
public void onBindViewHolder(@NonNull HistoryViewHolder holder, int position) {
HistoryItem item = historyList.get(position);
holder.tvTime.setText(item.getTime() + ", " + item.getDate());
// Format time with dots instead of colons (09.00 instead of 09:00)
String formattedTime = item.getTime().replace(":", ".") + ", " + item.getDate();
holder.tvTime.setText(formattedTime);
holder.tvAmount.setText("Rp. " + formatCurrency(item.getAmount()));
holder.tvChannel.setText(item.getChannelName());
// Set status color
// Set status color and text based on API response
String status = item.getStatus();
if ("SUCCESS".equals(status)) {
holder.tvStatus.setText("Berhasil");
holder.tvStatus.setTextColor(0xFF4CAF50); // Green
} else if ("FAILED".equals(status)) {
holder.tvStatus.setText("Gagal");
holder.tvStatus.setTextColor(0xFFF44336); // Red
if ("SETTLEMENT".equalsIgnoreCase(status)) {
holder.tvStatus.setText("Success");
holder.tvStatus.setTextColor(Color.parseColor("#4CAF50")); // Green
} else if ("SUCCESS".equalsIgnoreCase(status)) {
holder.tvStatus.setText("Success");
holder.tvStatus.setTextColor(Color.parseColor("#4CAF50")); // Green
} else if ("EXPIRE".equalsIgnoreCase(status)) {
holder.tvStatus.setText("Expired");
holder.tvStatus.setTextColor(Color.parseColor("#F44336")); // Red
} else if ("FAILED".equalsIgnoreCase(status)) {
holder.tvStatus.setText("Failed");
holder.tvStatus.setTextColor(Color.parseColor("#F44336")); // Red
} else {
holder.tvStatus.setText("Tertunda");
holder.tvStatus.setTextColor(0xFFFF9800); // Orange
holder.tvStatus.setText("Pending");
holder.tvStatus.setTextColor(Color.parseColor("#3141FF")); // Blue
}
}

View File

@@ -1,204 +0,0 @@
package com.example.bdkipoc;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class HistoryDetailActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private HistoryDetailAdapter adapter;
private List<HistoryItem> detailList;
private ImageView btnBack;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_history_detail);
initViews();
setupRecyclerView();
loadData();
setupClickListeners();
}
private void initViews() {
recyclerView = findViewById(R.id.recycler_view);
btnBack = findViewById(R.id.btn_back);
detailList = new ArrayList<>();
}
private void setupRecyclerView() {
adapter = new HistoryDetailAdapter(detailList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
private void setupClickListeners() {
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void loadData() {
try {
// Get data from HistoryActivity
List<HistoryItem> fullData = HistoryActivity.getFullHistoryData();
if (fullData != null && !fullData.isEmpty()) {
detailList.clear();
detailList.addAll(fullData);
adapter.notifyDataSetChanged();
} else {
loadSampleDetailData();
}
} catch (Exception e) {
e.printStackTrace();
loadSampleDetailData();
}
}
private void loadSampleDetailData() {
detailList.clear();
// Create sample detail data
HistoryItem[] sampleData = {
new HistoryItem("03:44", "11-05-2025", 2018619, "Kredit", "FAILED", "197870"),
new HistoryItem("03:10", "12-05-2025", 3974866, "QRIS", "SUCCESS", "053059"),
new HistoryItem("15:17", "13-05-2025", 2418167, "QRIS", "FAILED", "668320"),
new HistoryItem("12:09", "11-05-2025", 3429230, "Debit", "FAILED", "454790"),
new HistoryItem("08:39", "10-05-2025", 4656447, "QRIS", "FAILED", "454248"),
new HistoryItem("00:35", "12-05-2025", 3507704, "QRIS", "FAILED", "301644"),
new HistoryItem("22:43", "13-05-2025", 4277904, "Debit", "SUCCESS", "388709"),
new HistoryItem("18:16", "11-05-2025", 4456904, "Debit", "FAILED", "986861"),
new HistoryItem("12:51", "10-05-2025", 3027953, "Kredit", "SUCCESS", "771339"),
new HistoryItem("19:50", "14-05-2025", 4399035, "QRIS", "FAILED", "103478")
};
for (HistoryItem item : sampleData) {
detailList.add(item);
}
adapter.notifyDataSetChanged();
}
}
// HistoryDetailAdapter class - simplified for stability
class HistoryDetailAdapter extends RecyclerView.Adapter<HistoryDetailAdapter.DetailViewHolder> {
private List<HistoryItem> detailList;
public HistoryDetailAdapter(List<HistoryItem> detailList) {
this.detailList = detailList;
}
@NonNull
@Override
public DetailViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_history_detail, parent, false);
return new DetailViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull DetailViewHolder holder, int position) {
HistoryItem item = detailList.get(position);
try {
holder.tvReferenceId.setText("Ref: " + item.getReferenceId());
holder.tvAmount.setText("Rp. " + formatCurrency(item.getAmount()));
holder.tvChannel.setText(item.getChannelName());
holder.tvMerchant.setText("TEST MERCHANT");
holder.tvTime.setText(formatDateTime(item.getTime(), item.getDate()));
holder.tvIssuer.setText("BANK MANDIRI");
// Set status color
String status = item.getStatus();
if ("SUCCESS".equals(status)) {
holder.tvStatus.setText("Berhasil");
holder.tvStatus.setTextColor(0xFF4CAF50); // Green
} else if ("FAILED".equals(status)) {
holder.tvStatus.setText("Gagal");
holder.tvStatus.setTextColor(0xFFF44336); // Red
} else {
holder.tvStatus.setText("Tertunda");
holder.tvStatus.setTextColor(0xFFFF9800); // Orange
}
} catch (Exception e) {
e.printStackTrace();
// Set default values if error occurs
holder.tvReferenceId.setText("Ref: " + position);
holder.tvAmount.setText("Rp. 0");
holder.tvChannel.setText("Unknown");
holder.tvMerchant.setText("TEST MERCHANT");
holder.tvTime.setText("00:00, 01-01-2025");
holder.tvIssuer.setText("UNKNOWN");
holder.tvStatus.setText("Tidak Diketahui");
holder.tvStatus.setTextColor(0xFF666666);
}
}
@Override
public int getItemCount() {
return detailList != null ? detailList.size() : 0;
}
private String formatCurrency(long amount) {
try {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
} catch (Exception e) {
return String.valueOf(amount);
}
}
private String formatDateTime(String time, String date) {
try {
return time + ", " + date;
} catch (Exception e) {
return "00:00, 01-01-2025";
}
}
static class DetailViewHolder extends RecyclerView.ViewHolder {
TextView tvReferenceId;
TextView tvAmount;
TextView tvChannel;
TextView tvMerchant;
TextView tvTime;
TextView tvIssuer;
TextView tvStatus;
public DetailViewHolder(@NonNull View itemView) {
super(itemView);
try {
tvReferenceId = itemView.findViewById(R.id.tv_reference_id);
tvAmount = itemView.findViewById(R.id.tv_amount);
tvChannel = itemView.findViewById(R.id.tv_channel);
tvMerchant = itemView.findViewById(R.id.tv_merchant);
tvTime = itemView.findViewById(R.id.tv_time);
tvIssuer = itemView.findViewById(R.id.tv_issuer);
tvStatus = itemView.findViewById(R.id.tv_status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,338 @@
package com.example.bdkipoc.histori;
import com.example.bdkipoc.BuildConfig;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.bdkipoc.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
public class HistoryListActivity extends AppCompatActivity {
private RecyclerView rvHistory;
private TextView tvEmpty;
private HistoryListAdapter adapter;
private List<Transaction> transactionList = new ArrayList<>();
private String API_URL;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_history_list);
// Initialize views
rvHistory = findViewById(R.id.rv_history);
tvEmpty = findViewById(R.id.tv_empty);
// Set up RecyclerView
adapter = new HistoryListAdapter(transactionList);
rvHistory.setLayoutManager(new LinearLayoutManager(this));
rvHistory.setAdapter(adapter);
// Set up app bar
setupAppBar();
// Build API URL and load data
buildApiUrl();
fetchTransactionData();
}
private void buildApiUrl() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
String currentDate = dateFormat.format(new Date());
// Gunakan BuildConfig untuk base URL
API_URL = BuildConfig.BACKEND_BASE_URL + "/transactions/list?from_date=" + currentDate +
"&to_date=" + currentDate + "&location_id=0&merchant_id=0";
}
private void setupAppBar() {
LinearLayout backNavigation = findViewById(R.id.back_navigation);
TextView appbarTitle = findViewById(R.id.appbarTitle);
appbarTitle.setText("Kembali");
backNavigation.setOnClickListener(v -> onBackPressed());
}
private void fetchTransactionData() {
new FetchTransactionsTask().execute(API_URL);
}
private void updateTransactionList(List<Transaction> transactions) {
transactionList.clear();
if (transactions != null && !transactions.isEmpty()) {
transactionList.addAll(transactions);
tvEmpty.setVisibility(View.GONE);
rvHistory.setVisibility(View.VISIBLE);
} else {
tvEmpty.setVisibility(View.VISIBLE);
rvHistory.setVisibility(View.GONE);
}
adapter.notifyDataSetChanged();
}
// AsyncTask to fetch transactions from API
private class FetchTransactionsTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
try {
JSONObject jsonResponse = new JSONObject(result);
if (jsonResponse.getInt("status") == 200) {
JSONArray dataArray = jsonResponse.getJSONArray("data");
List<Transaction> transactions = parseTransactions(dataArray);
updateTransactionList(transactions);
} else {
showError("API Error: " + jsonResponse.getString("message"));
}
} catch (JSONException e) {
e.printStackTrace();
showError("Error parsing data");
}
} else {
showError("Network error");
}
}
}
private List<Transaction> parseTransactions(JSONArray dataArray) throws JSONException {
List<Transaction> transactions = new ArrayList<>();
for (int i = 0; i < dataArray.length(); i++) {
JSONObject item = dataArray.getJSONObject(i);
try {
Transaction transaction = new Transaction(
item.getString("transaction_date"),
item.getString("amount"),
item.getString("channel_code"),
item.getString("status")
);
transactions.add(transaction);
} catch (ParseException e) {
e.printStackTrace();
}
}
// Urutkan dari terbaru ke terlama
transactions.sort((t1, t2) -> {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
Date d1 = sdf.parse(t1.getDateTime());
Date d2 = sdf.parse(t2.getDateTime());
return d2.compareTo(d1); // terbaru di atas
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
});
return transactions;
}
private void showError(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
tvEmpty.setVisibility(View.VISIBLE);
rvHistory.setVisibility(View.GONE);
}
// Transaction model class
public static class Transaction {
private final String dateTime;
private final String amount;
private final String channel;
private final String status;
public Transaction(String dateTime, String amount, String channel, String status) throws ParseException {
this.dateTime = dateTime;
this.amount = amount;
this.channel = channel;
this.status = status;
}
public String getDateTime() {
return dateTime;
}
public String getAmount() {
return amount;
}
public String getChannel() {
return channel;
}
public String getStatus() {
return status;
}
}
// Adapter class
public class HistoryListAdapter extends RecyclerView.Adapter<HistoryListAdapter.ViewHolder> {
private final List<Transaction> transactions;
public HistoryListAdapter(List<Transaction> transactions) {
this.transactions = transactions;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_history_list, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Transaction transaction = transactions.get(position);
// Format date with timezone conversion
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
inputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
SimpleDateFormat outputFormat = new SimpleDateFormat("HH:mm.ss, dd MMM yyyy", Locale.getDefault());
outputFormat.setTimeZone(TimeZone.getDefault());
try {
Date date = inputFormat.parse(transaction.getDateTime());
holder.tvTime.setText(outputFormat.format(date));
} catch (ParseException e) {
holder.tvTime.setText(transaction.getDateTime());
e.printStackTrace();
}
// Format amount
holder.tvAmount.setText(String.format(Locale.getDefault(), "Rp. %s", transaction.getAmount()));
// Set channel and status
holder.tvChannel.setText(formatChannelName(transaction.getChannel()));
holder.tvStatus.setText(formatStatusText(transaction.getStatus()));
// Set status color
int statusColor = Color.parseColor("#000000"); // default black
if ("SUCCESS".equalsIgnoreCase(transaction.getStatus())) {
statusColor = Color.parseColor("#4CAF50"); // green
} else if ("FAILED".equalsIgnoreCase(transaction.getStatus())) {
statusColor = Color.parseColor("#F44336"); // red
} else if ("INIT".equalsIgnoreCase(transaction.getStatus())) {
statusColor = Color.parseColor("#FF9800"); // orange
}
holder.tvStatus.setTextColor(statusColor);
}
@Override
public int getItemCount() {
return transactions.size();
}
private String formatChannelName(String channelCode) {
switch (channelCode) {
case "E_MONEY":
return "E-Money";
case "QRIS":
return "QRIS";
case "CREDIT_CARD":
return "Kredit";
case "DEBIT_CARD":
return "Debit";
default:
return channelCode;
}
}
private String formatStatusText(String status) {
switch (status) {
case "SUCCESS":
return "Berhasil";
case "FAILED":
return "Gagal";
case "INIT":
return "Tertunda";
default:
return status;
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final TextView tvTime;
public final TextView tvAmount;
public final TextView tvChannel;
public final TextView tvStatus;
public ViewHolder(View view) {
super(view);
tvTime = view.findViewById(R.id.tv_time);
tvAmount = view.findViewById(R.id.tv_amount);
tvChannel = view.findViewById(R.id.tv_channel);
tvStatus = view.findViewById(R.id.tv_status);
// Set click listener if needed
view.setOnClickListener(v -> {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
Transaction transaction = transactions.get(position);
// TODO: Handle item click, maybe open detail activity
// Intent intent = new Intent(HistoryListActivity.this, HistoryDetailActivity.class);
// Pass transaction data to detail activity
// startActivity(intent);
}
});
}
}
}
}

View File

@@ -0,0 +1,424 @@
package com.example.bdkipoc.infotoko;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.InputType;
import android.view.View;
import android.view.ViewParent;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import com.example.bdkipoc.LoginActivity;
import com.example.bdkipoc.R;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import android.text.method.PasswordTransformationMethod;
import org.json.JSONException;
import org.json.JSONObject;
public class InfoTokoActivity extends AppCompatActivity {
private static final String TAG = "InfoTokoActivity";
// Views
private TextView tvStoreName;
private TextView tvMerchantId;
private TextView tvTerminalId;
private TextInputEditText etEmail;
private TextInputEditText etPassword;
private TextInputEditText etOwnerName;
private TextInputEditText etNik;
private TextInputEditText etPhone;
private TextInputEditText etBusinessType;
private TextInputEditText etBusinessName;
private TextInputEditText etAddress;
private MaterialButton btnUpdate;
private LinearLayout backNavigation; // Changed from ImageView to LinearLayout
// Data
private String authToken;
private JSONObject userData;
private String userPassword; // Add password storage
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_info_toko);
// Check if user is logged in
if (!LoginActivity.isLoggedIn(this)) {
finish();
return;
}
// Initialize views
initializeViews();
// Load user data
loadUserData();
// Setup listeners
setupListeners();
// Display store information
displayStoreInfo();
}
private void initializeViews() {
// Header
tvStoreName = findViewById(R.id.tv_store_name);
tvMerchantId = findViewById(R.id.tv_merchant_id);
tvTerminalId = findViewById(R.id.tv_terminal_id);
// Find the back navigation from the included layout
backNavigation = findViewById(R.id.back_navigation);
// Optionally, you can also update the title in the appbar
TextView appbarTitle = findViewById(R.id.appbarTitle);
if (appbarTitle != null) {
appbarTitle.setText("Kembali");
}
// Account Information
etEmail = findViewById(R.id.et_email);
etPassword = findViewById(R.id.et_password);
// Store Information
etOwnerName = findViewById(R.id.et_owner_name);
etNik = findViewById(R.id.et_nik);
etPhone = findViewById(R.id.et_phone);
etBusinessType = findViewById(R.id.et_business_type);
etBusinessName = findViewById(R.id.et_business_name);
etAddress = findViewById(R.id.et_address);
// Button
btnUpdate = findViewById(R.id.btn_update);
}
private void loadUserData() {
// Get authentication token
authToken = getIntent().getStringExtra("AUTH_TOKEN");
if (authToken == null) {
authToken = LoginActivity.getToken(this);
}
// Get user data
String userDataString = getIntent().getStringExtra("USER_DATA");
if (userDataString != null) {
try {
userData = new JSONObject(userDataString);
} catch (JSONException e) {
Log.e(TAG, "Error parsing user data: " + e.getMessage());
}
}
if (userData == null) {
userData = LoginActivity.getUserDataAsJson(this);
}
// Get saved password from SharedPreferences
SharedPreferences prefs = getSharedPreferences("LoginPrefs", MODE_PRIVATE);
userPassword = prefs.getString("current_password", ""); // Fix: use correct key
Log.d(TAG, "Loaded auth token: " + (authToken != null ? "" : ""));
Log.d(TAG, "Loaded user data: " + (userData != null ? "" : ""));
Log.d(TAG, "Loaded password: " + (!userPassword.isEmpty() ? "" : ""));
}
private void setupListeners() {
// Back button - now using the LinearLayout
if (backNavigation != null) {
backNavigation.setOnClickListener(v -> {
Log.d(TAG, "Back button clicked");
finish();
});
} else {
Log.e(TAG, "Back navigation not found!");
}
// Update button
if (btnUpdate != null) {
btnUpdate.setOnClickListener(v -> updateStoreInfo());
}
// Password toggle listener
setupPasswordToggle();
}
private void setupPasswordToggle() {
ViewParent passwordParentView = etPassword.getParent().getParent();
if (passwordParentView instanceof TextInputLayout) {
TextInputLayout passwordLayout = (TextInputLayout) passwordParentView;
// Set initial state to visible
etPassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
passwordLayout.setEndIconOnClickListener(v -> {
// Toggle password visibility
if (etPassword.getTransformationMethod() == null) {
// Hide password
etPassword.setTransformationMethod(PasswordTransformationMethod.getInstance());
passwordLayout.setEndIconDrawable(R.drawable.ic_visibility_off); // Set your eye-off icon
} else {
// Show password
etPassword.setTransformationMethod(null);
passwordLayout.setEndIconDrawable(R.drawable.ic_visibility); // Set your eye icon
}
// Move cursor to end
etPassword.setSelection(etPassword.getText().length());
});
}
}
private void displayStoreInfo() {
// Display store name and IDs (static for header)
tvStoreName.setText("TOKO KLONTONG PAK EKO");
tvMerchantId.setText("MID: 12345678901");
tvTerminalId.setText("TID: 12345678901");
// Hide fields that are not needed based on requirements
hideUnnecessaryFields();
// Display data from login response
if (userData != null) {
try {
// Email - from API response
String email = userData.optString("email", "");
if (!email.isEmpty()) {
etEmail.setText(email);
} else {
etEmail.setText("Email tidak tersedia");
}
// Password - show actual password from SharedPreferences (VISIBLE by default)
if (!userPassword.isEmpty()) {
etPassword.setText(userPassword);
// Start with password visible
etPassword.setTransformationMethod(null);
// Refresh the eye icon state
ViewParent passwordParentView = etPassword.getParent().getParent();
if (passwordParentView instanceof TextInputLayout) {
((TextInputLayout) passwordParentView).setEndIconDrawable(R.drawable.ic_visibility);
}
} else {
etPassword.setText("");
}
etPassword.setEnabled(true); // Enable for display with toggle
// Update the eye icon to show "hide" state initially
ViewParent passwordParentView = etPassword.getParent().getParent();
if (passwordParentView instanceof TextInputLayout) {
TextInputLayout passwordLayout = (TextInputLayout) passwordParentView;
passwordLayout.setPasswordVisibilityToggleEnabled(true);
// Force refresh the toggle icon
passwordLayout.refreshDrawableState();
}
// Debug log
Log.d(TAG, "Password field text: " + etPassword.getText().toString());
Log.d(TAG, "Password field length: " + etPassword.getText().length());
// Nama Pemilik - from API response
String ownerName = userData.optString("name", "");
if (!ownerName.isEmpty()) {
etOwnerName.setText(ownerName);
} else {
etOwnerName.setText("Nama tidak tersedia");
}
// Nomor Telepon - from API response
String phone = userData.optString("phone", "");
if (!phone.isEmpty()) {
etPhone.setText(phone);
} else {
etPhone.setText("Nomor telepon tidak tersedia");
}
// Log the user data for debugging
Log.d(TAG, "User Data: " + userData.toString());
} catch (Exception e) {
Log.e(TAG, "Error displaying user info: " + e.getMessage());
Toast.makeText(this, "Error loading user data", Toast.LENGTH_SHORT).show();
}
} else {
// Show default/empty values if no user data
etEmail.setText("Email tidak tersedia");
etPassword.setText("••••••••");
etPassword.setEnabled(false);
etOwnerName.setText("Nama tidak tersedia");
etPhone.setText("Nomor telepon tidak tersedia");
}
}
private void hideUnnecessaryFields() {
// Hide NIK field and its container
ViewParent nikContainer = etNik.getParent();
if (nikContainer != null && nikContainer instanceof View) {
((View) nikContainer).setVisibility(View.GONE);
}
// Hide Business Type field and its container
ViewParent businessTypeContainer = etBusinessType.getParent();
if (businessTypeContainer != null && businessTypeContainer instanceof View) {
((View) businessTypeContainer).setVisibility(View.GONE);
}
// Hide Business Name field and its container
ViewParent businessNameContainer = etBusinessName.getParent();
if (businessNameContainer != null && businessNameContainer instanceof View) {
((View) businessNameContainer).setVisibility(View.GONE);
}
// Hide Address field and its container
ViewParent addressContainer = etAddress.getParent();
if (addressContainer != null && addressContainer instanceof View) {
((View) addressContainer).setVisibility(View.GONE);
}
// Update the section title to be more accurate
// Note: You'll need to add an ID to the section title TextView in the XML
}
private void updateStoreInfo() {
// Validate inputs
if (!validateInputs()) {
return;
}
// Show loading
btnUpdate.setEnabled(false);
btnUpdate.setText("Memperbarui...");
// Simulate update process (in real app, this would call API)
btnUpdate.postDelayed(() -> {
// In a real implementation, you would:
// 1. Call API to update user info
// 2. Update SharedPreferences with new data
// 3. Show success/error message
// Show success message
Toast.makeText(this, "Informasi akun berhasil diperbarui", Toast.LENGTH_SHORT).show();
// If password was changed, inform user
String currentPasswordText = etPassword.getText().toString();
if (!currentPasswordText.isEmpty() && !currentPasswordText.equals(userPassword)) {
Toast.makeText(this, "Password berhasil diperbarui", Toast.LENGTH_SHORT).show();
userPassword = currentPasswordText; // Update local variable
}
// Reset button state
btnUpdate.setEnabled(true);
btnUpdate.setText("Perbarui Informasi Toko");
// Optional: Save updated data locally
saveUpdatedData();
}, 2000); // Simulate 2 second delay
}
private boolean validateInputs() {
// Check email
String email = etEmail.getText().toString().trim();
if (email.isEmpty()) {
etEmail.setError("Email tidak boleh kosong");
etEmail.requestFocus();
return false;
}
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
etEmail.setError("Format email tidak valid");
etEmail.requestFocus();
return false;
}
// Check owner name
String ownerName = etOwnerName.getText().toString().trim();
if (ownerName.isEmpty()) {
etOwnerName.setError("Nama pemilik tidak boleh kosong");
etOwnerName.requestFocus();
return false;
}
// Check phone
String phone = etPhone.getText().toString().trim();
if (phone.isEmpty()) {
etPhone.setError("Nomor telepon tidak boleh kosong");
etPhone.requestFocus();
return false;
}
if (phone.length() < 10) {
etPhone.setError("Nomor telepon tidak valid");
etPhone.requestFocus();
return false;
}
// Check password if changed
String password = etPassword.getText().toString();
if (!password.isEmpty() && password.length() < 6) {
etPassword.setError("Password minimal 6 karakter");
etPassword.requestFocus();
return false;
}
return true;
}
private void saveUpdatedData() {
// In a real app, this would update the user data in SharedPreferences
// and call an API to update the server
try {
JSONObject updatedData = new JSONObject();
updatedData.put("email", etEmail.getText().toString().trim());
updatedData.put("name", etOwnerName.getText().toString().trim());
updatedData.put("phone", etPhone.getText().toString().trim());
// Merge with existing userData
if (userData != null) {
// Keep other fields from original userData
updatedData.put("id", userData.optString("id", ""));
updatedData.put("role", userData.optString("role", ""));
// Add other fields as needed
}
// Save updated password to SharedPreferences
String newPassword = etPassword.getText().toString();
if (!newPassword.isEmpty() && !newPassword.equals(userPassword)) {
SharedPreferences prefs = getSharedPreferences("LoginPrefs", MODE_PRIVATE);
prefs.edit().putString("current_password", newPassword).apply(); // Fix: use correct key
Log.d(TAG, "Password updated in SharedPreferences");
}
Log.d(TAG, "Updated data: " + updatedData.toString());
// In real app, you would:
// 1. Call API to update user data
// 2. On success, update SharedPreferences:
// SharedPreferences prefs = getSharedPreferences("LoginPrefs", MODE_PRIVATE);
// prefs.edit().putString("user_data", updatedData.toString()).apply();
} catch (JSONException e) {
Log.e(TAG, "Error creating updated data: " + e.getMessage());
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
finish();
}
}

View File

@@ -1,4 +1,6 @@
package com.example.bdkipoc;
import com.example.bdkipoc.qris.view.QrisResultActivity;
import com.example.bdkipoc.BuildConfig;
import android.content.Context;
import android.content.Intent;
@@ -53,6 +55,7 @@ public class QrisActivity extends AppCompatActivity {
private String transactionId;
private String transactionUuid;
private String transactionDate;
private String referenceId;
private int amount;
private JSONObject midtransResponse;
@@ -68,10 +71,7 @@ public class QrisActivity extends AppCompatActivity {
private static final long REFERENCE_COOLDOWN_MS = 60000; // 1 minute cooldown
private static final long TRANSACTION_COOLDOWN_MS = 5000; // 5 second cooldown
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1PM2t1bXkwVDl4M1VvYnVvVTc3NW5QbXc=";
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
private static final String MIDTRANS_AUTH = BuildConfig.MIDTRANS_SANDBOX_AUTH;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -119,6 +119,11 @@ public class QrisActivity extends AppCompatActivity {
initiatePaymentButton.setEnabled(false);
}
private String getCurrentDateTime() {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", new java.util.Locale("id", "ID"));
return sdf.format(new java.util.Date());
}
/**
* ✅ FRONTEND DEDUPLICATION: Generate unique reference ID with local tracking
*/
@@ -303,10 +308,10 @@ public class QrisActivity extends AppCompatActivity {
editTextAmount.setText(formatAmount(amountStr));
descriptionText.setText("Tekan Konfirmasi untuk melanjutkan");
// Enable button if amount is valid
// Enable button if amount is valid (any positive amount)
try {
int amt = Integer.parseInt(amountStr);
initiatePaymentButton.setEnabled(amt >= 1000);
initiatePaymentButton.setEnabled(amt > 0); // Changed from >= 1000 to > 0
} catch (NumberFormatException e) {
initiatePaymentButton.setEnabled(false);
}
@@ -381,7 +386,8 @@ public class QrisActivity extends AppCompatActivity {
private boolean isValidServerKey(String serverKey) {
return serverKey != null &&
serverKey.startsWith("SB-Mid-server-") &&
(serverKey.startsWith("SB-Mid-server-") || // Sandbox format
serverKey.startsWith("Mid-server-")) && // Production format
serverKey.length() > 20;
}
@@ -446,17 +452,17 @@ public class QrisActivity extends AppCompatActivity {
// Parse amount - expecting integer in lowest denomination (Indonesian Rupiah)
amount = Integer.parseInt(amountText);
// Validate minimum amount
if (amount < 1000) {
errorMessage = "Minimum amount is IDR 1,000";
return false;
}
// // Validate minimum amount
// if (amount < 1000) {
// errorMessage = "Minimum amount is IDR 1,000";
// return false;
// }
// Validate maximum amount for testing
if (amount > 10000000) {
errorMessage = "Maximum amount is IDR 10,000,000";
return false;
}
// // Validate maximum amount for testing
// if (amount > 10000000) {
// errorMessage = "Maximum amount is IDR 10,000,000";
// return false;
// }
Log.d("MidtransCharge", "Parsed amount: " + amount);
} catch (NumberFormatException e) {
@@ -470,6 +476,7 @@ public class QrisActivity extends AppCompatActivity {
payload.put("status", "INIT");
payload.put("device_id", 1);
payload.put("transaction_uuid", transactionUuid);
payload.put("transaction_date", getCurrentDateTime());
payload.put("transaction_time_seconds", 0.0);
payload.put("device_code", "PB4K252T00021");
payload.put("merchant_name", "Marcel Panjaitan");
@@ -479,7 +486,7 @@ public class QrisActivity extends AppCompatActivity {
Log.d("MidtransCharge", "Backend transaction payload: " + payload.toString());
// Make the API call
URL url = new URI(BACKEND_BASE + "/transactions").toURL();
URL url = new URI(BuildConfig.BACKEND_BASE_URL + "/transactions").toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
@@ -629,22 +636,22 @@ public class QrisActivity extends AppCompatActivity {
// Log the request details
Log.d("MidtransCharge", "=== MIDTRANS QRIS REQUEST ===");
Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
Log.d("MidtransCharge", "URL: " + BuildConfig.MIDTRANS_CHARGE_URL);
Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH);
Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL);
Log.d("MidtransCharge", "X-Override-Notification: " + BuildConfig.WEBHOOK_URL);
Log.d("MidtransCharge", "Reference ID: " + referenceId);
Log.d("MidtransCharge", "Order ID: " + transactionUuid);
Log.d("MidtransCharge", "Amount: " + amount);
Log.d("MidtransCharge", "================================");
// Make the API call to Midtrans
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
URL url = new URI(BuildConfig.MIDTRANS_CHARGE_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
conn.setRequestProperty("X-Override-Notification", BuildConfig.WEBHOOK_URL);
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
conn.setDoOutput(true);
conn.setConnectTimeout(30000); // 30 seconds

View File

@@ -0,0 +1,187 @@
package com.example.bdkipoc.qris.model;
import android.util.Log;
import com.example.bdkipoc.qris.network.QrisApiService;
import org.json.JSONObject;
/**
* Repository class untuk menghandle semua data access terkait QRIS
* Mengabstraksi sumber data (API, local storage, etc.)
*/
public class QrisRepository {
private static final String TAG = "QrisRepository";
private QrisApiService apiService;
// Singleton pattern
private static QrisRepository instance;
private QrisRepository() {
this.apiService = new QrisApiService();
}
public static QrisRepository getInstance() {
if (instance == null) {
instance = new QrisRepository();
}
return instance;
}
/**
* Interface untuk callback hasil operasi
*/
public interface RepositoryCallback<T> {
void onSuccess(T result);
void onError(String errorMessage);
}
/**
* Refresh QR Code
*/
public void refreshQrCode(QrisTransaction transaction, RepositoryCallback<QrRefreshResult> callback) {
Log.d(TAG, "🔄 Refreshing QR code for transaction: " + transaction.getOrderId());
new Thread(() -> {
try {
QrRefreshResult result = apiService.generateNewQrCode(transaction);
if (result != null && result.qrUrl != null && !result.qrUrl.isEmpty()) {
Log.d(TAG, "✅ QR refresh successful");
callback.onSuccess(result);
} else {
Log.e(TAG, "❌ QR refresh failed - empty result");
callback.onError("Failed to generate new QR code");
}
} catch (Exception e) {
Log.e(TAG, "❌ QR refresh exception: " + e.getMessage(), e);
callback.onError("QR refresh error: " + e.getMessage());
}
}).start();
}
/**
* Check payment status
*/
public void checkPaymentStatus(QrisTransaction transaction, RepositoryCallback<PaymentStatusResult> callback) {
Log.d(TAG, "🔍 Checking payment status for: " + transaction.getCurrentQrTransactionId());
new Thread(() -> {
try {
// Gunakan current transaction ID, bukan original
PaymentStatusResult result = apiService.checkTransactionStatus(transaction);
if (result != null) {
Log.d(TAG, "✅ Payment status check successful: " + result.status);
// Update transaction ID jika berbeda
if (result.transactionId != null &&
!result.transactionId.equals(transaction.getCurrentQrTransactionId())) {
transaction.setCurrentQrTransactionId(result.transactionId);
}
callback.onSuccess(result);
} else {
Log.w(TAG, "⚠️ Payment status check returned null");
callback.onError("Failed to check payment status");
}
} catch (Exception e) {
Log.e(TAG, "❌ Payment status check exception: " + e.getMessage(), e);
callback.onError("Payment status error: " + e.getMessage());
}
}).start();
}
/**
* Send webhook simulation
*/
public void simulatePayment(QrisTransaction transaction, RepositoryCallback<Boolean> callback) {
Log.d(TAG, "🚀 Simulating payment for: " + transaction.getOrderId());
new Thread(() -> {
try {
boolean success = apiService.simulateWebhook(transaction);
if (success) {
Log.d(TAG, "✅ Payment simulation successful");
callback.onSuccess(true);
} else {
Log.e(TAG, "❌ Payment simulation failed");
callback.onError("Payment simulation failed");
}
} catch (Exception e) {
Log.e(TAG, "❌ Payment simulation exception: " + e.getMessage(), e);
callback.onError("Payment simulation error: " + e.getMessage());
}
}).start();
}
/**
* Poll for payment logs
*/
public void pollPaymentLogs(String orderId, RepositoryCallback<PaymentLogResult> callback) {
Log.d(TAG, "📊 Polling payment logs for: " + orderId);
new Thread(() -> {
try {
PaymentLogResult result = apiService.pollPendingPaymentLog(orderId);
if (result != null) {
Log.d(TAG, "✅ Payment log polling successful");
callback.onSuccess(result);
} else {
Log.w(TAG, "⚠️ No payment logs found");
callback.onError("No payment logs found");
}
} catch (Exception e) {
Log.e(TAG, "❌ Payment log polling exception: " + e.getMessage(), e);
callback.onError("Payment log polling error: " + e.getMessage());
}
}).start();
}
/**
* Result classes
*/
public static class QrRefreshResult {
public String qrUrl;
public String qrString;
public String transactionId;
public QrRefreshResult(String qrUrl, String qrString, String transactionId) {
this.qrUrl = qrUrl;
this.qrString = qrString;
this.transactionId = transactionId;
}
}
public static class PaymentStatusResult {
public String status;
public String paymentType;
public String issuer;
public String acquirer;
public String qrString;
public boolean statusChanged;
public String transactionId;
public PaymentStatusResult(String status) {
this.status = status;
this.statusChanged = false;
}
}
public static class PaymentLogResult {
public boolean found;
public String status;
public String orderId;
public PaymentLogResult(boolean found, String status, String orderId) {
this.found = found;
this.status = status;
this.orderId = orderId;
}
}
}

View File

@@ -0,0 +1,289 @@
package com.example.bdkipoc.qris.model;
import com.example.bdkipoc.BuildConfig;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
/**
* Model class untuk data transaksi QRIS
* Menampung semua data yang dibutuhkan untuk transaksi
*/
public class QrisTransaction {
private static final String TAG = "QrisTransaction";
// Transaction identifiers
private String orderId;
private String transactionId;
private String referenceId;
private String merchantId;
// Amount information
private int originalAmount;
private String grossAmount;
private String formattedAmount;
// QR Code information
private String qrImageUrl;
private String qrString;
private long qrCreationTime;
private int qrExpirationMinutes;
// Provider information
private String acquirer;
private String detectedProvider;
private String actualIssuer;
private String actualAcquirer;
// Transaction timing
private String transactionTime;
private long creationTimestamp;
// Status tracking
private String currentStatus;
private boolean paymentProcessed;
private boolean isQrRefreshTransaction;
private String currentQrTransactionId;
// Provider expiration mapping
private static final Map<String, Integer> PROVIDER_EXPIRATION_MAP = new HashMap<String, Integer>() {{
put("shopeepay", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("shopee", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("airpay shopee", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("gopay", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("dana", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("ovo", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("linkaja", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("link aja", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("jenius", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("qris", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
put("others", BuildConfig.DEFAULT_QR_EXPIRATION_MINUTES);
}};
// Provider display name mapping
private static final Map<String, String> ISSUER_DISPLAY_MAP = new HashMap<String, String>() {{
put("airpay shopee", "ShopeePay");
put("shopeepay", "ShopeePay");
put("shopee", "ShopeePay");
put("linkaja", "LinkAja");
put("link aja", "LinkAja");
put("dana", "DANA");
put("ovo", "OVO");
put("gopay", "GoPay");
put("jenius", "Jenius");
put("sakuku", "Sakuku");
put("bni", "BNI");
put("bca", "BCA");
put("mandiri", "Mandiri");
put("bri", "BRI");
put("cimb", "CIMB Niaga");
put("permata", "Permata");
put("maybank", "Maybank");
put("qris", "QRIS");
}};
// Constructor
public QrisTransaction() {
this.creationTimestamp = System.currentTimeMillis();
this.currentStatus = "pending";
this.paymentProcessed = false;
this.isQrRefreshTransaction = false;
}
// Initialization method
public void initialize(String orderId, String transactionId, int amount,
String qrImageUrl, String qrString, String acquirer) {
this.orderId = orderId;
this.transactionId = transactionId;
this.currentQrTransactionId = transactionId;
this.originalAmount = amount;
this.qrImageUrl = qrImageUrl;
this.qrString = qrString;
this.acquirer = acquirer;
// Detect provider and set expiration
this.detectedProvider = detectProviderFromData();
this.qrExpirationMinutes = PROVIDER_EXPIRATION_MAP.get(detectedProvider.toLowerCase());
this.qrCreationTime = System.currentTimeMillis();
// Format amount
this.formattedAmount = formatRupiahAmount(String.valueOf(amount));
}
/**
* Detect provider dari acquirer atau QR string
*/
private String detectProviderFromData() {
// Try to detect from acquirer first
if (acquirer != null && !acquirer.isEmpty()) {
String lowerAcquirer = acquirer.toLowerCase().trim();
if (PROVIDER_EXPIRATION_MAP.containsKey(lowerAcquirer)) {
return lowerAcquirer;
}
}
// Try to detect from QR string content
if (qrString != null && !qrString.isEmpty()) {
String lowerQrString = qrString.toLowerCase();
for (String provider : PROVIDER_EXPIRATION_MAP.keySet()) {
if (lowerQrString.contains(provider.toLowerCase())) {
return provider;
}
}
}
return "others";
}
/**
* Format amount ke format Rupiah
*/
private String formatRupiahAmount(String amount) {
try {
String cleanAmount = amount.replaceAll("[^0-9]", "");
long amountLong = Long.parseLong(cleanAmount);
return "RP." + String.format("%,d", amountLong).replace(',', '.');
} catch (NumberFormatException e) {
return "RP." + amount;
}
}
/**
* Check apakah QR sudah expired
*/
public boolean isQrExpired() {
long currentTime = System.currentTimeMillis();
long elapsedMinutes = (currentTime - qrCreationTime) / (1000 * 60);
boolean expired = elapsedMinutes >= qrExpirationMinutes;
Log.d(TAG, "QR expired check: " + elapsedMinutes + "/" + qrExpirationMinutes + " = " + expired);
return expired;
}
/**
* Get remaining time dalam detik
*/
public int getRemainingTimeInSeconds() {
long currentTime = System.currentTimeMillis();
long elapsedMs = currentTime - qrCreationTime;
long totalExpirationMs = qrExpirationMinutes * 60 * 1000;
long remainingMs = totalExpirationMs - elapsedMs;
return (int) Math.max(0, remainingMs / 1000);
}
/**
* Update QR code dengan data baru
*/
public void updateQrCode(String newQrUrl, String newQrString, String newTransactionId) {
this.qrImageUrl = newQrUrl;
this.qrString = newQrString;
this.qrCreationTime = System.currentTimeMillis();
if (newTransactionId != null && !newTransactionId.isEmpty()) {
this.currentQrTransactionId = newTransactionId;
this.isQrRefreshTransaction = true;
}
}
/**
* Get display name untuk provider
*/
public String getDisplayProviderName() {
String issuerToCheck = actualIssuer != null && !actualIssuer.isEmpty()
? actualIssuer : acquirer;
if (issuerToCheck == null || issuerToCheck.isEmpty()) {
return "QRIS";
}
String lowerName = issuerToCheck.toLowerCase().trim();
String displayName = ISSUER_DISPLAY_MAP.get(lowerName);
if (displayName != null) {
return displayName;
}
// Fallback: capitalize first letter
String[] words = issuerToCheck.split("\\s+");
StringBuilder result = new StringBuilder();
for (String word : words) {
if (word.length() > 0) {
result.append(Character.toUpperCase(word.charAt(0)))
.append(word.substring(1).toLowerCase())
.append(" ");
}
}
return result.toString().trim();
}
// Getters and Setters
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public String getTransactionId() { return transactionId; }
public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
public String getCurrentQrTransactionId() { return currentQrTransactionId; }
public void setCurrentQrTransactionId(String currentQrTransactionId) {
this.currentQrTransactionId = currentQrTransactionId;
}
public String getReferenceId() { return referenceId; }
public void setReferenceId(String referenceId) { this.referenceId = referenceId; }
public String getMerchantId() { return merchantId; }
public void setMerchantId(String merchantId) { this.merchantId = merchantId; }
public int getOriginalAmount() { return originalAmount; }
public void setOriginalAmount(int originalAmount) { this.originalAmount = originalAmount; }
public String getGrossAmount() { return grossAmount; }
public void setGrossAmount(String grossAmount) { this.grossAmount = grossAmount; }
public String getFormattedAmount() { return formattedAmount; }
public void setFormattedAmount(String formattedAmount) { this.formattedAmount = formattedAmount; }
public String getQrImageUrl() { return qrImageUrl; }
public void setQrImageUrl(String qrImageUrl) { this.qrImageUrl = qrImageUrl; }
public String getQrString() { return qrString; }
public void setQrString(String qrString) { this.qrString = qrString; }
public long getQrCreationTime() { return qrCreationTime; }
public void setQrCreationTime(long qrCreationTime) { this.qrCreationTime = qrCreationTime; }
public int getQrExpirationMinutes() { return qrExpirationMinutes; }
public void setQrExpirationMinutes(int qrExpirationMinutes) {
this.qrExpirationMinutes = qrExpirationMinutes;
}
public String getAcquirer() { return acquirer; }
public void setAcquirer(String acquirer) { this.acquirer = acquirer; }
public String getDetectedProvider() { return detectedProvider; }
public void setDetectedProvider(String detectedProvider) { this.detectedProvider = detectedProvider; }
public String getActualIssuer() { return actualIssuer; }
public void setActualIssuer(String actualIssuer) { this.actualIssuer = actualIssuer; }
public String getActualAcquirer() { return actualAcquirer; }
public void setActualAcquirer(String actualAcquirer) { this.actualAcquirer = actualAcquirer; }
public String getTransactionTime() { return transactionTime; }
public void setTransactionTime(String transactionTime) { this.transactionTime = transactionTime; }
public long getCreationTimestamp() { return creationTimestamp; }
public void setCreationTimestamp(long creationTimestamp) { this.creationTimestamp = creationTimestamp; }
public String getCurrentStatus() { return currentStatus; }
public void setCurrentStatus(String currentStatus) { this.currentStatus = currentStatus; }
public boolean isPaymentProcessed() { return paymentProcessed; }
public void setPaymentProcessed(boolean paymentProcessed) { this.paymentProcessed = paymentProcessed; }
public boolean isQrRefreshTransaction() { return isQrRefreshTransaction; }
public void setQrRefreshTransaction(boolean qrRefreshTransaction) {
this.isQrRefreshTransaction = qrRefreshTransaction;
}
}

View File

@@ -0,0 +1,411 @@
package com.example.bdkipoc.qris.network;
import com.example.bdkipoc.BuildConfig;
import android.util.Log;
import com.example.bdkipoc.qris.model.QrisRepository;
import com.example.bdkipoc.qris.model.QrisTransaction;
import com.example.bdkipoc.qris.model.QrisRepository.PaymentStatusResult;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* API Service untuk handling semua network calls terkait QRIS
* Mengabstraksi implementasi detail dari repository
*/
public class QrisApiService {
private static final String TAG = "QrisApiService";
// API Endpoints
private static final String MIDTRANS_SANDBOX_AUTH = BuildConfig.MIDTRANS_SANDBOX_AUTH;
private static final String MIDTRANS_PRODUCTION_AUTH = BuildConfig.MIDTRANS_PRODUCTION_AUTH;
private static final String MIDTRANS_AUTH = BuildConfig.MIDTRANS_SANDBOX_AUTH;
private static final String MIDTRANS_CHARGE_URL = BuildConfig.MIDTRANS_CHARGE_URL;
private static final String MIDTRANS_STATUS_BASE_URL = BuildConfig.MIDTRANS_STATUS_BASE_URL;
private String backendBase = BuildConfig.BACKEND_BASE_URL;
private String webhookUrl = BuildConfig.WEBHOOK_URL;
/**
* Generate new QR code via Midtrans API
*/
public QrisRepository.QrRefreshResult generateNewQrCode(QrisTransaction transaction) throws Exception {
Log.d(TAG, "🔧 Generating new QR code for: " + transaction.getOrderId());
// Generate unique order ID untuk QR refresh
String shortTimestamp = String.valueOf(System.currentTimeMillis()).substring(7);
String newOrderId = transaction.getOrderId().substring(0, Math.min(transaction.getOrderId().length(), 43)) + "-q" + shortTimestamp;
// Validate order ID length
if (newOrderId.length() > 50) {
newOrderId = transaction.getOrderId().substring(0, 36) + "-q" + shortTimestamp.substring(0, Math.min(shortTimestamp.length(), 7));
}
Log.d(TAG, "🆕 New QR Order ID: " + newOrderId);
// Create enhanced payload
JSONObject payload = createQrRefreshPayload(transaction, newOrderId);
// Make API call
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
conn.setRequestProperty("X-Override-Notification", webhookUrl);
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0 QR-Refresh-Enhanced");
conn.setRequestProperty("X-QR-Refresh", "true");
conn.setRequestProperty("X-Parent-Transaction", transaction.getTransactionId());
conn.setRequestProperty("X-Provider", transaction.getDetectedProvider());
conn.setRequestProperty("X-Expiration-Minutes", String.valueOf(transaction.getQrExpirationMinutes()));
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
Log.d(TAG, "📥 QR refresh response code: " + responseCode);
if (responseCode == 200 || responseCode == 201) {
String response = readResponse(conn.getInputStream());
return parseQrRefreshResponse(response);
} else {
String errorResponse = readResponse(conn.getErrorStream());
throw new Exception("QR refresh failed: HTTP " + responseCode + " - " + errorResponse);
}
}
/**
* Check transaction status via Midtrans API
*/
public PaymentStatusResult checkTransactionStatus(QrisTransaction transaction) throws Exception {
String monitoringTransactionId = transaction.getCurrentQrTransactionId();
String statusUrl = MIDTRANS_STATUS_BASE_URL + monitoringTransactionId + "/status";
Log.d(TAG, "🔍 Checking status for: " + monitoringTransactionId);
URL url = new URI(statusUrl).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
String response = readResponse(conn.getInputStream());
PaymentStatusResult result = parseStatusResponse(response, transaction);
JSONObject statusResponse = new JSONObject(response);
result.transactionId = statusResponse.optString("transaction_id", monitoringTransactionId);
return result;
} else {
throw new Exception("Status check failed: HTTP " + responseCode);
}
}
/**
* Simulate webhook payment
*/
public boolean simulateWebhook(QrisTransaction transaction) throws Exception {
Log.d(TAG, "🚀 Simulating webhook for: " + transaction.getOrderId());
String serverKey = getServerKey();
String signatureKey = generateSignature(
transaction.getOrderId(),
"200",
transaction.getGrossAmount() != null ? transaction.getGrossAmount() : String.valueOf(transaction.getOriginalAmount()),
serverKey
);
JSONObject payload = createWebhookPayload(transaction, signatureKey);
URL url = new URL(webhookUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0-Enhanced-Simulation");
conn.setRequestProperty("X-Simulation", "true");
conn.setRequestProperty("X-Provider", transaction.getDetectedProvider());
conn.setDoOutput(true);
conn.setConnectTimeout(1000);
conn.setReadTimeout(1000);
try (OutputStream os = conn.getOutputStream()) {
os.write(payload.toString().getBytes("utf-8"));
}
int responseCode = conn.getResponseCode();
Log.d(TAG, "📥 Webhook simulation response: " + responseCode);
if (responseCode == 200 || responseCode == 201) {
String response = readResponse(conn.getInputStream());
Log.d(TAG, "✅ Webhook simulation successful");
return true;
} else {
String errorResponse = readResponse(conn.getErrorStream());
throw new Exception("Webhook simulation failed: HTTP " + responseCode + " - " + errorResponse);
}
}
/**
* Poll for pending payment logs
*/
public QrisRepository.PaymentLogResult pollPendingPaymentLog(String orderId) throws Exception {
Log.d(TAG, "📊 Polling payment logs for: " + orderId);
String urlStr = backendBase + "/api-logs?request_body_search_strict={\"order_id\":\"" + orderId + "\"}";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0-Enhanced");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
String response = readResponse(conn.getInputStream());
return parsePaymentLogResponse(response, orderId);
} else {
throw new Exception("Payment log polling failed: HTTP " + responseCode);
}
}
/**
* Helper methods
*/
private JSONObject createQrRefreshPayload(QrisTransaction transaction, String newOrderId) throws Exception {
JSONObject customField1 = new JSONObject();
customField1.put("parent_transaction_id", transaction.getTransactionId());
customField1.put("parent_order_id", transaction.getOrderId());
customField1.put("parent_reference_id", transaction.getReferenceId());
customField1.put("qr_refresh_time", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()).format(new Date()));
customField1.put("qr_refresh_count", System.currentTimeMillis());
customField1.put("is_qr_refresh", true);
customField1.put("detected_provider", transaction.getDetectedProvider());
customField1.put("expiration_minutes", transaction.getQrExpirationMinutes());
JSONObject payload = new JSONObject();
payload.put("payment_type", "qris");
JSONObject transactionDetails = new JSONObject();
transactionDetails.put("order_id", newOrderId);
transactionDetails.put("gross_amount", transaction.getOriginalAmount());
payload.put("transaction_details", transactionDetails);
JSONObject customerDetails = new JSONObject();
customerDetails.put("first_name", "Test");
customerDetails.put("last_name", "Customer");
customerDetails.put("email", "test@example.com");
customerDetails.put("phone", "081234567890");
payload.put("customer_details", customerDetails);
JSONArray itemDetails = new JSONArray();
JSONObject item = new JSONObject();
item.put("id", "item1_qr_refresh_" + System.currentTimeMillis());
item.put("price", transaction.getOriginalAmount());
item.put("quantity", 1);
item.put("name", "QRIS Payment QR Refresh - " + new SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(new Date()) +
" (" + transaction.getDetectedProvider().toUpperCase() + " - " + transaction.getQrExpirationMinutes() + "min)");
itemDetails.put(item);
payload.put("item_details", itemDetails);
payload.put("custom_field1", customField1.toString());
JSONObject qrisDetails = new JSONObject();
qrisDetails.put("acquirer", "gopay");
qrisDetails.put("qr_refresh", true);
qrisDetails.put("parent_transaction_id", transaction.getTransactionId());
qrisDetails.put("refresh_timestamp", System.currentTimeMillis());
qrisDetails.put("provider", transaction.getDetectedProvider());
qrisDetails.put("expiration_minutes", transaction.getQrExpirationMinutes());
payload.put("qris", qrisDetails);
return payload;
}
private JSONObject createWebhookPayload(QrisTransaction transaction, String signatureKey) throws Exception {
JSONObject payload = new JSONObject();
payload.put("transaction_type", "on-us");
payload.put("transaction_time", transaction.getTransactionTime() != null ? transaction.getTransactionTime() : getCurrentISOTime());
payload.put("transaction_status", "settlement");
payload.put("transaction_id", transaction.getCurrentQrTransactionId());
payload.put("status_message", "midtrans payment notification");
payload.put("status_code", "200");
payload.put("signature_key", signatureKey);
payload.put("settlement_time", getCurrentISOTime());
payload.put("payment_type", "qris");
payload.put("order_id", transaction.getOrderId());
payload.put("merchant_id", transaction.getMerchantId() != null ? transaction.getMerchantId() : "G616299250");
payload.put("issuer", transaction.getActualIssuer() != null ? transaction.getActualIssuer() : transaction.getAcquirer());
payload.put("gross_amount", transaction.getGrossAmount() != null ? transaction.getGrossAmount() : String.valueOf(transaction.getOriginalAmount()));
payload.put("fraud_status", "accept");
payload.put("currency", "IDR");
payload.put("acquirer", transaction.getActualAcquirer() != null ? transaction.getActualAcquirer() : transaction.getAcquirer());
payload.put("shopeepay_reference_number", "");
payload.put("reference_id", transaction.getReferenceId() != null ? transaction.getReferenceId() : "DUMMY_REFERENCE_ID");
// Enhanced fields
payload.put("detected_provider", transaction.getDetectedProvider());
payload.put("qr_expiration_minutes", transaction.getQrExpirationMinutes());
payload.put("is_simulation", true);
payload.put("simulation_type", "enhanced_manual");
if (transaction.getQrString() != null && !transaction.getQrString().isEmpty()) {
payload.put("qr_string", transaction.getQrString());
}
return payload;
}
private QrisRepository.QrRefreshResult parseQrRefreshResponse(String response) throws Exception {
JSONObject jsonResponse = new JSONObject(response);
if (jsonResponse.has("status_code")) {
String statusCode = jsonResponse.getString("status_code");
if (!statusCode.equals("201")) {
String statusMessage = jsonResponse.optString("status_message", "Unknown error");
throw new Exception("QR refresh failed: " + statusCode + " - " + statusMessage);
}
}
String newQrUrl = null;
String newQrString = null;
String newTransactionId = jsonResponse.optString("transaction_id", "");
// Get QR URL from actions
if (jsonResponse.has("actions")) {
JSONArray actionsArray = jsonResponse.getJSONArray("actions");
if (actionsArray.length() > 0) {
JSONObject actions = actionsArray.getJSONObject(0);
newQrUrl = actions.getString("url");
}
}
// Get QR String
if (jsonResponse.has("qr_string")) {
newQrString = jsonResponse.getString("qr_string");
}
return new QrisRepository.QrRefreshResult(newQrUrl, newQrString, newTransactionId);
}
private QrisRepository.PaymentStatusResult parseStatusResponse(String response, QrisTransaction transaction) throws Exception {
JSONObject statusResponse = new JSONObject(response);
String transactionStatus = statusResponse.optString("transaction_status", "");
String paymentType = statusResponse.optString("payment_type", "");
String actualIssuer = statusResponse.optString("issuer", "");
String actualAcquirer = statusResponse.optString("acquirer", "");
String qrStringFromStatus = statusResponse.optString("qr_string", "");
QrisRepository.PaymentStatusResult result = new QrisRepository.PaymentStatusResult(transactionStatus);
result.paymentType = paymentType;
result.issuer = actualIssuer;
result.acquirer = actualAcquirer;
result.qrString = qrStringFromStatus;
result.statusChanged = !transactionStatus.equals(transaction.getCurrentStatus());
return result;
}
private QrisRepository.PaymentLogResult parsePaymentLogResponse(String response, String orderId) throws Exception {
JSONObject json = new JSONObject(response);
JSONArray results = json.optJSONArray("results");
if (results != null && results.length() > 0) {
for (int i = 0; i < results.length(); i++) {
JSONObject log = results.getJSONObject(i);
JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null) {
String transactionStatus = reqBody.optString("transaction_status");
String logOrderId = reqBody.optString("order_id");
if (orderId.equals(logOrderId) &&
(transactionStatus.equals("pending") ||
transactionStatus.equals("settlement") ||
transactionStatus.equals("capture") ||
transactionStatus.equals("success"))) {
return new QrisRepository.PaymentLogResult(true, transactionStatus, logOrderId);
}
}
}
}
return new QrisRepository.PaymentLogResult(false, "", orderId);
}
private String readResponse(InputStream inputStream) throws Exception {
if (inputStream == null) return "";
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
return response.toString();
}
private String getServerKey() {
try {
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT);
String decodedString = new String(decoded);
return decodedString.replace(":", "");
} catch (Exception e) {
Log.e(TAG, "Error decoding server key: " + e.getMessage());
return "";
}
}
private String generateSignature(String orderId, String statusCode, String grossAmount, String serverKey) {
String input = orderId + statusCode + grossAmount + serverKey;
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] messageDigest = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
Log.e(TAG, "Error generating signature: " + e.getMessage());
return "dummy_signature";
}
}
private String getCurrentISOTime() {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
.format(new Date());
}
}

View File

@@ -0,0 +1,544 @@
package com.example.bdkipoc.qris.presenter;
import com.example.bdkipoc.BuildConfig;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.example.bdkipoc.qris.model.QrisRepository;
import com.example.bdkipoc.qris.model.QrisTransaction;
import com.example.bdkipoc.qris.view.QrisResultContract;
/**
* Presenter untuk QrisResult module
* Menghandle semua business logic dan koordinasi antara Model dan View
*/
public class QrisResultPresenter implements QrisResultContract.Presenter {
private static final String TAG = "QrisResultPresenter";
private QrisResultContract.View view;
private QrisRepository repository;
private QrisTransaction transaction;
// Handlers untuk background tasks
private Handler timerHandler;
private Handler qrRefreshHandler;
private Handler paymentMonitorHandler;
// Runnables untuk periodic tasks
private Runnable timerRunnable;
private Runnable qrRefreshRunnable;
private Runnable paymentMonitorRunnable;
// State management
private boolean isTimerActive = false;
private boolean isQrRefreshActive = false;
private boolean isPaymentMonitorActive = false;
private String lastKnownStatus = "pending";
private int refreshCounter = 0;
private static final int MAX_REFRESH_ATTEMPTS = BuildConfig.MAX_REFRESH_ATTEMPTS;
public QrisResultPresenter() {
this.repository = QrisRepository.getInstance();
this.transaction = new QrisTransaction();
// Initialize handlers
this.timerHandler = new Handler(Looper.getMainLooper());
this.qrRefreshHandler = new Handler(Looper.getMainLooper());
this.paymentMonitorHandler = new Handler(Looper.getMainLooper());
}
@Override
public void attachView(QrisResultContract.View view) {
this.view = view;
Log.d(TAG, "📎 View attached to presenter");
}
@Override
public void detachView() {
this.view = null;
Log.d(TAG, "📎 View detached from presenter");
}
@Override
public void onDestroy() {
stopAllTimers();
detachView();
Log.d(TAG, "💀 Presenter destroyed");
}
@Override
public void initializeTransaction(String orderId, String transactionId, String amount,
String qrImageUrl, String qrString, String acquirer) {
Log.d(TAG, "🚀 Initializing transaction");
try {
int amountInt = Integer.parseInt(amount);
transaction.initialize(orderId, transactionId, amountInt, qrImageUrl, qrString, acquirer);
if (view != null) {
view.showAmount(transaction.getFormattedAmount());
view.showQrImage(transaction.getQrImageUrl());
view.showProviderName(transaction.getDisplayProviderName());
view.showStatus("Waiting for payment...");
}
Log.d(TAG, "✅ Transaction initialized successfully");
Log.d(TAG, " Provider: " + transaction.getDetectedProvider());
Log.d(TAG, " Expiration: " + transaction.getQrExpirationMinutes() + " minutes");
} catch (Exception e) {
Log.e(TAG, "❌ Failed to initialize transaction: " + e.getMessage(), e);
if (view != null) {
view.showError("Failed to initialize transaction: " + e.getMessage());
}
}
}
@Override
public void startQrManagement() {
Log.d(TAG, "🔄 Starting QR management");
isQrRefreshActive = true;
startTimer();
startQrRefreshMonitoring();
}
@Override
public void stopQrManagement() {
Log.d(TAG, "🛑 Stopping QR management");
isQrRefreshActive = false;
stopTimer();
if (qrRefreshHandler != null && qrRefreshRunnable != null) {
qrRefreshHandler.removeCallbacks(qrRefreshRunnable);
}
}
@Override
public void startTimer() {
if (isTimerActive) {
stopTimer();
}
Log.d(TAG, "⏰ Starting timer");
isTimerActive = true;
// Reset creation time
transaction.setQrCreationTime(System.currentTimeMillis());
timerRunnable = new Runnable() {
@Override
public void run() {
if (!isTimerActive || transaction.isPaymentProcessed()) {
return;
}
int remainingSeconds = transaction.getRemainingTimeInSeconds();
if (remainingSeconds > 0) {
// Update UI di main thread
new Handler(Looper.getMainLooper()).post(() -> {
if (view != null) {
int displayMinutes = remainingSeconds / 60;
int displaySeconds = remainingSeconds % 60;
String timeDisplay = String.format("%d:%02d", displayMinutes, displaySeconds);
view.showTimer(timeDisplay);
}
});
// Schedule next update
timerHandler.postDelayed(this, 1000);
} else {
// Timer expired
Log.w(TAG, "⏰ Timer expired");
isTimerActive = false;
onQrExpired();
}
}
};
timerHandler.post(timerRunnable);
}
@Override
public void stopTimer() {
Log.d(TAG, "⏰ Stopping timer");
isTimerActive = false;
if (timerHandler != null && timerRunnable != null) {
timerHandler.removeCallbacks(timerRunnable);
}
}
@Override
public void refreshQrCode() {
Log.d(TAG, "🔄 Refreshing QR code - Attempt " + refreshCounter);
// Pastikan di Main Thread untuk UI updates
new Handler(Looper.getMainLooper()).post(() -> {
if (view != null) {
view.showQrRefreshing();
view.showLoading();
}
});
repository.refreshQrCode(transaction, new QrisRepository.RepositoryCallback<QrisRepository.QrRefreshResult>() {
@Override
public void onSuccess(QrisRepository.QrRefreshResult result) {
Log.d(TAG, "✅ QR refresh successful");
// Update transaction data
transaction.updateQrCode(result.qrUrl, result.qrString, result.transactionId);
transaction.setQrCreationTime(System.currentTimeMillis()); // Reset creation time
// Pastikan di Main Thread untuk UI updates
new Handler(Looper.getMainLooper()).post(() -> {
if (view != null) {
view.hideLoading();
view.updateQrImage(result.qrUrl);
view.updateQrUrl(result.qrUrl);
view.showQrRefreshSuccess();
view.showToast("QR Code berhasil diperbarui!");
}
// Stop dan restart timer dengan benar
stopTimer();
startTimer();
// Restart monitoring
isQrRefreshActive = true;
startQrRefreshMonitoring();
});
}
@Override
public void onError(String errorMessage) {
Log.e(TAG, "❌ QR refresh failed: " + errorMessage);
new Handler(Looper.getMainLooper()).post(() -> {
if (view != null) {
view.hideLoading();
view.showQrRefreshFailed(errorMessage);
if (refreshCounter >= MAX_REFRESH_ATTEMPTS) {
view.navigateToMain();
}
}
});
}
});
}
@Override
public void onQrExpired() {
Log.w(TAG, "⏰ Handling QR expiration");
// Stop current timers to prevent race conditions
stopTimer();
if (view != null) {
view.showQrExpired();
}
// Cek apakah sudah mencapai limit refresh
if (refreshCounter >= MAX_REFRESH_ATTEMPTS) {
Log.w(TAG, "🛑 Maximum refresh attempts reached");
if (view != null) {
view.showToast("Maksimum percobaan refresh QR tercapai");
view.navigateToMain();
}
return;
}
// Increment counter
refreshCounter++;
Log.d(TAG, "🔄 Refresh attempt #" + refreshCounter);
// Auto-refresh tanpa delay
refreshQrCode();
}
private void startQrRefreshMonitoring() {
Log.d(TAG, "🔄 Starting QR refresh monitoring");
qrRefreshRunnable = new Runnable() {
@Override
public void run() {
if (!isQrRefreshActive || transaction.isPaymentProcessed()) {
return;
}
// Check if QR expired
if (transaction.isQrExpired()) {
Log.w(TAG, "⏰ QR Code expired during monitoring");
onQrExpired();
return;
}
// Schedule next check in 30 seconds
if (isQrRefreshActive) {
qrRefreshHandler.postDelayed(this, 30000);
}
}
};
qrRefreshHandler.post(qrRefreshRunnable);
}
@Override
public void startPaymentMonitoring() {
Log.d(TAG, "🔍 Starting payment monitoring");
isPaymentMonitorActive = true;
paymentMonitorRunnable = new Runnable() {
@Override
public void run() {
if (!isPaymentMonitorActive || transaction.isPaymentProcessed()) {
return;
}
checkPaymentStatus();
// Schedule next check in 3 seconds
if (isPaymentMonitorActive && !transaction.isPaymentProcessed()) {
paymentMonitorHandler.postDelayed(this, 3000);
}
}
};
paymentMonitorHandler.post(paymentMonitorRunnable);
}
@Override
public void stopPaymentMonitoring() {
Log.d(TAG, "🔍 Stopping payment monitoring");
isPaymentMonitorActive = false;
if (paymentMonitorHandler != null && paymentMonitorRunnable != null) {
paymentMonitorHandler.removeCallbacks(paymentMonitorRunnable);
}
}
@Override
public void checkPaymentStatus() {
repository.checkPaymentStatus(transaction, new QrisRepository.RepositoryCallback<QrisRepository.PaymentStatusResult>() {
@Override
public void onSuccess(QrisRepository.PaymentStatusResult result) {
handlePaymentStatusResult(result);
}
@Override
public void onError(String errorMessage) {
Log.w(TAG, "⚠️ Payment status check failed: " + errorMessage);
// Don't show error to user untuk status check failures
}
});
}
private void handlePaymentStatusResult(QrisRepository.PaymentStatusResult result) {
Log.d(TAG, "💳 Payment status result: " + result.status);
// Update transaction dengan actual issuer/acquirer
if (result.issuer != null && !result.issuer.isEmpty()) {
transaction.setActualIssuer(result.issuer);
}
if (result.acquirer != null && !result.acquirer.isEmpty()) {
transaction.setActualAcquirer(result.acquirer);
}
// Update QR string jika ada
if (result.qrString != null && !result.qrString.isEmpty()) {
transaction.setQrString(result.qrString);
}
// Handle status changes
if (!result.status.equals(lastKnownStatus)) {
Log.d(TAG, "📊 Status changed: " + lastKnownStatus + " -> " + result.status);
lastKnownStatus = result.status;
transaction.setCurrentStatus(result.status);
if (view != null) {
switch (result.status) {
case "settlement":
case "capture":
case "success":
if (!transaction.isPaymentProcessed()) {
handlePaymentSuccess();
}
break;
case "expire":
case "cancel":
view.showPaymentFailed("Payment " + result.status);
stopAllTimers();
break;
case "pending":
view.showPaymentPending();
break;
default:
view.showStatus("Status: " + result.status);
break;
}
}
}
}
private void handlePaymentSuccess() {
Log.d(TAG, "🎉 Payment successful!");
transaction.setPaymentProcessed(true);
stopAllTimers();
if (view != null) {
String providerName = transaction.getDisplayProviderName();
view.showPaymentSuccess(providerName);
view.startSuccessAnimation();
view.showToast("Pembayaran " + providerName + " berhasil! 🎉");
}
// Don't auto-navigate here - let the view handle the navigation timing
}
@Override
public void onCancelClicked() {
Log.d(TAG, "❌ Cancel clicked");
stopAllTimers();
if (view != null) {
view.finishActivity();
}
}
@Override
public void onBackPressed() {
Log.d(TAG, "⬅️ Back pressed");
stopAllTimers();
if (view != null) {
view.finishActivity();
}
}
@Override
public void onSimulatePayment() {
Log.d(TAG, "🚀 Simulating payment");
if (transaction.isPaymentProcessed()) {
Log.w(TAG, "⚠️ Payment already processed");
if (view != null) {
view.showToast("Pembayaran sudah diproses");
}
return;
}
stopAllTimers();
if (view != null) {
view.showToast("Mensimulasikan pembayaran...");
view.showLoading();
}
repository.simulatePayment(transaction, new QrisRepository.RepositoryCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
Log.d(TAG, "✅ Payment simulation successful");
if (view != null) {
view.hideLoading();
}
// Wait a bit then trigger success
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!transaction.isPaymentProcessed()) {
handlePaymentSuccess();
}
}, 2000);
}
@Override
public void onError(String errorMessage) {
Log.e(TAG, "❌ Payment simulation failed: " + errorMessage);
if (view != null) {
view.hideLoading();
view.showError("Simulasi gagal: " + errorMessage);
}
// Restart monitoring after simulation failure
startPaymentMonitoring();
}
});
}
/**
* Stop all timers dan background tasks
*/
private void stopAllTimers() {
Log.d(TAG, "🛑 Stopping all timers");
stopTimer();
stopQrManagement();
stopPaymentMonitoring();
// Clear all pending callbacks
if (timerHandler != null) {
timerHandler.removeCallbacksAndMessages(null);
}
if (qrRefreshHandler != null) {
qrRefreshHandler.removeCallbacksAndMessages(null);
}
if (paymentMonitorHandler != null) {
paymentMonitorHandler.removeCallbacksAndMessages(null);
}
}
/**
* Public method untuk start semua monitoring
*/
public void startAllMonitoring() {
Log.d(TAG, "🚀 Starting all monitoring");
startQrManagement();
startPaymentMonitoring();
// Start polling untuk payment logs
repository.pollPaymentLogs(transaction.getOrderId(), new QrisRepository.RepositoryCallback<QrisRepository.PaymentLogResult>() {
@Override
public void onSuccess(QrisRepository.PaymentLogResult result) {
if (result.found) {
Log.d(TAG, "📊 Payment log found with status: " + result.status);
if ("settlement".equals(result.status) ||
"capture".equals(result.status) ||
"success".equals(result.status)) {
if (!transaction.isPaymentProcessed()) {
handlePaymentSuccess();
}
}
if (view != null) {
view.showToast("Payment log found!");
}
}
}
@Override
public void onError(String errorMessage) {
Log.w(TAG, "⚠️ Payment log polling failed: " + errorMessage);
// Don't show error to user
}
});
}
// Getter untuk transaction (untuk testing atau debugging)
public QrisTransaction getTransaction() {
return transaction;
}
}

View File

@@ -0,0 +1,162 @@
package com.example.bdkipoc.qris.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
/**
* Utility class untuk loading QR images secara asynchronous
* Dengan error handling dan validation yang proper
*/
public class QrImageLoader {
private static final String TAG = "QrImageLoader";
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1PM2t1bXkwVDl4M1VvYnVvVTc3NW5QbXc=";
/**
* Interface untuk callback hasil loading image
*/
public interface ImageLoadCallback {
void onImageLoaded(Bitmap bitmap);
void onImageLoadFailed(String errorMessage);
}
/**
* Load QR image dari URL dengan callback
*/
public static void loadQrImage(String qrImageUrl, ImageLoadCallback callback) {
if (qrImageUrl == null || qrImageUrl.isEmpty()) {
Log.w(TAG, "⚠️ QR image URL is empty");
callback.onImageLoadFailed("QR image URL is empty");
return;
}
if (!qrImageUrl.startsWith("http")) {
Log.e(TAG, "❌ Invalid QR URL format: " + qrImageUrl);
callback.onImageLoadFailed("Invalid QR code URL format");
return;
}
Log.d(TAG, "🖼️ Loading QR image from: " + qrImageUrl);
new EnhancedDownloadImageTask(callback).execute(qrImageUrl);
}
/**
* Load QR image langsung ke ImageView (legacy support)
*/
public static void loadQrImageToView(String qrImageUrl, ImageView imageView) {
loadQrImage(qrImageUrl, new ImageLoadCallback() {
@Override
public void onImageLoaded(Bitmap bitmap) {
if (imageView != null) {
imageView.setImageBitmap(bitmap);
Log.d(TAG, "✅ QR code image displayed successfully");
}
}
@Override
public void onImageLoadFailed(String errorMessage) {
Log.e(TAG, "❌ Failed to display QR code image: " + errorMessage);
if (imageView != null) {
imageView.setImageResource(android.R.drawable.ic_menu_report_image);
if (imageView.getContext() != null) {
Toast.makeText(imageView.getContext(), "QR Error: " + errorMessage, Toast.LENGTH_LONG).show();
}
}
}
});
}
/**
* Enhanced AsyncTask untuk download image dengan proper error handling
*/
private static class EnhancedDownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageLoadCallback callback;
private String errorMessage;
EnhancedDownloadImageTask(ImageLoadCallback callback) {
this.callback = callback;
}
@Override
protected Bitmap doInBackground(String... urls) {
String urlDisplay = urls[0];
Bitmap bitmap = null;
try {
if (urlDisplay == null || urlDisplay.isEmpty()) {
Log.e(TAG, "❌ Empty QR URL provided");
errorMessage = "QR URL is empty";
return null;
}
if (!urlDisplay.startsWith("http")) {
Log.e(TAG, "❌ Invalid QR URL format: " + urlDisplay);
errorMessage = "Invalid QR URL format";
return null;
}
Log.d(TAG, "📥 Downloading image from: " + urlDisplay);
URL url = new URI(urlDisplay).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setConnectTimeout(1000);
connection.setReadTimeout(1000);
connection.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
connection.setRequestProperty("Accept", "image/*");
// Add auth header untuk Midtrans URLs
if (urlDisplay.contains("midtrans.com")) {
connection.setRequestProperty("Authorization", MIDTRANS_AUTH);
}
connection.connect();
int responseCode = connection.getResponseCode();
Log.d(TAG, "📥 Image download response code: " + responseCode);
if (responseCode == 200) {
InputStream input = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(input);
if (bitmap != null) {
Log.d(TAG, "✅ Image downloaded successfully. Size: " +
bitmap.getWidth() + "x" + bitmap.getHeight());
} else {
Log.e(TAG, "❌ Failed to decode bitmap from stream");
errorMessage = "Failed to decode QR code image";
}
} else {
Log.e(TAG, "❌ Failed to download image. HTTP code: " + responseCode);
errorMessage = "Failed to download QR code (HTTP " + responseCode + ")";
}
} catch (Exception e) {
Log.e(TAG, "❌ Exception downloading image: " + e.getMessage(), e);
errorMessage = "Error downloading QR code: " + e.getMessage();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
if (callback != null) {
if (result != null) {
callback.onImageLoaded(result);
} else {
callback.onImageLoadFailed(errorMessage != null ? errorMessage : "Unknown error");
}
}
}
}
}

View File

@@ -0,0 +1,757 @@
package com.example.bdkipoc.qris.view;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import com.example.bdkipoc.R;
import com.example.bdkipoc.ReceiptActivity;
import com.example.bdkipoc.qris.model.QrisTransaction;
import com.example.bdkipoc.qris.presenter.QrisResultPresenter;
import com.example.bdkipoc.qris.utils.QrImageLoader;
/**
* QrisResultActivity - refactored menggunakan MVP pattern
* Hanya menghandle UI logic, business logic ada di Presenter
*/
public class QrisResultActivity extends AppCompatActivity implements QrisResultContract.View {
private static final String TAG = "QrisResultActivity";
// Presenter
private QrisResultPresenter presenter;
// Main UI Components
private ImageView qrImageView;
private TextView amountTextView;
private TextView timerTextView;
private Button cancelButton;
private TextView qrisLogo;
private CardView mainCard;
private View headerBackground;
private View backNavigation;
// Hidden components for functionality
private TextView referenceTextView;
private TextView statusTextView;
private TextView qrStatusTextView;
private ProgressBar progressBar;
private Button downloadQrisButton;
private Button checkStatusButton;
private Button returnMainButton;
// Success screen views
private View successScreen;
private ImageView successIcon;
private TextView successMessage;
private TextView qrUrlTextView;
private Button simulatorButton;
// Animation handler
private Handler animationHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qris_result);
Log.d(TAG, "=== QRIS RESULT ACTIVITY STARTED (MVP) ===");
// Initialize presenter
presenter = new QrisResultPresenter();
presenter.attachView(this);
// Initialize views
initializeViews();
// Setup UI components
setupUI();
setupClickListeners();
// Get intent data dan initialize transaction
initializeFromIntent();
// Start monitoring
presenter.startAllMonitoring();
}
/**
* Initialize all view components
*/
private void initializeViews() {
// Main visible components
qrImageView = findViewById(R.id.qrImageView);
amountTextView = findViewById(R.id.amountTextView);
timerTextView = findViewById(R.id.timerTextView);
cancelButton = findViewById(R.id.cancel_button);
qrisLogo = findViewById(R.id.qris_logo);
mainCard = findViewById(R.id.main_card);
headerBackground = findViewById(R.id.header_background);
backNavigation = findViewById(R.id.back_navigation);
// Hidden components for functionality
referenceTextView = findViewById(R.id.referenceTextView);
statusTextView = findViewById(R.id.statusTextView);
qrStatusTextView = findViewById(R.id.qrStatusTextView);
progressBar = findViewById(R.id.progressBar);
downloadQrisButton = findViewById(R.id.downloadQrisButton);
checkStatusButton = findViewById(R.id.checkStatusButton);
returnMainButton = findViewById(R.id.returnMainButton);
// Success screen views
successScreen = findViewById(R.id.success_screen);
successIcon = findViewById(R.id.success_icon);
successMessage = findViewById(R.id.success_message);
qrUrlTextView = findViewById(R.id.qrUrlTextView);
simulatorButton = findViewById(R.id.simulatorButton);
}
/**
* Setup basic UI components
*/
private void setupUI() {
// Hide success screen initially
if (successScreen != null) {
successScreen.setVisibility(View.GONE);
}
// Disable check status button initially
if (checkStatusButton != null) {
checkStatusButton.setEnabled(false);
}
// Setup URL copy functionality
setupUrlCopyFunctionality();
// Setup simulator button
setupSimulatorButton();
}
/**
* Get data dari intent dan initialize transaction
*/
private void initializeFromIntent() {
Intent intent = getIntent();
String orderId = intent.getStringExtra("orderId");
String transactionId = intent.getStringExtra("transactionId");
String amount = String.valueOf(intent.getIntExtra("amount", 0));
String qrImageUrl = intent.getStringExtra("qrImageUrl");
String qrString = intent.getStringExtra("qrString");
String acquirer = intent.getStringExtra("acquirer");
Log.d(TAG, "Initializing transaction with data:");
Log.d(TAG, " Order ID: " + orderId);
Log.d(TAG, " Transaction ID: " + transactionId);
Log.d(TAG, " Amount: " + amount);
Log.d(TAG, " QR URL: " + qrImageUrl);
Log.d(TAG, " Acquirer: " + acquirer);
// Validate required data
if (orderId == null || transactionId == null) {
Log.e(TAG, "❌ Critical error: orderId or transactionId is null!");
showError("Missing transaction details! Cannot proceed.");
finish();
return;
}
// Initialize via presenter
presenter.initializeTransaction(orderId, transactionId, amount, qrImageUrl, qrString, acquirer);
// Set additional data
if (referenceTextView != null) {
String referenceId = intent.getStringExtra("referenceId");
referenceTextView.setText("Reference ID: " + referenceId);
}
}
/**
* Setup click listeners untuk semua buttons dan views
*/
private void setupClickListeners() {
// Cancel button
if (cancelButton != null) {
cancelButton.setOnClickListener(v -> {
addClickAnimation(v);
presenter.onCancelClicked();
});
}
// Back navigation
if (backNavigation != null) {
backNavigation.setOnClickListener(v -> {
addClickAnimation(v);
presenter.onBackPressed();
});
}
// Hidden check status button untuk testing
if (checkStatusButton != null) {
checkStatusButton.setOnClickListener(v -> {
Log.d(TAG, "Manual payment simulation triggered");
presenter.onSimulatePayment();
});
}
// Hidden return main button
if (returnMainButton != null) {
returnMainButton.setOnClickListener(v -> {
navigateToMain();
});
}
// Double tap pada QR logo untuk testing
if (qrisLogo != null) {
qrisLogo.setOnClickListener(new View.OnClickListener() {
private int clickCount = 0;
private Handler handler = new Handler();
private final int DOUBLE_TAP_TIMEOUT = 300;
@Override
public void onClick(View v) {
clickCount++;
if (clickCount == 1) {
handler.postDelayed(() -> clickCount = 0, DOUBLE_TAP_TIMEOUT);
} else if (clickCount == 2) {
// Double tap detected - simulate payment
clickCount = 0;
Log.d(TAG, "Double tap detected - simulating payment");
presenter.onSimulatePayment();
}
}
});
}
}
private void setupUrlCopyFunctionality() {
if (qrUrlTextView != null) {
qrUrlTextView.setOnClickListener(v -> {
if (presenter.getTransaction() != null) {
String qrUrl = presenter.getTransaction().getQrImageUrl();
if (qrUrl != null) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("QR URL", qrUrl);
clipboard.setPrimaryClip(clip);
showToast("URL copied to clipboard");
}
}
});
}
}
private void setupSimulatorButton() {
if (simulatorButton != null) {
simulatorButton.setOnClickListener(v -> {
try {
String simulatorUrl = "https://simulator.sandbox.midtrans.com/v2/qris/index";
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(simulatorUrl));
startActivity(browserIntent);
} catch (Exception e) {
showToast("Could not open browser");
Log.e(TAG, "Error opening simulator URL", e);
}
});
}
}
// ========================================================================================
// MVP CONTRACT VIEW IMPLEMENTATIONS
// ========================================================================================
@Override
public void showQrImage(String qrImageUrl) {
Log.d(TAG, "🖼️ Showing QR image: " + qrImageUrl);
if (qrImageUrl != null && !qrImageUrl.isEmpty()) {
QrImageLoader.loadQrImage(qrImageUrl, new QrImageLoader.ImageLoadCallback() {
@Override
public void onImageLoaded(Bitmap bitmap) {
if (qrImageView != null) {
qrImageView.setImageBitmap(bitmap);
qrImageView.setAlpha(1.0f); // Ensure fully visible
}
}
@Override
public void onImageLoadFailed(String errorMessage) {
Log.e(TAG, "❌ Failed to load QR image: " + errorMessage);
if (qrImageView != null) {
qrImageView.setVisibility(View.GONE);
}
showError("Failed to load QR code: " + errorMessage);
}
});
// Update URL display
if (qrUrlTextView != null) {
qrUrlTextView.setText(qrImageUrl);
}
} else {
Log.w(TAG, "⚠️ QR image URL is not available");
if (qrImageView != null) {
qrImageView.setVisibility(View.GONE);
}
showToast("QR code URL not available");
}
}
@Override
public void showAmount(String formattedAmount) {
Log.d(TAG, "💰 Showing amount: " + formattedAmount);
if (amountTextView != null) {
amountTextView.setText(formattedAmount);
}
}
@Override
public void showTimer(String timeDisplay) {
if (timerTextView != null) {
timerTextView.setText(timeDisplay);
timerTextView.setTextColor(getResources().getColor(android.R.color.black));
}
}
@Override
public void showStatus(String status) {
Log.d(TAG, "📊 Showing status: " + status);
if (statusTextView != null) {
statusTextView.setText(status);
}
}
@Override
public void showProviderName(String providerName) {
Log.d(TAG, "🏷️ Showing provider: " + providerName);
// Provider name bisa ditampilkan di UI jika ada komponen khusus
}
@Override
public void updateQrImage(String newQrImageUrl) {
Log.d(TAG, "🔄 Updating QR image: " + newQrImageUrl);
runOnUiThread(() -> {
// Reset QR image appearance first
if (qrImageView != null) {
qrImageView.setAlpha(1.0f);
qrImageView.setVisibility(View.VISIBLE);
}
// Load new QR image
showQrImage(newQrImageUrl);
// Update timer display
if (timerTextView != null) {
timerTextView.setTextColor(getResources().getColor(android.R.color.black));
}
});
}
@Override
public void updateQrUrl(String newQrUrl) {
Log.d(TAG, "🔄 Updating QR URL: " + newQrUrl);
runOnUiThread(() -> {
if (qrUrlTextView != null) {
qrUrlTextView.setText(newQrUrl);
qrUrlTextView.setVisibility(View.VISIBLE);
}
});
}
@Override
public void showQrExpired() {
Log.w(TAG, "⏰ Showing QR expired");
runOnUiThread(() -> {
// Make QR semi-transparent
if (qrImageView != null) {
qrImageView.setAlpha(0.5f);
}
if (timerTextView != null) {
timerTextView.setText("EXPIRED");
timerTextView.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
}
});
}
@Override
public void showQrRefreshing() {
Log.d(TAG, "🔄 Showing QR refreshing");
runOnUiThread(() -> {
if (timerTextView != null) {
timerTextView.setText("Refreshing...");
timerTextView.setTextColor(getResources().getColor(android.R.color.holo_orange_dark));
}
});
}
@Override
public void showQrRefreshFailed(String errorMessage) {
Log.e(TAG, "❌ QR refresh failed: " + errorMessage);
runOnUiThread(() -> {
if (timerTextView != null) {
timerTextView.setText("Refresh Gagal");
timerTextView.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
}
// Tidak langsung navigate, biarkan presenter handle
showToast("Gagal refresh QR: " + errorMessage);
});
}
@Override
public void showQrRefreshSuccess() {
Log.d(TAG, "✅ QR refresh successful");
runOnUiThread(() -> {
// Reset QR image appearance
if (qrImageView != null) {
qrImageView.setAlpha(1.0f);
qrImageView.setVisibility(View.VISIBLE);
}
// Reset timer color and show success message
if (timerTextView != null) {
timerTextView.setTextColor(getResources().getColor(android.R.color.black));
}
// Show success toast
showToast("QR Code berhasil diperbarui!");
});
}
@Override
public void showPaymentSuccess(String providerName) {
Log.d(TAG, "🎉 Showing payment success for: " + providerName);
runOnUiThread(() -> {
showFullScreenSuccess(providerName);
// Navigate to receipt after 3 seconds, then to main activity
new Handler(Looper.getMainLooper()).postDelayed(() -> {
// Fixed: Remove the undefined 'view' variable and just check if activity is still valid
if (!isFinishing() && !isDestroyed()) {
navigateToReceipt(presenter.getTransaction());
}
}, 3000);
});
}
@Override
public void showPaymentFailed(String reason) {
Log.w(TAG, "❌ Payment failed: " + reason);
showToast("Payment failed: " + reason);
}
@Override
public void showPaymentPending() {
Log.d(TAG, "⏳ Payment pending");
showStatus("Payment pending...");
}
@Override
public void showLoading() {
if (progressBar != null) {
progressBar.setVisibility(View.VISIBLE);
}
}
@Override
public void hideLoading() {
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1001) { // Receipt activity result
Log.d(TAG, "📄 Receipt activity finished, navigating to main");
navigateToMainWithTransactionComplete();
}
}
private void navigateToMainWithTransactionComplete() {
Intent intent = new Intent(this, com.example.bdkipoc.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Add transaction completion data
intent.putExtra("transaction_completed", true);
if (presenter != null && presenter.getTransaction() != null) {
intent.putExtra("transaction_amount", String.valueOf(presenter.getTransaction().getOriginalAmount()));
intent.putExtra("payment_provider", presenter.getTransaction().getDisplayProviderName());
}
startActivity(intent);
finishAffinity(); // Clear all activities in the task
}
@Override
public void navigateToReceipt(QrisTransaction transaction) {
Log.d(TAG, "📄 Navigating to receipt");
Intent intent = new Intent(this, ReceiptActivity.class);
// Put transaction data
intent.putExtra("calling_activity", "QrisResultActivity");
intent.putExtra("transaction_id", transaction.getTransactionId());
intent.putExtra("reference_id", transaction.getReferenceId());
intent.putExtra("order_id", transaction.getOrderId());
intent.putExtra("transaction_amount", String.valueOf(transaction.getOriginalAmount()));
intent.putExtra("gross_amount", transaction.getGrossAmount() != null ? transaction.getGrossAmount() : String.valueOf(transaction.getOriginalAmount()));
intent.putExtra("created_at", getCurrentDateTime());
intent.putExtra("transaction_date", getCurrentDateTime());
intent.putExtra("payment_method", "QRIS");
intent.putExtra("channel_code", "QRIS");
intent.putExtra("channel_category", "RETAIL_OUTLET");
intent.putExtra("card_type", transaction.getDisplayProviderName());
intent.putExtra("merchant_name", "Marcel Panjaitan");
intent.putExtra("merchant_location", "Jakarta, Indonesia");
intent.putExtra("acquirer", transaction.getActualIssuer() != null ? transaction.getActualIssuer() : transaction.getAcquirer());
intent.putExtra("mid", "71000026521");
intent.putExtra("tid", "73001500");
// Enhanced data
intent.putExtra("detected_provider", transaction.getDetectedProvider());
intent.putExtra("qr_expiration_minutes", transaction.getQrExpirationMinutes());
intent.putExtra("was_qr_refresh_transaction", transaction.isQrRefreshTransaction());
// QR string
if (transaction.getQrString() != null && !transaction.getQrString().isEmpty()) {
intent.putExtra("qr_string", transaction.getQrString());
}
// Add flag to automatically return to main after receipt
intent.putExtra("auto_return_to_main", true);
startActivityForResult(intent, 1001);
}
@Override
public void navigateToMain() {
Intent intent = new Intent(this, com.example.bdkipoc.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finishAffinity();
}
@Override
public void finishActivity() {
finish();
}
@Override
public void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void showError(String errorMessage) {
Log.e(TAG, "❌ Error: " + errorMessage);
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
}
@Override
public void startSuccessAnimation() {
Log.d(TAG, "🎬 Starting success animation");
// Animation akan di-handle oleh showFullScreenSuccess
}
@Override
public void stopAllAnimations() {
Log.d(TAG, "🛑 Stopping all animations");
if (animationHandler != null) {
animationHandler.removeCallbacksAndMessages(null);
}
}
// ========================================================================================
// PRIVATE HELPER METHODS
// ========================================================================================
/**
* Show full screen success dengan animations
*/
private void showFullScreenSuccess(String providerName) {
if (successScreen != null && !isFinishing()) {
// Hide main UI components
hideMainUIComponents();
// Set success message
if (successMessage != null) {
successMessage.setText("Pembayaran " + providerName + " Berhasil");
}
// Show success screen dengan fade in animation
successScreen.setVisibility(View.VISIBLE);
successScreen.setAlpha(0f);
// Fade in background
ObjectAnimator backgroundFadeIn = ObjectAnimator.ofFloat(successScreen, "alpha", 0f, 1f);
backgroundFadeIn.setDuration(500);
backgroundFadeIn.start();
// Success icon animation
if (successIcon != null) {
successIcon.setScaleX(0f);
successIcon.setScaleY(0f);
successIcon.setAlpha(0f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(successIcon, "scaleX", 0f, 1.2f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(successIcon, "scaleY", 0f, 1.2f, 1f);
ObjectAnimator iconFadeIn = ObjectAnimator.ofFloat(successIcon, "alpha", 0f, 1f);
AnimatorSet iconAnimation = new AnimatorSet();
iconAnimation.playTogether(scaleX, scaleY, iconFadeIn);
iconAnimation.setDuration(800);
iconAnimation.setStartDelay(300);
iconAnimation.setInterpolator(new android.view.animation.OvershootInterpolator(1.2f));
iconAnimation.start();
}
// Success message animation
if (successMessage != null) {
successMessage.setAlpha(0f);
successMessage.setTranslationY(50f);
ObjectAnimator messageSlideUp = ObjectAnimator.ofFloat(successMessage, "translationY", 50f, 0f);
ObjectAnimator messageFadeIn = ObjectAnimator.ofFloat(successMessage, "alpha", 0f, 1f);
AnimatorSet messageAnimation = new AnimatorSet();
messageAnimation.playTogether(messageSlideUp, messageFadeIn);
messageAnimation.setDuration(600);
messageAnimation.setStartDelay(600);
messageAnimation.setInterpolator(new android.view.animation.DecelerateInterpolator());
messageAnimation.start();
}
}
}
/**
* Hide main UI components untuk clean success screen
*/
private void hideMainUIComponents() {
if (mainCard != null) {
mainCard.setVisibility(View.GONE);
}
if (headerBackground != null) {
headerBackground.setVisibility(View.GONE);
}
if (backNavigation != null) {
backNavigation.setVisibility(View.GONE);
}
if (cancelButton != null) {
cancelButton.setVisibility(View.GONE);
}
}
/**
* Add click animation ke view
*/
private void addClickAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setDuration(150);
animatorSet.start();
}
/**
* Get current date time untuk receipt
*/
private String getCurrentDateTime() {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", new java.util.Locale("id", "ID"));
return sdf.format(new java.util.Date());
}
// ========================================================================================
// LIFECYCLE METHODS
// ========================================================================================
@Override
protected void onDestroy() {
super.onDestroy();
// Cleanup presenter
if (presenter != null) {
presenter.onDestroy();
}
// Cleanup animation handler
if (animationHandler != null) {
animationHandler.removeCallbacksAndMessages(null);
}
Log.d(TAG, "💀 QrisResultActivity destroyed");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "⏸️ QrisResultActivity paused");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "▶️ QrisResultActivity resumed");
}
@Override
public void onBackPressed() {
// Prevent back press during success screen animation
if (successScreen != null && successScreen.getVisibility() == View.VISIBLE) {
Log.d(TAG, "⬅️ Back press blocked during success screen");
return;
}
// Show confirmation dialog before leaving
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this);
builder.setTitle("Batalkan Transaksi");
builder.setMessage("Apakah Anda yakin ingin membatalkan transaksi ini?");
builder.setPositiveButton("Ya, Batalkan", (dialog, which) -> {
if (presenter != null) {
presenter.onBackPressed();
} else {
super.onBackPressed();
}
});
builder.setNegativeButton("Tidak", (dialog, which) -> {
dialog.dismiss();
});
builder.show();
}
}

View File

@@ -0,0 +1,86 @@
package com.example.bdkipoc.qris.view;
import com.example.bdkipoc.qris.model.QrisTransaction;
/**
* Contract interface untuk QrisResult module
* Mendefinisikan komunikasi antara View dan Presenter
*/
public interface QrisResultContract {
/**
* View interface - apa yang bisa dilakukan oleh View (Activity)
*/
interface View {
// UI Display methods
void showQrImage(String qrImageUrl);
void showAmount(String formattedAmount);
void showTimer(String timeDisplay);
void showStatus(String status);
void showProviderName(String providerName);
// QR Management
void updateQrImage(String newQrImageUrl);
void updateQrUrl(String newQrUrl);
void showQrExpired();
void showQrRefreshing();
void showQrRefreshFailed(String errorMessage);
void showQrRefreshSuccess();
// Payment Status
void showPaymentSuccess(String providerName);
void showPaymentFailed(String reason);
void showPaymentPending();
// Loading states
void showLoading();
void hideLoading();
// Navigation
void navigateToReceipt(QrisTransaction transaction);
void navigateToMain();
void finishActivity();
// User feedback
void showToast(String message);
void showError(String errorMessage);
// Animation
void startSuccessAnimation();
void stopAllAnimations();
}
/**
* Presenter interface - apa yang bisa dilakukan oleh Presenter
*/
interface Presenter {
// Lifecycle
void attachView(View view);
void detachView();
void onDestroy();
// Initialization
void initializeTransaction(String orderId, String transactionId, String amount,
String qrImageUrl, String qrString, String acquirer);
// QR Management
void startQrManagement();
void stopQrManagement();
void refreshQrCode();
void onQrExpired();
// Payment Monitoring
void startPaymentMonitoring();
void stopPaymentMonitoring();
void checkPaymentStatus();
// User Actions
void onCancelClicked();
void onBackPressed();
void onSimulatePayment(); // For testing
// Timer
void startTimer();
void stopTimer();
}
}

View File

@@ -1,4 +1,4 @@
package com.example.bdkipoc;
package com.example.bdkipoc.settlement;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -8,6 +8,9 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.LinearLayout;
import android.widget.Button;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -20,11 +23,16 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import com.example.bdkipoc.BuildConfig;
import com.example.bdkipoc.R;
public class SettlementActivity extends AppCompatActivity {
private TextView tvTotalAmount;
@@ -32,9 +40,8 @@ public class SettlementActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private SettlementAdapter adapter;
private List<SettlementItem> settlementList;
private ImageView btnBack;
private String API_URL = "https://be-edc.msvc.app/transactions/performa-chanel-pembayaran?from_date=2025-01-01&to_date=2025-06-04&location_id=0&merchant_id=0";
private LinearLayout backNavigation;
private Button btnContinue;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -48,8 +55,18 @@ public class SettlementActivity extends AppCompatActivity {
}
private void fetchApiData() {
// Get current date in yyyy-MM-dd format
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
String currentDate = sdf.format(new Date());
// Build API URL with current date and credentials from BuildConfig
String apiUrl = BuildConfig.BACKEND_BASE_URL + "/transactions/performa-chanel-pembayaran" +
"?from_date=" + currentDate +
"&to_date=" + currentDate +
"&location_id=0&merchant_id=0";
// Execute network call in background thread
new ApiTask().execute(API_URL);
new ApiTask().execute(apiUrl);
}
private void processApiData(JSONArray dataArray) {
@@ -112,7 +129,8 @@ public class SettlementActivity extends AppCompatActivity {
tvTotalAmount = findViewById(R.id.tv_total_amount);
tvTotalTransactions = findViewById(R.id.tv_total_transactions);
recyclerView = findViewById(R.id.recycler_view);
btnBack = findViewById(R.id.btn_back);
backNavigation = findViewById(R.id.back_navigation);
btnContinue = findViewById(R.id.btn_continue);
settlementList = new ArrayList<>();
}
@@ -124,12 +142,21 @@ public class SettlementActivity extends AppCompatActivity {
}
private void setupClickListeners() {
btnBack.setOnClickListener(new View.OnClickListener() {
// Updated to use backNavigation instead of btnBack
backNavigation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
btnContinue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SettlementActivity.this, SettlementDetailActivity.class);
startActivity(intent);
}
});
}
private void loadSampleData() {
@@ -216,9 +243,6 @@ public class SettlementActivity extends AppCompatActivity {
return formatter.format(amount);
}
// Deprecated helper class - no longer needed
// private static class ChannelData { ... }
// AsyncTask for API call
private class ApiTask extends AsyncTask<String, Void, String> {
@Override
@@ -230,6 +254,9 @@ public class SettlementActivity extends AppCompatActivity {
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// Add authorization header if needed
// connection.setRequestProperty("Authorization", BuildConfig.MIDTRANS_SANDBOX_AUTH);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));

View File

@@ -0,0 +1,403 @@
package com.example.bdkipoc.settlement;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import com.example.bdkipoc.BuildConfig;
import com.example.bdkipoc.LoginActivity;
import com.example.bdkipoc.R;
public class SettlementDetailActivity extends AppCompatActivity {
private TextView tvStoreName, tvStoreLocation, tvMid, tvTid;
private TextView tvSettlementDate, tvSettlementTime;
private TextView tvTotalMasuk, tvTotalKeluar, tvBiayaAdmin, tvGrandTotal;
private RecyclerView recyclerView;
private SettlementDetailAdapter adapter;
private List<SettlementDetailItem> settlementDetailList;
private LinearLayout backNavigation;
private Button btnSendSettlement;
// Summary totals
private long totalMasuk = 0; // Set to 0
private long totalKeluar = 0;
private long biayaAdmin = 0; // Set to 0
private long grandTotal = 0;
// User data
private JSONObject userData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settlement_detail);
// Load user data from login session
loadUserData();
initViews();
setupRecyclerView();
fetchApiData();
setupClickListeners();
updateDateTime();
}
private void loadUserData() {
userData = LoginActivity.getUserDataAsJson(this);
}
private void fetchApiData() {
// Get current date in yyyy-MM-dd format
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
String currentDate = sdf.format(new Date());
// Build API URL with current date
String apiUrl = BuildConfig.BACKEND_BASE_URL + "/transactions/performa-chanel-pembayaran" +
"?from_date=" + currentDate +
"&to_date=" + currentDate +
"&location_id=0&merchant_id=0";
new ApiTask().execute(apiUrl);
}
private void processApiData(JSONArray dataArray) {
try {
settlementDetailList.clear();
long totalAmount = 0;
for (int i = 0; i < dataArray.length(); i++) {
JSONObject item = dataArray.getJSONObject(i);
String channelCode = item.getString("channel_code");
int transactions = item.getInt("total_transactions");
long maxAmount = item.getLong("max_transastions");
String displayName = formatChannelName(channelCode);
settlementDetailList.add(new SettlementDetailItem(
displayName,
maxAmount,
transactions
));
totalAmount += maxAmount;
}
calculateTotalsFromApiData(totalAmount);
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
updateSummary();
}
});
} catch (JSONException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(SettlementDetailActivity.this, "Error parsing data", Toast.LENGTH_SHORT).show();
loadSampleData();
}
});
}
}
private void calculateTotalsFromApiData(long totalAmount) {
totalMasuk = 0; // Set to 0 temporarily
totalKeluar = totalAmount;
biayaAdmin = 0; // Set to 0 temporarily
grandTotal = totalKeluar - totalMasuk - biayaAdmin;
}
private String formatChannelName(String channelCode) {
switch (channelCode) {
case "GO-PAY": return "GoPay";
case "SHOPEEPAY": return "ShopeePay";
case "LINKAJA": return "LinkAja";
case "MASTERCARD": return "Mastercard";
case "VISA": return "Visa";
case "QRIS": return "QRIS";
case "DANA": return "Dana";
case "OVO": return "OVO";
case "DEBIT": return "Kartu Debit";
case "GPN": return "GPN";
case "OTHER": return "Lainnya";
case "CREDIT": return "Kartu Kredit";
case "TRANSFER": return "Transfer";
case "E_MONEY": return "Uang Elektronik";
case "CASH_DEPOSIT": return "Setoran Tunai";
case "BILL_PAYMENT": return "Pembayaran Tagihan";
case "CASH_WITHDRAWAL": return "Penarikan Tunai";
case "TOP_UP": return "Top-up Saldo";
case "REFUND": return "Refund (void)";
default:
return channelCode.substring(0, 1).toUpperCase() +
channelCode.substring(1).toLowerCase();
}
}
private void initViews() {
tvStoreName = findViewById(R.id.tv_store_name);
tvStoreLocation = findViewById(R.id.tv_store_location);
tvMid = findViewById(R.id.tv_mid);
tvTid = findViewById(R.id.tv_tid);
tvSettlementDate = findViewById(R.id.tv_settlement_date);
tvSettlementTime = findViewById(R.id.tv_settlement_time);
tvTotalMasuk = findViewById(R.id.tv_total_masuk);
tvTotalKeluar = findViewById(R.id.tv_total_keluar);
tvBiayaAdmin = findViewById(R.id.tv_biaya_admin);
tvGrandTotal = findViewById(R.id.tv_grand_total);
recyclerView = findViewById(R.id.recycler_settlement_details);
backNavigation = findViewById(R.id.back_navigation);
btnSendSettlement = findViewById(R.id.btn_send_settlement);
settlementDetailList = new ArrayList<>();
}
private void setupRecyclerView() {
adapter = new SettlementDetailAdapter(settlementDetailList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
private void loadSampleData() {
settlementDetailList.clear();
settlementDetailList.add(new SettlementDetailItem("Kartu Kredit", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Kartu Debit", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("QRIS", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Transfer", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Uang Elektronik", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Setoran Tunai", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Pembayaran Tagihan", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Penarikan Tunai", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Top-up Saldo", 200000, 14));
settlementDetailList.add(new SettlementDetailItem("Refund (void)", 200000, 14));
totalMasuk = 0; // Set to 0 temporarily
totalKeluar = 2000000; // Total from sample data
biayaAdmin = 0; // Set to 0 temporarily
grandTotal = totalKeluar - totalMasuk - biayaAdmin;
adapter.notifyDataSetChanged();
updateSummary();
}
private void updateSummary() {
tvTotalMasuk.setText("Rp " + formatCurrency(totalMasuk));
tvTotalKeluar.setText("Rp " + formatCurrency(totalKeluar));
tvBiayaAdmin.setText("Rp " + formatCurrency(biayaAdmin));
tvGrandTotal.setText(formatCurrency(grandTotal));
}
private void updateDateTime() {
SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", new Locale("id", "ID"));
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
Date now = new Date();
tvSettlementDate.setText(dateFormat.format(now));
tvSettlementTime.setText(timeFormat.format(now));
// Set store info from user data
if (userData != null) {
String storeName = userData.optString("store_name", "TOKO KLONTONG PAK EKO");
String storeAddress = userData.optString("store_address", "Ciputat Baru, Tangsel");
String mid = userData.optString("mid", "12345678901");
String tid = userData.optString("tid", "12345678901");
tvStoreName.setText(storeName);
tvStoreLocation.setText(storeAddress);
tvMid.setText(mid);
tvTid.setText(tid);
} else {
// Fallback to default values
tvStoreName.setText("TOKO KLONTONG PAK EKO");
tvStoreLocation.setText("Ciputat Baru, Tangsel");
tvMid.setText("12345678901");
tvTid.setText("12345678901");
}
}
private void setupClickListeners() {
backNavigation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
btnSendSettlement.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendSettlement();
}
});
}
private void sendSettlement() {
Toast.makeText(this, "Mengirim settlement...", Toast.LENGTH_SHORT).show();
// TODO: Implement actual settlement sending logic
Toast.makeText(this, "Settlement berhasil dikirim!", Toast.LENGTH_LONG).show();
finish();
}
private String formatCurrency(long amount) {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
}
private class ApiTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
try {
JSONObject jsonResponse = new JSONObject(result);
if (jsonResponse.getInt("status") == 200) {
JSONArray dataArray = jsonResponse.getJSONArray("data");
processApiData(dataArray);
} else {
Toast.makeText(SettlementDetailActivity.this, "API Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(SettlementDetailActivity.this, "JSON Parse Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
} else {
Toast.makeText(SettlementDetailActivity.this, "Network Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
}
}
}
class SettlementDetailItem {
private String paymentMethod;
private long totalNominal;
private int jumlahTransaksi;
public SettlementDetailItem(String paymentMethod, long totalNominal, int jumlahTransaksi) {
this.paymentMethod = paymentMethod;
this.totalNominal = totalNominal;
this.jumlahTransaksi = jumlahTransaksi;
}
public String getPaymentMethod() { return paymentMethod; }
public long getTotalNominal() { return totalNominal; }
public int getJumlahTransaksi() { return jumlahTransaksi; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
public void setTotalNominal(long totalNominal) { this.totalNominal = totalNominal; }
public void setJumlahTransaksi(int jumlahTransaksi) { this.jumlahTransaksi = jumlahTransaksi; }
}
class SettlementDetailAdapter extends RecyclerView.Adapter<SettlementDetailAdapter.SettlementDetailViewHolder> {
private List<SettlementDetailItem> settlementDetailList;
public SettlementDetailAdapter(List<SettlementDetailItem> settlementDetailList) {
this.settlementDetailList = settlementDetailList;
}
@NonNull
@Override
public SettlementDetailViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_settlement_detail, parent, false);
return new SettlementDetailViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SettlementDetailViewHolder holder, int position) {
SettlementDetailItem item = settlementDetailList.get(position);
holder.tvPaymentMethod.setText(item.getPaymentMethod());
holder.tvTotalNominal.setText(formatCurrency(item.getTotalNominal()));
holder.tvJumlahTransaksi.setText(String.valueOf(item.getJumlahTransaksi()));
}
@Override
public int getItemCount() {
return settlementDetailList.size();
}
private String formatCurrency(long amount) {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
}
static class SettlementDetailViewHolder extends RecyclerView.ViewHolder {
TextView tvPaymentMethod;
TextView tvTotalNominal;
TextView tvJumlahTransaksi;
public SettlementDetailViewHolder(@NonNull View itemView) {
super(itemView);
tvPaymentMethod = itemView.findViewById(R.id.tv_payment_method);
tvTotalNominal = itemView.findViewById(R.id.tv_total_nominal);
tvJumlahTransaksi = itemView.findViewById(R.id.tv_jumlah_transaksi);
}
}
}

View File

@@ -500,18 +500,54 @@ public class CreateTransactionActivity extends AppCompatActivity implements
Log.e(TAG, "EMV Transaction failed: " + desc + " (Code: " + code + ")");
modalManager.hideModal();
if (code == -50009) {
// EMV process conflict - reset and retry
Log.d(TAG, "EMV process conflict detected - resetting...");
// Handle specific error codes
if (code == -4125) {
showToast("Card not supported. Please try another card or insert chip.");
performCompleteReset();
} else if (code == -50009) {
showToast("EMV busy, resetting...");
resetEMVAndRetry();
performCompleteReset();
} else {
// Other errors - show message and restart scanning
showToast("Transaction failed: " + desc);
restartScanningAfterDelay();
performCompleteReset();
}
}
private void performCompleteReset() {
Log.d(TAG, "Performing complete system reset...");
new Thread(() -> {
try {
// 1. Stop all scanning
if (cardScannerManager != null) {
cardScannerManager.stopScanning();
}
Thread.sleep(500);
// 2. Reset EMV completely
if (emvManager != null) {
emvManager.resetEMVProcess();
Thread.sleep(1000);
emvManager.resetEMVProcess(); // Double reset
}
// 3. Restart after proper delay
runOnUiThread(() -> {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!isFinishing()) {
Log.d(TAG, "Complete reset done - restarting");
startCardScanningFlow();
}
}, 3000); // 3 detik delay
});
} catch (Exception e) {
Log.e(TAG, "Error in complete reset: " + e.getMessage());
}
}).start();
}
private void showSuccessScreen(Runnable onAnimationComplete) {
// Hide modal first
modalManager.hideModal();

View File

@@ -25,6 +25,11 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import com.sunmi.peripheral.printer.InnerResultCallback;
import android.os.RemoteException;
import com.example.bdkipoc.MyApplication;
import com.sunmi.peripheral.printer.SunmiPrinterService;
/**
* ResultTransactionActivity - Enhanced Receipt-style Display using activity_receipt.xml
* Shows EMV/Card transaction results using the same layout as QRIS receipts
@@ -78,6 +83,35 @@ public class ResultTransactionActivity extends AppCompatActivity {
private long serviceFeeAmount = 500; // Default service fee
private double taxPercentageValue = 0.11; // 11% tax
private final InnerResultCallback printCallback = new InnerResultCallback() {
@Override
public void onRunResult(boolean isSuccess) throws RemoteException {
runOnUiThread(() -> {
if (isSuccess) {
showToast("Struk berhasil dicetak");
} else {
showToast("Gagal mencetak struk");
}
});
}
@Override
public void onReturnString(String result) throws RemoteException {
Log.d(TAG, "Print result: " + result);
}
@Override
public void onRaiseException(int code, String msg) throws RemoteException {
runOnUiThread(() -> showToast("Printer error: " + msg));
Log.e(TAG, "Printer exception: " + code + " - " + msg);
}
@Override
public void onPrintResult(int code, String msg) throws RemoteException {
Log.d(TAG, "Print result code: " + code + ", msg: " + msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -589,8 +623,95 @@ public class ResultTransactionActivity extends AppCompatActivity {
// Action Methods
private void printReceipt() {
Log.d(TAG, "Print receipt requested");
showToast("Fitur cetak akan segera tersedia");
try {
// Check if printer service is available
if (com.example.bdkipoc.MyApplication.app.sunmiPrinterService == null) {
showToast("Printer tidak tersedia");
return;
}
SunmiPrinterService printerService = com.example.bdkipoc.MyApplication.app.sunmiPrinterService;
// Get all the data from the views
String merchantNameText = merchantName.getText().toString();
String merchantLocationText = merchantLocation.getText().toString();
String midTextValue = midText.getText().toString();
String tidTextValue = tidText.getText().toString();
String transactionNumberText = transactionNumber.getText().toString();
String transactionDateText = transactionDate.getText().toString();
String paymentMethodText = paymentMethod.getText().toString();
String cardTypeText = cardType.getText().toString();
String transactionTotalText = transactionTotal.getText().toString();
String taxPercentageText = taxPercentage.getText().toString();
String serviceFeeText = serviceFee.getText().toString();
String finalTotalText = finalTotal.getText().toString();
showToast("Mencetak struk...");
try {
// Start printing
printerService.enterPrinterBuffer(true);
// Set alignment to center
printerService.setAlignment(1, null);
// Print header
printerService.printText("# Payvora PRO\n\n", null);
// Set alignment to left
printerService.setAlignment(0, null);
// Print merchant info
printerService.printText(merchantNameText + "\n", null);
printerService.printText(merchantLocationText + "\n\n", null);
// Print MID/TID
printerService.printText(midTextValue + " | " + tidTextValue + "\n\n", null);
// Print transaction details
printerService.printText("Nomor transaksi " + transactionNumberText + "\n", null);
printerService.printText("Tanggal transaksi " + transactionDateText + "\n", null);
printerService.printText("Metode pembayaran " + paymentMethodText + "\n", null);
printerService.printText("Jenis Kartu " + cardTypeText + "\n\n", null);
// Print amounts
printerService.printText("Total transaksi " + transactionTotalText + "\n", null);
printerService.printText("Pajak (%) " + taxPercentageText + "\n", null);
printerService.printText("Biaya Layanan " + serviceFeeText + "\n\n", null);
// Print total in bold
printerService.printText("**TOTAL** **" + finalTotalText + "**\n", null);
// Add EMV specific details if available
// if (emvMode && emvCardholderName != null) {
// printerService.printText("\nDETAIL EMV:\n", null);
// printerService.printText("Cardholder: " + emvCardholderName + "\n", null);
// if (cardNo != null) {
// printerService.printText("Card: " + maskCardNumber(cardNo) + "\n", null);
// }
// if (emvAid != null) {
// printerService.printText("AID: " + emvAid + "\n", null);
// }
// if (emvExpiry != null) {
// printerService.printText("Expiry: " + emvExpiry + "\n", null);
// }
// }
// Add some line feeds
printerService.lineWrap(4, null);
// Exit buffer mode
printerService.exitPrinterBuffer(true);
} catch (RemoteException e) {
e.printStackTrace();
showToast("Error printer: " + e.getMessage());
}
} catch (Exception e) {
Log.e(TAG, "Print error", e);
showToast("Error saat mencetak: " + e.getMessage());
}
}
private void emailReceipt() {

View File

@@ -121,58 +121,68 @@ public class EMVManager {
}
public void startEMVTransaction(String transactionAmount, int cardType) {
if (mProcessStep != 0) {
Log.d(TAG, "EMV transaction already in progress (step: " + mProcessStep + ") - ignoring call");
return;
// Force reset jika masih ada proses berjalan
if (mProcessStep > 0) {
Log.w(TAG, "Forcing EMV reset - previous step: " + mProcessStep);
resetEMVProcess();
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
Log.d(TAG, "Starting EMV transaction process");
Log.d(TAG, "Starting fresh EMV transaction");
mProcessStep = 1;
mCardType = cardType;
try {
// Extended initialization
mEMVOptV2.initEmvProcess();
Thread.sleep(500);
new Handler(Looper.getMainLooper()).postDelayed(() -> {
try {
if (mProcessStep <= 0) {
Log.d(TAG, "EMV process was cancelled - not starting");
return;
}
Bundle bundle = new Bundle();
bundle.putString("amount", transactionAmount);
bundle.putString("transType", "00");
bundle.putInt("flowType", AidlConstantsV2.EMV.FlowType.TYPE_EMV_STANDARD);
bundle.putInt("cardType", mCardType);
Log.d(TAG, "Starting transactProcessEx with reset EMV");
Log.d(TAG, "Starting EMV with reset state");
mEMVOptV2.transactProcessEx(bundle, mEMVListener);
} catch (Exception e) {
Log.e(TAG, "Error in delayed EMV start: " + e.getMessage(), e);
Log.e(TAG, "Error starting EMV: " + e.getMessage());
if (callback != null) {
callback.onTransactionFailed(-1, "Error starting EMV: " + e.getMessage());
callback.onTransactionFailed(-1, "EMV start error: " + e.getMessage());
}
}
}, 300);
}, 800); // Longer delay
} catch (Exception e) {
Log.e(TAG, "Error starting EMV transaction: " + e.getMessage());
Log.e(TAG, "Error in EMV transaction setup: " + e.getMessage());
if (callback != null) {
callback.onTransactionFailed(-1, "Error starting EMV transaction: " + e.getMessage());
callback.onTransactionFailed(-1, e.getMessage());
}
}
}
public void resetEMVProcess() {
try {
if (mEMVOptV2 != null) {
mEMVOptV2.initEmvProcess();
}
Log.d(TAG, "Resetting EMV process - current step: " + mProcessStep);
// Reset semua state variables FIRST
mProcessStep = 0;
mCardNo = null;
Log.d(TAG, "EMV process reset");
mPinType = 0;
mCertInfo = null;
mCardType = 0;
if (mEMVOptV2 != null) {
// Double reset untuk memastikan
mEMVOptV2.initEmvProcess();
Thread.sleep(300);
mEMVOptV2.initEmvProcess();
}
Log.d(TAG, "EMV process reset completed");
} catch (Exception e) {
Log.e(TAG, "Error resetting EMV process: " + e.getMessage());
}

View File

@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white"/>
<stroke android:width="1dp" android:color="#DE0701"/>
<corners android:radius="8dp"/>
</shape>

View File

@@ -1,6 +1,12 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#3498DB" />
<corners android:radius="12dp" />
<stroke android:width="0dp" />
<!-- Warna solid (biru cerah seperti #4299E1) -->
<solid android:color="#4299E1" />
<!-- Sudut melengkung -->
<corners android:radius="16dp" />
<!-- Hilangkan stroke jika tidak dibutuhkan -->
<stroke android:width="0dp" android:color="#00000000" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F0F0F0" />
<corners android:radius="8dp" />
<stroke android:width="1dp" android:color="#DDDDDD" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/black"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/black"
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- Semi-transparent color similar to #CCB2B24D -->
<solid android:color="#30FFFFFF" />
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#DE0701" />
<corners
android:bottomLeftRadius="48dp"
android:bottomRightRadius="48dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>

View File

@@ -0,0 +1,8 @@
<!-- res/drawable/tab_active_bg.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#DE0701" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#FFFFFF" />
</shape>

View File

@@ -0,0 +1,8 @@
<!-- res/drawable/tab_inactive_bg.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#DE0701" />
</shape>

View File

@@ -0,0 +1,518 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FFFFFF">
<!-- Custom AppBar -->
<include layout="@layout/component_appbar" />
<!-- Main Card Container -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="480dp"
android:layout_marginTop="-80dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/white">
<!-- Logo Section with Tabs -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="94.149dp"
android:layout_height="38.248dp"
android:layout_marginTop="5dp"
android:layout_marginLeft="10.9dp"
android:layout_marginStart="5dp"
android:src="@drawable/ic_logo_icon"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:contentDescription="Payvora PRO Logo"/>
<!-- Tabs -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:paddingHorizontal="16dp"
android:gravity="center_horizontal">
<LinearLayout
android:id="@+id/tab_umum"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="12dp"
android:background="@drawable/tab_active_bg"
android:layout_marginEnd="8dp">
<TextView
android:id="@+id/text_umum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Umum"
android:textColor="#FFFFFF"
android:textSize="16sp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/tab_riwayat"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/tab_inactive_bg"
android:gravity="center"
android:padding="12dp"
android:layout_marginStart="8dp">
<TextView
android:id="@+id/text_riwayat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Riwayat"
android:textColor="#DE0701"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<!-- Content Umum (Static help content) -->
<ScrollView
android:id="@+id/content_umum"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Help Items -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Transaksi gagal tapi saldo terpotong"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="QRIS tidak terbaca"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="EDC tidak merespon / hang"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Gagal cetak struk"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cara Reset EDC"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cara Hubungkan ke WiFi"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cara pembayaran kartu (debit/kredit)"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cara Refund Transaksi QRIS"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cara melakukan Settlement"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cara membatalkan transaksi (void)"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cara melihat riwayat transaksi"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lihat Solusi"
android:textColor="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
<!-- Content Riwayat (Dynamic content from API) -->
<ScrollView
android:id="@+id/content_riwayat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Dynamic content will be added here programmatically -->
</LinearLayout>
</ScrollView>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Bottom Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:background="@android:color/white"
android:gravity="center">
<!-- Tombol Isi Form -->
<LinearLayout
android:id="@+id/btn_form"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:orientation="horizontal"
android:gravity="center"
android:background="@drawable/border_button_red">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Isi Form Bantuan"
android:textSize="14sp"
android:textColor="#DE0701"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
<!-- Tombol WhatsApp -->
<LinearLayout
android:id="@+id/btn_whatsapp"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:orientation="horizontal"
android:gravity="center"
android:background="@drawable/border_button_red"
android:paddingHorizontal="8dp">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_whatsapp"
android:contentDescription="WhatsApp Icon"
android:layout_marginEnd="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WhatsApp CS"
android:textSize="14sp"
android:textColor="#DE0701"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,305 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5">
<!-- Main Content (Original Layout) -->
<LinearLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Custom AppBar -->
<include layout="@layout/component_appbar_small" />
<!-- Main Content -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingHorizontal="16dp"
android:paddingTop="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Form Card -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Form Title -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Formulir Helpdesk untuk Merchant"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#DE0701"
android:layout_marginBottom="8dp" />
<!-- Form Subtitle -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Mohon isi data Anda pada formulir di bawah ini untuk melaporkan masalah yang terjadi."
android:textSize="14sp"
android:textColor="#666666"
android:layout_marginBottom="24dp"
android:lineSpacingExtra="2dp" />
<!-- Ticket Code Field -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ticket Code"
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp"
android:textStyle="bold" />
<EditText
android:id="@+id/et_ticket_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:hint="Masukkan kode tiket (contoh: #20240312)"
android:padding="16dp"
android:textSize="14sp"
android:textColorHint="#AAAAAA"
android:layout_marginBottom="20dp"
android:singleLine="true" />
<!-- Source Field -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Source"
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp"
android:textStyle="bold" />
<Spinner
android:id="@+id/spinner_source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_marginBottom="20dp"
android:padding="16dp" />
<!-- Issue Field -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Issue"
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp"
android:textStyle="bold" />
<Spinner
android:id="@+id/spinner_issue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_marginBottom="20dp"
android:padding="16dp" />
<!-- Merchant Field -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Merchant"
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp"
android:textStyle="bold" />
<Spinner
android:id="@+id/spinner_merchant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_marginBottom="20dp"
android:padding="16dp" />
<!-- Status Field (Read-only) -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Status"
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:layout_marginBottom="20dp">
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pengajuan"
android:textSize="14sp"
android:textColor="#666666" />
</LinearLayout>
<!-- Assign Field -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Assign To"
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp"
android:textStyle="bold" />
<Spinner
android:id="@+id/spinner_assign"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_marginBottom="20dp"
android:padding="16dp" />
<!-- Resolved Date Field -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Resolved Date"
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/ll_resolved_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:layout_marginBottom="12dp"
android:clickable="true"
android:focusable="true">
<TextView
android:id="@+id/tv_resolved_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Pilih Tanggal Resolved"
android:textSize="14sp"
android:textColor="#AAAAAA" />
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_my_calendar"
android:contentDescription="Calendar Icon" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>
<!-- Submit Button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="@android:color/white"
android:elevation="4dp">
<Button
android:id="@+id/btn_kirim"
android:layout_width="match_parent"
android:layout_height="52dp"
android:text="Kirim Sekarang"
android:textSize="16sp"
android:textStyle="bold"
android:textAllCaps="false"
android:background="@android:color/darker_gray"
android:textColor="@android:color/white"
android:enabled="false"
android:stateListAnimator="@null"
android:elevation="0dp" />
</LinearLayout>
</LinearLayout>
<!-- Success Screen (Full Screen Overlay) -->
<LinearLayout
android:id="@+id/success_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#E31937"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Success Icon -->
<ImageView
android:id="@+id/success_icon"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_success_payment"
android:layout_marginBottom="32dp"
android:scaleType="centerInside"
android:contentDescription="Success Icon" />
<!-- Success Message -->
<TextView
android:id="@+id/success_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Formulir Berhasil Dikirim"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold"
android:gravity="center"
android:letterSpacing="0.02"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -3,136 +3,124 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:background="#FFFFFF"
android:orientation="vertical">
<!-- Header with red background -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#E53E3E">
<!-- Back button -->
<ImageView
android:id="@+id/btn_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
android:src="@android:drawable/ic_menu_revert"
app:tint="@android:color/white" />
<!-- Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_toEndOf="@id/btn_back"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="16sp" />
<!-- Custom AppBar -->
<include layout="@layout/component_appbar" />
<!-- History Card -->
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="64dp"
android:layout_marginTop="-70dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
android:layout_marginBottom="5dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#4299E1"
android:orientation="vertical"
android:layout_height="wrap_content"
android:background="@drawable/card_background"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<!-- Judul -->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Transaksi Hari Ini"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
android:textSize="20sp"
android:fontFamily="@font/inter"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- Lihat Detail -->
<TextView
android:id="@+id/btn_lihat_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Lihat Detail"
android:textColor="@android:color/white"
android:textSize="10sp"
android:fontFamily="@font/inter"
android:textStyle="normal"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintVertical_bias="0.5" />
<!-- Label Total Transaksi -->
<TextView
android:id="@+id/label_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Total Transaksi"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
android:textSize="10sp"
android:fontFamily="@font/inter"
android:textStyle="normal"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent" />
<!-- Nilai Total Transaksi -->
<TextView
android:id="@+id/tv_total_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="RP 4.500.000"
android:text="RP 0"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
android:fontFamily="@font/inter"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/label_total"
app:layout_constraintStart_toStartOf="parent" />
<!-- Label Jumlah Transaksi -->
<TextView
android:id="@+id/label_jumlah"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginTop="12dp"
android:text="Jumlah Transaksi"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
android:fontFamily="@font/inter"
android:textStyle="normal"
android:textSize="10sp"
app:layout_constraintTop_toBottomOf="@id/tv_total_amount"
app:layout_constraintStart_toStartOf="parent" />
<!-- Nilai Jumlah Transaksi -->
<TextView
android:id="@+id/tv_total_transactions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="30"
android:text="0"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
android:textSize="20sp"
android:fontFamily="@font/inter"
android:textStyle="bold"
android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="@id/label_jumlah"
app:layout_constraintStart_toStartOf="parent" />
</LinearLayout>
<TextView
android:id="@+id/btn_lihat_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="8dp"
android:text="Lihat Detail"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<!-- Content -->
<LinearLayout
@@ -145,10 +133,11 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginBottom="2dp"
android:text="Transaksi Terbaru"
android:textColor="#333333"
android:textColor="#000000"
android:textSize="16sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
<!-- Transaction List -->
@@ -170,6 +159,8 @@
android:text="Lihat Detail"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
android:fontFamily="@font/inter"
android:textStyle="bold"
app:cornerRadius="8dp" />
</LinearLayout>

View File

@@ -1,46 +0,0 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:orientation="vertical">
<!-- Header -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="#E53E3E"
android:padding="16dp">
<ImageView
android:id="@+id/btn_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:src="@android:drawable/ic_menu_revert"
app:tint="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Detail Transaksi"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
</RelativeLayout>
<!-- Content -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" />
</LinearLayout>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="0dp">
<!-- App Bar -->
<include layout="@layout/component_appbar_small" />
<!-- Title: Transaksi Terbaru -->
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Transaksi Terbaru"
android:textSize="18sp"
android:textStyle="bold"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="4dp"
android:textColor="#000000"
android:fontFamily="@font/inter" />
<!-- Konten List & Empty View ditumpuk -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!-- RecyclerView: Daftar Transaksi -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_history"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingHorizontal="16dp"
android:paddingBottom="8dp"
android:scrollbars="vertical" />
<!-- Teks Jika Tidak Ada Data -->
<TextView
android:id="@+id/tv_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Tidak ada transaksi"
android:visibility="gone"
android:textColor="#999999"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,356 @@
<?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="match_parent"
android:orientation="vertical"
android:background="#FFFFFF"
tools:context=".InfoTokoActivity">
<!-- Custom AppBar -->
<include layout="@layout/component_appbar_small" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Red background section with logo and store info -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Background with rounded bottom corners -->
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/rounded_bottom_background" />
<!-- Decorative Ovals -->
<!-- Oval 1 - Top Left Large -->
<View
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginStart="-40dp"
android:layout_marginTop="-30dp"
android:background="@drawable/oval_blur_decoration"
android:rotation="15" />
<!-- Oval 2 - Top Right Medium -->
<View
android:layout_width="88dp"
android:layout_height="88dp"
android:layout_marginStart="280dp"
android:layout_marginTop="13dp"
android:background="@drawable/oval_blur_decoration"
android:rotation="-20" />
<!-- Oval 3 - Bottom Left Small -->
<View
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="54dp"
android:layout_marginTop="180dp"
android:background="@drawable/oval_blur_decoration"
android:rotation="45" />
<!-- Oval 4 - Bottom Right Medium -->
<View
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_marginStart="250dp"
android:layout_marginTop="160dp"
android:background="@drawable/oval_blur_decoration"
android:rotation="0" />
<!-- Oval 5 - Center Left Small -->
<View
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="20dp"
android:layout_marginTop="90dp"
android:background="@drawable/oval_blur_decoration"
android:rotation="30" />
<!-- Main Content -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingTop="16dp"
android:paddingBottom="40dp">
<!-- Logo -->
<androidx.cardview.widget.CardView
android:layout_width="120dp"
android:layout_height="120dp"
app:cardCornerRadius="60dp"
app:cardElevation="4dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ic_logo_toko"
android:scaleType="centerCrop"
android:contentDescription="Logo Pak Eko" />
</androidx.cardview.widget.CardView>
<!-- Store Name -->
<TextView
android:id="@+id/tv_store_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="TOKO KLONTONG PAK EKO"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
<!-- MID and TID -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tv_merchant_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MID: 12345678901"
android:textColor="@android:color/white"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="|"
android:textColor="@android:color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_terminal_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TID: 12345678901"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
<!-- White card with store information -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="-36dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Account Information Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Informasi Akun"
android:textColor="@android:color/black"
android:textSize="16sp"
android:textStyle="bold" />
<!-- Email -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Email"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Password -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Kata Sandi"
app:passwordToggleEnabled="true"
app:endIconMode="password_toggle"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Store Information Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Informasi Pemilik"
android:textColor="@android:color/black"
android:textSize="16sp"
android:textStyle="bold" />
<!-- Owner Name -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Nama Pemilik"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_owner_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Phone Number -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Nomor Telepon"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Hidden fields that will be programmatically hidden -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="NIK"
android:visibility="gone"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_nik"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Jenis Usaha"
android:visibility="gone"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_business_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Nama Usaha"
android:visibility="gone"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_business_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Alamat"
android:visibility="gone"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:minLines="3"
android:gravity="top"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Update Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_update"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="24dp"
android:text="Perbarui Informasi Toko"
android:textAllCaps="false"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#DE0701"
android:backgroundTint="@android:color/white"
app:cornerRadius="8dp"
app:strokeColor="#DE0701"
app:strokeWidth="2dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
android:background="#DE0701"
tools:context=".LoginActivity">
<!-- Background dengan gradient -->
<View
android:id="@+id/background_gradient"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#DE0701"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Main Content Container -->
<androidx.cardview.widget.CardView
android:id="@+id/login_card"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:elevation="8dp"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp">
<!-- Logo/Title Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="32dp">
<!-- App Icon/Logo -->
<View
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="16dp"
android:background="#DE0701"
android:elevation="4dp" />
<!-- Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="BDKI POC"
android:textColor="#333333"
android:textSize="28sp"
android:textStyle="bold" />
<!-- Subtitle -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Masuk ke akun Anda"
android:textColor="#666666"
android:textSize="16sp" />
</LinearLayout>
<!-- Login Form -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Email/Username Input -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Email atau Username"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="8dp"
app:boxCornerRadiusBottomStart="8dp"
app:boxCornerRadiusTopEnd="8dp"
app:boxCornerRadiusTopStart="8dp"
app:boxStrokeColor="#DE0701"
app:hintTextColor="#DE0701"
app:startIconDrawable="@android:drawable/ic_dialog_email">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_identifier"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:maxLines="1"
android:textColor="#333333"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Password Input -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:hint="Password"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="8dp"
app:boxCornerRadiusBottomStart="8dp"
app:boxCornerRadiusTopEnd="8dp"
app:boxCornerRadiusTopStart="8dp"
app:boxStrokeColor="@android:color/holo_blue_bright"
app:endIconMode="password_toggle"
app:hintTextColor="@android:color/holo_blue_bright"
app:startIconDrawable="@android:drawable/ic_lock_idle_lock">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="#333333"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Login Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="16dp"
android:backgroundTint="#DE0701"
android:text="MASUK"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
app:cornerRadius="8dp"
app:elevation="4dp" />
<!-- Progress Bar -->
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:indeterminateTint="#DE0701"
android:visibility="gone" />
</LinearLayout>
<!-- Footer Info -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="Demo Credentials:"
android:textColor="#999999"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:text="Email: welno@gmail.com"
android:textColor="#999999"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password: 55555555"
android:textColor="#999999"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Status Bar Overlay -->
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -28,7 +28,7 @@
<View
android:id="@+id/red_header_background"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_height="60dp"
android:background="#E31937"
app:layout_constraintTop_toBottomOf="@id/status_bar_background"/>
@@ -37,57 +37,152 @@
android:id="@+id/merchant_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:translationY="-12dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:layout_constraintTop_toBottomOf="@id/status_bar_background">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Logout Button di pojok kanan atas -->
<com.google.android.material.button.MaterialButton
android:id="@+id/logout_button"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Logout"
android:textColor="#DE0701"
android:textSize="11sp"
android:textAllCaps="false"
android:backgroundTint="@android:color/transparent"
android:drawableLeft="@android:drawable/ic_lock_power_off"
android:drawableTint="#DE0701"
android:drawablePadding="4dp"
android:padding="6dp"
android:minWidth="0dp"
android:minHeight="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Logo Payvora PRO -->
<ImageView
android:id="@+id/logo_payvora"
android:layout_width="144dp"
android:layout_height="36dp"
android:src="@drawable/ic_logo_icon"
android:scaleType="fitStart"
android:adjustViewBounds="true"
android:layout_marginBottom="8dp"/>
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/logout_button"
app:layout_constraintHorizontal_bias="0" />
<!-- Nama Toko -->
<TextView
android:layout_width="wrap_content"
android:id="@+id/tv_store_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="TOKO KLONTONG PAK EKO"
android:textColor="#061D28"
android:textSize="18sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"/>
android:gravity="center"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/logo_payvora" />
<!-- Alamat Toko -->
<TextView
android:id="@+id/tv_store_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Ciputat Baru, Tangsel"
android:textColor="#9FA4A9"
android:textSize="14sp"
android:layout_gravity="center_horizontal"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_store_name" />
<!-- User Info Section -->
<LinearLayout
android:id="@+id/user_info_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginTop="8dp"
android:padding="8dp"
android:background="#F8F9FA"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_store_address">
<TextView
android:id="@+id/tv_user_welcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="👤 Welcome, "
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="User Name"
android:textColor="#333333"
android:textSize="12sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/tv_user_role"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="(Role)"
android:textColor="#666666"
android:textSize="11sp"
android:layout_marginStart="4dp" />
</LinearLayout>
<!-- Divider -->
<View
android:layout_width="match_parent"
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:background="#EEEEEE"/>
android:background="#EEEEEE"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_info_section" />
<!-- MID and TID Info -->
<LinearLayout
android:layout_width="match_parent"
android:id="@+id/mid_tid_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider">
<TextView
android:id="@+id/tv_mid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MID: 12345678901"
@@ -96,13 +191,17 @@
android:layout_marginEnd="16dp" />
<TextView
android:id="@+id/tv_tid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TID: 12345678901"
android:textColor="#9FA4A9"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- Menu Grid -->
@@ -539,59 +638,32 @@
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/menu_grid"/>
<!-- Scan dan Bayar Card -->
<!-- Scan dan Bayar Banner -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardBackgroundColor="#E31937"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_lainnya"
app:layout_constraintVertical_bias="0">
<LinearLayout
android:id="@+id/scan_bayar_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp">
app:layout_constraintTop_toBottomOf="@id/btn_lainnya">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="@android:color/white"/>
<LinearLayout
android:layout_width="0dp"
android:id="@+id/banner_qris"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SCAN DAN BAYAR"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Terima pembayaran dengan QRIS secara mudah"
android:textColor="@android:color/white"
android:textSize="12sp"/>
</LinearLayout>
</LinearLayout>
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/banner"
android:contentDescription="Banner QRIS"/>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -1,10 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView 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="match_parent"
android:background="#F5F5F5"
android:fillViewport="true"
android:background="#F5F5F5">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".QrisResultActivity">
<!-- Header Background -->
@@ -46,7 +51,6 @@
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter" />
</LinearLayout>
<!-- Main Content Card -->
@@ -61,9 +65,7 @@
app:cardElevation="8dp"
app:layout_constraintTop_toBottomOf="@id/header_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/cancel_button"
app:layout_constraintVertical_bias="0.3">
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
@@ -120,8 +122,38 @@
android:background="@drawable/timer_circle_background"
android:fontFamily="@font/inter" />
</LinearLayout>
<!-- QR URL (Copyable) -->
<TextView
android:id="@+id/qrUrlTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="QR URL will appear here"
android:textSize="12sp"
android:textColor="#666666"
android:gravity="center"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:padding="8dp"
android:background="@drawable/copyable_text_background"
android:clickable="true"
android:focusable="true"
android:textIsSelectable="true"
android:fontFamily="@font/inter" />
<!-- Simulator Button -->
<Button
android:id="@+id/simulatorButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Demo Simulator"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:backgroundTint="#E31937"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:padding="12dp"
android:fontFamily="@font/inter" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Cancel Button -->
@@ -143,7 +175,32 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Hidden Elements for Functionality -->
<!-- Add these inside your ConstraintLayout -->
<Button
android:id="@+id/downloadQrisButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/checkStatusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/returnMainButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- Hidden Elements -->
<TextView
android:id="@+id/referenceTextView"
android:layout_width="wrap_content"
@@ -176,31 +233,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/downloadQrisButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/checkStatusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/returnMainButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- Success Screen (Full Screen Overlay) -->
<!-- Success Screen Overlay -->
<LinearLayout
android:id="@+id/success_screen"
android:layout_width="match_parent"
@@ -214,7 +247,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Success Icon -->
<ImageView
android:id="@+id/success_icon"
android:layout_width="120dp"
@@ -223,7 +255,6 @@
android:layout_marginBottom="32dp"
android:scaleType="centerInside"/>
<!-- Success Message -->
<TextView
android:id="@+id/success_message"
android:layout_width="wrap_content"
@@ -235,7 +266,6 @@
android:fontFamily="@font/inter"
android:gravity="center"
android:letterSpacing="0.02"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -4,268 +4,165 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
app:elevation="0dp">
<!-- Status Bar Background -->
<View
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="#F44336" />
<!-- Main Toolbar -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="#F44336"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:contentInsetStart="0dp"
app:contentInsetLeft="0dp">
<!-- Custom toolbar content -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<!-- Back Button -->
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_revert"
android:layout_marginEnd="16dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="4dp"
android:clickable="true"
android:focusable="true"
android:rotation="180" />
<!-- Title -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cetak Ulang Struk"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="normal"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<!-- Title Section (outside toolbar) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cetak Ulang Struk"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold"
android:fontFamily="inter-bold" />
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<!-- Include the component appbar -->
<include layout="@layout/component_appbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#f8f9fa"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
android:background="@android:color/white"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_marginTop="50dp">
<!-- Search and Filter Section -->
<!-- Title Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cetak Ulang Struk"
android:textSize="20sp"
android:textColor="#333333"
android:textStyle="bold"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp" />
<!-- Search and Filter Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:weightSum="10">
<!-- Search Bar -->
<!-- Search Container (Ratio 8) -->
<LinearLayout
android:id="@+id/searchContainer"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_height="44dp"
android:layout_weight="8"
android:background="@android:drawable/edit_text"
android:backgroundTint="@android:color/white"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:layout_marginEnd="12dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_search"
android:layout_marginEnd="12dp"
android:alpha="0.5" />
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:layout_marginEnd="8dp">
<EditText
android:id="@+id/searchEditText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:hint="Search"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Cari dengan nomor struk..."
android:textColorHint="#999999"
android:textColor="#333333"
android:textSize="14sp"
android:background="@android:color/transparent"
android:inputType="text"
android:maxLines="1"
android:gravity="center_vertical" />
android:imeOptions="actionSearch" />
</LinearLayout>
<!-- Filter Button -->
<!-- Filter Button (Ratio 2) -->
<LinearLayout
android:id="@+id/filterButton"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="2"
android:background="@android:drawable/btn_default"
android:backgroundTint="@android:color/white"
android:orientation="horizontal"
android:gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:clickable="true"
android:focusable="true"
android:minWidth="120dp">
android:focusable="true">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@android:drawable/ic_menu_my_calendar"
android:layout_marginEnd="6dp"
android:alpha="0.5" />
android:src="@android:drawable/ic_menu_sort_by_size"
android:tint="#666666" />
<TextView
android:id="@+id/filterButtonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Filter"
android:textSize="14sp"
android:textColor="#666666"
android:maxLines="2"
android:ellipsize="end"
android:gravity="center" />
</LinearLayout>
</LinearLayout>
<!-- Table Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:background="#f5f5f5"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Nomor Struk"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="#666666" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Amount"
android:textSize="14sp"
android:textStyle="bold"
android:text=""
android:textColor="#666666"
android:gravity="center" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Action"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="#666666"
android:gravity="center" />
android:textSize="12sp"
android:gravity="center"
android:layout_marginStart="4dp"
android:visibility="gone" />
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp" />
</LinearLayout>
<!-- RecyclerView for transactions -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#ffffff"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:background="@android:color/white"
android:clipToPadding="false"
android:paddingBottom="8dp" />
android:scrollbars="vertical" />
<!-- Progress Bar -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:visibility="gone" />
<!-- Pagination Controls -->
<LinearLayout
android:id="@+id/paginationControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:orientation="vertical"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@android:color/white"
android:paddingTop="16dp"
android:paddingBottom="20dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:gravity="center"
android:elevation="4dp"
android:visibility="gone"
android:layout_marginTop="2dp">
android:visibility="gone">
<!-- Navigation Controls -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<!-- First Page Button -->
<ImageButton
android:id="@+id/btnFirstPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginEnd="8dp"
android:background="@android:drawable/btn_default"
android:src="@android:drawable/ic_media_previous"
android:contentDescription="First Page"
android:layout_marginEnd="8dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
android:scaleType="fitCenter" />
<!-- Previous Page Button -->
<ImageButton
android:id="@+id/btnPrevPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginEnd="8dp"
android:background="@android:drawable/btn_default"
android:src="@android:drawable/ic_media_rew"
android:contentDescription="Previous Page"
android:layout_marginEnd="12dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
android:scaleType="fitCenter" />
<!-- Page Numbers Container -->
<LinearLayout
@@ -273,32 +170,32 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:minHeight="48dp" />
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" />
<!-- Next Page Button -->
<ImageButton
android:id="@+id/btnNextPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="8dp"
android:background="@android:drawable/btn_default"
android:src="@android:drawable/ic_media_ff"
android:contentDescription="Next Page"
android:layout_marginStart="12dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
android:scaleType="fitCenter" />
<!-- Last Page Button -->
<ImageButton
android:id="@+id/btnLastPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="8dp"
android:background="@android:drawable/btn_default"
android:src="@android:drawable/ic_media_next"
android:contentDescription="Last Page"
android:layout_marginStart="8dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
android:scaleType="fitCenter" />
</LinearLayout>
</LinearLayout>

View File

@@ -3,115 +3,102 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:background="#FFFFFF"
android:orientation="vertical">
<!-- Header with solid red background -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#E53E3E">
<!-- Custom AppBar -->
<include layout="@layout/component_appbar" />
<!-- Back button -->
<ImageView
android:id="@+id/btn_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
android:src="@android:drawable/ic_menu_revert"
app:tint="@android:color/white" />
<!-- Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_toEndOf="@id/btn_back"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="16sp" />
<!-- Settlement Card -->
<!-- Settlement Card positioned to overlap -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="64dp"
android:layout_marginTop="-70dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
android:layout_marginBottom="5dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:background="#4299E1"
android:orientation="vertical"
android:padding="16dp">
<!-- SETTLEMENT Title -->
<TextView
android:id="@+id/settlement_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SETTLEMENT"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
android:textSize="16sp"
android:fontFamily="@font/inter"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- Description -->
<TextView
android:id="@+id/settlement_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Rekap dan kirim transaksi hari ini"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
android:textSize="12sp"
android:fontFamily="@font/inter"
android:textStyle="normal"
app:layout_constraintTop_toBottomOf="@id/settlement_title"
app:layout_constraintStart_toStartOf="parent" />
<!-- Total Amount -->
<TextView
android:id="@+id/tv_total_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="12dp"
android:text="3.506.500"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal">
android:fontFamily="@font/inter"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/settlement_desc"
app:layout_constraintStart_toStartOf="parent" />
<!-- Transaction Count Label -->
<TextView
android:id="@+id/label_jumlah"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Jumlah Transaksi"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
</LinearLayout>
android:textSize="12sp"
android:fontFamily="@font/inter"
android:textStyle="normal"
app:layout_constraintTop_toBottomOf="@id/tv_total_amount"
app:layout_constraintStart_toStartOf="parent" />
<!-- Transaction Count Value -->
<TextView
android:id="@+id/tv_total_transactions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="65"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
android:fontFamily="@font/inter"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/label_jumlah"
app:layout_constraintStart_toStartOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<!-- Content -->
<!-- Content Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
@@ -119,6 +106,7 @@
android:orientation="vertical"
android:padding="16dp">
<!-- Main Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -126,22 +114,27 @@
android:text="Informasi Ringkasan Transaksi Hari Ini"
android:textColor="#333333"
android:textSize="16sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
<!-- Description Text -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Pastikan Anda melakukan settlement untuk menyelesaikan transaksi harian."
android:textColor="#666666"
android:textSize="12sp" />
android:textSize="12sp"
android:fontFamily="@font/inter"
android:textStyle="normal" />
<!-- Transaction List -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_weight="1"
android:background="@android:color/transparent" />
</LinearLayout>
@@ -155,6 +148,9 @@
android:text="Selanjutnya"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
android:fontFamily="@font/inter"
android:textStyle="bold"
android:clickable="true"
android:focusable="true" />
</LinearLayout>

View File

@@ -0,0 +1,395 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:orientation="vertical">
<!-- Custom AppBar -->
<include layout="@layout/component_appbar" />
<!-- Header Card with Store Info -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="-70dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:orientation="vertical"
android:padding="20dp">
<!-- Payvora PRO Logo -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="P"
android:textColor="#E53E3E"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="@font/inter" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ayvora"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PRO"
android:textColor="#E53E3E"
android:textSize="14sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginStart="4dp" />
</LinearLayout>
<!-- Store Name -->
<TextView
android:id="@+id/tv_store_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="TOKO KLONTONG PAK EKO"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginBottom="4dp" />
<!-- Store Location -->
<TextView
android:id="@+id/tv_store_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Ciputat Baru, Tangsel"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:layout_marginBottom="12dp" />
<!-- MID and TID Row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MID: "
android:textColor="#666666"
android:textSize="12sp"
android:fontFamily="@font/inter" />
<TextView
android:id="@+id/tv_mid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12345678901"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="end">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TID: "
android:textColor="#666666"
android:textSize="12sp"
android:fontFamily="@font/inter" />
<TextView
android:id="@+id/tv_tid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12345678901"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter" />
</LinearLayout>
</LinearLayout>
<!-- Settlement Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="SETTLEMENT"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginBottom="4dp" />
<!-- Date and Time -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Date: "
android:textColor="#666666"
android:textSize="12sp"
android:fontFamily="@font/inter" />
<TextView
android:id="@+id/tv_settlement_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="27 Agustus 2025"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter"
android:layout_marginEnd="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Time: "
android:textColor="#666666"
android:textSize="12sp"
android:fontFamily="@font/inter" />
<TextView
android:id="@+id/tv_settlement_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="21:59:40"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter" />
</LinearLayout>
<!-- Total Transaksi EDC Anda -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Total transaksi EDC Anda"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold"
android:fontFamily="@font/inter" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Scrollable Content -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<!-- Settlement Details List -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_settlement_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<!-- Summary Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#F7FAFC"
android:padding="16dp"
android:layout_marginBottom="16dp">
<!-- Total Transaksi Masuk -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Total Transaksi Masuk:"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter" />
<TextView
android:id="@+id/tv_total_masuk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp 1.800.000"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
</LinearLayout>
<!-- Total Transaksi Keluar -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Total Transaksi Keluar:"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter" />
<TextView
android:id="@+id/tv_total_keluar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp 5.800.000"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
</LinearLayout>
<!-- Biaya Admin -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Biaya Admin:"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter" />
<TextView
android:id="@+id/tv_biaya_admin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp 15.000"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
</LinearLayout>
<!-- Divider -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E2E8F0"
android:layout_marginBottom="12dp" />
<!-- Total -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TOTAL"
android:textColor="#333333"
android:textSize="16sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_grand_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3.506.500"
android:textColor="#E53E3E"
android:textSize="18sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- Bottom Button -->
<Button
android:id="@+id/btn_send_settlement"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:background="#E53E3E"
android:text="Kirim Settlement"
android:textColor="@android:color/white"
android:textSize="16sp"
android:fontFamily="@font/inter"
android:textStyle="bold"
android:clickable="true"
android:focusable="true" />
</LinearLayout>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="50dp"
android:background="#DE0701"
android:fitsSystemWindows="true">
<!-- Back Navigation -->
<LinearLayout
android:id="@+id/back_navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- Back Arrow -->
<ImageView
android:id="@+id/backArrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_arrow_back"
android:contentDescription="Back"
app:tint="@android:color/white" />
<!-- Title Text -->
<TextView
android:id="@+id/appbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="10sp"
android:fontFamily="@font/inter"
android:textStyle="normal" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -3,67 +3,70 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<!-- Left Content -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp"
android:gravity="center_vertical">
<!-- Tanggal (ditambahkan detik) -->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="09:00, 07-05-2025"
android:textColor="#333333"
android:textSize="14sp" />
android:layout_weight="2"
android:text="HH.mm.ss, dd MMM yyyy"
android:textColor="#000000"
android:fontFamily="@font/inter"
android:textStyle="normal"
android:textSize="10sp" />
<!-- Jumlah -->
<TextView
android:id="@+id/tv_amount"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Rp. 78.000"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Center Content -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:orientation="vertical">
android:layout_weight="2"
android:text="Rp. 0"
android:textColor="#000000"
android:fontFamily="@font/inter"
android:textStyle="normal"
android:textSize="10sp"
android:gravity="center_horizontal" />
<!-- Channel -->
<TextView
android:id="@+id/tv_channel"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Kredit"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
android:layout_weight="1.5"
android:text="-"
android:textColor="#000000"
android:fontFamily="@font/inter"
android:textStyle="normal"
android:textSize="10sp"
android:gravity="center_horizontal" />
</LinearLayout>
<!-- Right Content -->
<!-- Status -->
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="Berhasil"
android:textColor="#4CAF50"
android:textSize="12sp"
android:textStyle="bold" />
android:layout_weight="1.5"
android:text="-"
android:textColor="#000000"
android:fontFamily="@font/inter"
android:textStyle="normal"
android:textSize="10sp"
android:gravity="end" />
</LinearLayout>
<!-- Garis pemisah -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#DDDDDD" />
</LinearLayout>

View File

@@ -1,178 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Header Row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_reference_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Ref: 197870"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SUCCESS"
android:textColor="#4CAF50"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Amount -->
<TextView
android:id="@+id/tv_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Rp. 2.018.619"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold" />
<!-- Details Grid -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="vertical">
<!-- Row 1 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Channel"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Other Payment"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Waktu"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="16/05/2025 05:40"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<!-- Row 2 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Merchant"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_merchant"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEST MERCHANT"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Issuer"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_issuer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BANK MANDIRI"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:gravity="center_vertical">
<!-- Waktu (ditambahkan detik) -->
<TextView
android:id="@+id/tv_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="09.00.00, 07-05-2025"
android:textColor="#000000"
android:fontFamily="@font/inter"
android:textSize="13sp" />
<!-- Nominal -->
<TextView
android:id="@+id/tv_amount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Rp. 78.000"
android:textColor="#000000"
android:gravity="center"
android:fontFamily="@font/inter"
android:textSize="13sp" />
<!-- Channel / Tipe -->
<TextView
android:id="@+id/tv_channel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:text="Kredit"
android:textColor="#000000"
android:gravity="center"
android:fontFamily="@font/inter"
android:textSize="13sp" />
<!-- Status -->
<TextView
android:id="@+id/tv_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:text="Berhasil"
android:textColor="#009966"
android:gravity="end"
android:fontFamily="@font/inter"
android:textSize="13sp" />
</LinearLayout>
<!-- Garis Bawah -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#DDDDDD" />
</LinearLayout>

View File

@@ -7,25 +7,41 @@
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
android:padding="16dp"
android:layout_marginBottom="1dp">
<!-- Icon Container -->
<androidx.cardview.widget.CardView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
android:layout_marginEnd="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7FAFC"
android:gravity="center">
<!-- Icon -->
<ImageView
android:id="@+id/iv_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:background="#F7FAFC"
android:padding="8dp"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_gallery"
app:tint="#E53E3E" />
<!-- Content -->
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Content Section -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
@@ -35,11 +51,12 @@
android:text="Kartu Kredit"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
</LinearLayout>
<!-- Amount and Count -->
<!-- Amount and Transaction Count -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -54,6 +71,7 @@
android:text="Rp. 200.000"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
<TextView
@@ -64,7 +82,9 @@
android:layout_marginTop="2dp"
android:text="13 Transaksi"
android:textColor="#666666"
android:textSize="12sp" />
android:textSize="12sp"
android:fontFamily="@font/inter"
android:textStyle="normal" />
</LinearLayout>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:layout_marginBottom="8dp"
android:background="@android:color/white">
<!-- Payment Method Title -->
<TextView
android:id="@+id/tv_payment_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kartu Kredit"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginBottom="8dp" />
<!-- Total Nominal Row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Total Nominal"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:paddingStart="16dp" />
<TextView
android:id="@+id/tv_total_nominal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="200.000"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
</LinearLayout>
<!-- Jumlah Transaksi Row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Jumlah Transaksi"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:paddingStart="16dp" />
<TextView
android:id="@+id/tv_jumlah_transaksi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="14"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:textStyle="bold" />
</LinearLayout>
<!-- Divider -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E2E8F0"
android:layout_marginTop="4dp" />
</LinearLayout>