Compare commits
36 Commits
6f78b6df3f
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
| ad08e80ae0 | |||
| ccfd3a09eb | |||
| dd57975908 | |||
| a49aab14f8 | |||
| 72b39fd9c8 | |||
| 40d0fc2402 | |||
| 78f9e95c3f | |||
| 69fd69ac3a | |||
| 0e86870b8b | |||
| 1c1d580a38 | |||
| 6d519d96cf | |||
| 64b666869e | |||
| a674574031 | |||
| 8cef8fdb22 | |||
| e0aec6e840 | |||
| 538249fc57 | |||
| a38cea065f | |||
| 671b585fe5 | |||
| c18fd2d831 | |||
| c033a26516 | |||
| f64779755a | |||
| 22d0409c0a | |||
| 2803182a02 | |||
| 960f64ee81 | |||
| 9dac55d07a | |||
| ddf76d2540 | |||
| b2442ada48 | |||
| a52f56e154 | |||
| 4209b193d7 | |||
| 44225f1d67 | |||
| 88069c0b56 | |||
| 6f98a91372 | |||
| 597921e32b | |||
| 3ac3598359 | |||
| 6660fca373 | |||
| edb1c6d09b |
27
.env.example
Normal file
27
.env.example
Normal 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
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
*.iml
|
||||
.gradle
|
||||
.env
|
||||
*.env
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=".histori.HistoryActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".HistoryDetailActivity"
|
||||
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>
|
||||
</manifest>
|
||||
|
||||
|
||||
323
app/src/main/java/com/example/bdkipoc/LoginActivity.java
Normal file
323
app/src/main/java/com/example/bdkipoc/LoginActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, "==================================");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()) {
|
||||
// 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:
|
||||
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":
|
||||
// ✅ IMPROVED: For QRIS, try to get real acquirer or return generic
|
||||
return "qris"; // Will be processed by receipt activity to find real acquirer
|
||||
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";
|
||||
return "BCA";
|
||||
case "mandiri":
|
||||
return "mandiri";
|
||||
return "Mandiri";
|
||||
case "bni":
|
||||
return "bni";
|
||||
return "BNI";
|
||||
case "bri":
|
||||
return "bri";
|
||||
return "BRI";
|
||||
case "permata":
|
||||
return "permata";
|
||||
return "Permata";
|
||||
case "cimb":
|
||||
return "cimb";
|
||||
return "CIMB Niaga";
|
||||
case "danamon":
|
||||
return "danamon";
|
||||
return "Danamon";
|
||||
case "bsi":
|
||||
return "bsi";
|
||||
return "BSI";
|
||||
case "debit":
|
||||
return "visa"; // Default for debit cards
|
||||
case "debit_card":
|
||||
return "Visa"; // Default for debit cards
|
||||
case "credit":
|
||||
return "mastercard"; // Default for credit cards
|
||||
case "credit_card":
|
||||
return "Mastercard"; // Default for credit cards
|
||||
default:
|
||||
return "unknown"; // ✅ CHANGED: unknown instead of gopay for unknown channels
|
||||
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";
|
||||
case "BCA":
|
||||
case "MANDIRI":
|
||||
case "BNI":
|
||||
case "BRI": return "Kartu " + channelCode.toUpperCase();
|
||||
case "CASH": return "Tunai";
|
||||
case "EDC": return "EDC";
|
||||
default:
|
||||
if (channelCategory != null && !channelCategory.isEmpty()) {
|
||||
return channelCategory.toUpperCase();
|
||||
}
|
||||
return channelCode.toUpperCase();
|
||||
// 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":
|
||||
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()) {
|
||||
String mappedFromCategory = mapChannelCategoryToPaymentMethod(channelCategory);
|
||||
Log.d("ReprintActivity", "✅ Mapped from channelCategory: " + mappedFromCategory);
|
||||
return mappedFromCategory;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -528,37 +528,128 @@ 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()) {
|
||||
case "QRIS":
|
||||
return "QRIS";
|
||||
// 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";
|
||||
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 "CASH":
|
||||
return "Tunai";
|
||||
case "EDC":
|
||||
return "EDC";
|
||||
case "RETAIL_OUTLET":
|
||||
// ✅ SPECIAL HANDLING: For RETAIL_OUTLET, determine by context
|
||||
return determinePaymentMethodFromCategory(channelCategory);
|
||||
default:
|
||||
Log.d("ReprintAdapterActivity", "🔍 Unknown channelCode: " + code + ", trying channelCategory");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Use channelCategory as fallback
|
||||
if (channelCategory != null && !channelCategory.isEmpty()) {
|
||||
return mapChannelCategoryToPaymentMethod(channelCategory);
|
||||
}
|
||||
|
||||
// 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 "BCA":
|
||||
return "BCA";
|
||||
case "MANDIRI":
|
||||
return "Mandiri";
|
||||
case "BNI":
|
||||
return "BNI";
|
||||
case "BRI":
|
||||
return "BRI";
|
||||
case "CASH":
|
||||
return "Tunai";
|
||||
case "EDC":
|
||||
return "EDC";
|
||||
case "E_MONEY":
|
||||
case "EMONEY":
|
||||
return "E-Money";
|
||||
case "BANK_TRANSFER":
|
||||
return "Transfer Bank";
|
||||
case "VIRTUAL_ACCOUNT":
|
||||
return "Virtual Account";
|
||||
default:
|
||||
// If channel category is available, use it as fallback
|
||||
if (channelCategory != null && !channelCategory.isEmpty()) {
|
||||
return channelCategory.toUpperCase();
|
||||
}
|
||||
return channelCode.toUpperCase();
|
||||
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
|
||||
public int getItemCount() {
|
||||
return transactionList.size();
|
||||
|
||||
@@ -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
|
||||
// Add to both lists (since we're limiting to 10 in API call)
|
||||
historyList.add(historyItem);
|
||||
fullHistoryData.add(historyItem);
|
||||
|
||||
// Add first 10 to display list
|
||||
if (i < 10) {
|
||||
historyList.add(historyItem);
|
||||
}
|
||||
|
||||
totalAmountArray[0] += (long) amountValue;
|
||||
totalTransactionsArray[0]++;
|
||||
}
|
||||
|
||||
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,24 +506,33 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return historyList != null ? historyList.size() : 0;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,11 +71,8 @@ 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) {
|
||||
super.onCreate(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,8 +386,9 @@ public class QrisActivity extends AppCompatActivity {
|
||||
|
||||
private boolean isValidServerKey(String serverKey) {
|
||||
return serverKey != null &&
|
||||
serverKey.startsWith("SB-Mid-server-") &&
|
||||
serverKey.length() > 20;
|
||||
(serverKey.startsWith("SB-Mid-server-") || // Sandbox format
|
||||
serverKey.startsWith("Mid-server-")) && // Production format
|
||||
serverKey.length() > 20;
|
||||
}
|
||||
|
||||
private String generateSignature(String orderId, String statusCode, String grossAmount, String serverKey) {
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -77,6 +82,35 @@ public class ResultTransactionActivity extends AppCompatActivity {
|
||||
private long taxAmount = 0;
|
||||
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) {
|
||||
@@ -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() {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
6
app/src/main/res/drawable/border_button_red.xml
Normal file
6
app/src/main/res/drawable/border_button_red.xml
Normal 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>
|
||||
@@ -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" />
|
||||
</shape>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
6
app/src/main/res/drawable/copyable_text_background.xml
Normal file
6
app/src/main/res/drawable/copyable_text_background.xml
Normal 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>
|
||||
BIN
app/src/main/res/drawable/ic_logo_toko.png
Normal file
BIN
app/src/main/res/drawable/ic_logo_toko.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
10
app/src/main/res/drawable/ic_visibility.xml
Normal file
10
app/src/main/res/drawable/ic_visibility.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/ic_visibility_off.xml
Normal file
10
app/src/main/res/drawable/ic_visibility_off.xml
Normal 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>
|
||||
BIN
app/src/main/res/drawable/ic_whatsapp.png
Normal file
BIN
app/src/main/res/drawable/ic_whatsapp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
8
app/src/main/res/drawable/oval_blur_decoration.xml
Normal file
8
app/src/main/res/drawable/oval_blur_decoration.xml
Normal 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>
|
||||
13
app/src/main/res/drawable/rounded_bottom_background.xml
Normal file
13
app/src/main/res/drawable/rounded_bottom_background.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/tab_active_bg.xml
Normal file
8
app/src/main/res/drawable/tab_active_bg.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/tab_inactive_bg.xml
Normal file
8
app/src/main/res/drawable/tab_inactive_bg.xml
Normal 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>
|
||||
518
app/src/main/res/layout/activity_bantuan.xml
Normal file
518
app/src/main/res/layout/activity_bantuan.xml
Normal 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>
|
||||
305
app/src/main/res/layout/activity_bantuan_form.xml
Normal file
305
app/src/main/res/layout/activity_bantuan_form.xml
Normal 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>
|
||||
@@ -3,137 +3,125 @@
|
||||
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
|
||||
<!-- 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="200dp"
|
||||
android:background="#E53E3E">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="-70dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="6dp">
|
||||
|
||||
<!-- 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" />
|
||||
|
||||
<!-- History Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/card_background"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#4299E1"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<!-- 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="20sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<!-- Lihat Detail -->
|
||||
<TextView
|
||||
android:id="@+id/btn_lihat_detail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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" />
|
||||
|
||||
<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="Transaksi Hari Ini"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
<!-- 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="10sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Total Transaksi"
|
||||
android:textColor="#E0FFFFFF"
|
||||
android:textSize="12sp" />
|
||||
<!-- 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 0"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toBottomOf="@id/label_total"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<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:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
<!-- Label Jumlah Transaksi -->
|
||||
<TextView
|
||||
android:id="@+id/label_jumlah"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Jumlah Transaksi"
|
||||
android:textColor="#E0FFFFFF"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="normal"
|
||||
android:textSize="10sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_total_amount"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Jumlah Transaksi"
|
||||
android:textColor="#E0FFFFFF"
|
||||
android:textSize="12sp" />
|
||||
<!-- Nilai Jumlah Transaksi -->
|
||||
<TextView
|
||||
android:id="@+id/tv_total_transactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@android:color/white"
|
||||
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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_total_transactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="30"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</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.cardview.widget.CardView>
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<!-- Content -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
56
app/src/main/res/layout/activity_history_list.xml
Normal file
56
app/src/main/res/layout/activity_history_list.xml
Normal 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>
|
||||
356
app/src/main/res/layout/activity_info_toko.xml
Normal file
356
app/src/main/res/layout/activity_info_toko.xml
Normal 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>
|
||||
211
app/src/main/res/layout/activity_login.xml
Normal file
211
app/src/main/res/layout/activity_login.xml
Normal 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>
|
||||
@@ -28,81 +28,180 @@
|
||||
<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"/>
|
||||
|
||||
|
||||
<!-- Merchant Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
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"
|
||||
android:textColor="#9FA4A9"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
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"/>
|
||||
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">
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_lainnya">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/scan_bayar_content"
|
||||
<ImageView
|
||||
android:id="@+id/banner_qris"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<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: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>
|
||||
@@ -1,241 +1,271 @@
|
||||
<?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"
|
||||
tools:context=".QrisResultActivity">
|
||||
android:fillViewport="true"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<!-- Header Background -->
|
||||
<View
|
||||
android:id="@+id/header_background"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:background="#E31937"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<!-- 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:padding="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
tools:context=".QrisResultActivity">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/backArrow"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_arrow_back"
|
||||
android:layout_marginEnd="8dp" />
|
||||
<!-- Header Background -->
|
||||
<View
|
||||
android:id="@+id/header_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:background="#E31937"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toolbarTitle"
|
||||
<!-- Back Navigation -->
|
||||
<LinearLayout
|
||||
android:id="@+id/back_navigation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Generate QR"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Main Content Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/main_card"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
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">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- QRIS Logo -->
|
||||
<TextView
|
||||
android:id="@+id/qris_logo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="QRIS"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#000000"
|
||||
android:letterSpacing="0.1"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
<!-- QR Code -->
|
||||
<ImageView
|
||||
android:id="@+id/qrImageView"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="240dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#FFFFFF"
|
||||
android:padding="8dp" />
|
||||
android:id="@+id/backArrow"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_arrow_back"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<!-- Amount -->
|
||||
<TextView
|
||||
android:id="@+id/amountTextView"
|
||||
android:id="@+id/toolbarTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RP.200.000"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
<!-- Timer -->
|
||||
<TextView
|
||||
android:id="@+id/timerTextView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:text="60"
|
||||
android:text="Generate QR"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#666666"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/timer_circle_background"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
<!-- Main Content Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/main_card"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/header_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="Batalkan"
|
||||
android:textColor="#E31937"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:background="@drawable/button_cancel_background"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textAllCaps="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- Hidden Elements for Functionality -->
|
||||
<TextView
|
||||
android:id="@+id/referenceTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<!-- QRIS Logo -->
|
||||
<TextView
|
||||
android:id="@+id/qris_logo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="QRIS"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#000000"
|
||||
android:letterSpacing="0.1"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<!-- QR Code -->
|
||||
<ImageView
|
||||
android:id="@+id/qrImageView"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="240dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#FFFFFF"
|
||||
android:padding="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrStatusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<!-- Amount -->
|
||||
<TextView
|
||||
android:id="@+id/amountTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RP.200.000"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<!-- Timer -->
|
||||
<TextView
|
||||
android:id="@+id/timerTextView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:text="60"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#666666"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/timer_circle_background"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
<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" />
|
||||
<!-- 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" />
|
||||
|
||||
<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" />
|
||||
<!-- 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>
|
||||
|
||||
<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) -->
|
||||
<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"
|
||||
<!-- Cancel Button -->
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:scaleType="centerInside"/>
|
||||
android:text="Batalkan"
|
||||
android:textColor="#E31937"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:background="@drawable/button_cancel_background"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textAllCaps="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<!-- Success Message -->
|
||||
<TextView
|
||||
android:id="@+id/success_message"
|
||||
<!-- Add these inside your ConstraintLayout -->
|
||||
<Button
|
||||
android:id="@+id/downloadQrisButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pembayaran Berhasil"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
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"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrStatusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
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 Overlay -->
|
||||
<LinearLayout
|
||||
android:id="@+id/success_screen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.02"/>
|
||||
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">
|
||||
|
||||
</LinearLayout>
|
||||
<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"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<TextView
|
||||
android:id="@+id/success_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pembayaran Berhasil"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.02"/>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
||||
@@ -4,301 +4,198 @@
|
||||
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_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Filter"
|
||||
android:textSize="14sp"
|
||||
android:layout_weight="1"
|
||||
android:text=""
|
||||
android:textColor="#666666"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center" />
|
||||
android:textSize="12sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginStart="4dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</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: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" />
|
||||
|
||||
</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" />
|
||||
|
||||
<!-- 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">
|
||||
|
||||
<!-- First Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnFirstPage"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_media_previous"
|
||||
android:contentDescription="First Page"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless" />
|
||||
|
||||
<!-- Previous Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnPrevPage"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_media_rew"
|
||||
android:contentDescription="Previous Page"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless" />
|
||||
|
||||
<!-- Page Numbers Container -->
|
||||
<!-- Navigation Controls -->
|
||||
<LinearLayout
|
||||
android:id="@+id/pageNumbersContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:minHeight="48dp" />
|
||||
android:gravity="center">
|
||||
|
||||
<!-- Next Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnNextPage"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_media_ff"
|
||||
android:contentDescription="Next Page"
|
||||
android:layout_marginStart="12dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless" />
|
||||
<!-- First Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnFirstPage"
|
||||
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:scaleType="fitCenter" />
|
||||
|
||||
<!-- Last Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnLastPage"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
android:contentDescription="Last Page"
|
||||
android:layout_marginStart="8dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless" />
|
||||
<!-- Previous Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnPrevPage"
|
||||
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:scaleType="fitCenter" />
|
||||
|
||||
<!-- Page Numbers Container -->
|
||||
<LinearLayout
|
||||
android:id="@+id/pageNumbersContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<!-- Next Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnNextPage"
|
||||
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:scaleType="fitCenter" />
|
||||
|
||||
<!-- Last Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnLastPage"
|
||||
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:scaleType="fitCenter" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -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
|
||||
<!-- Custom AppBar -->
|
||||
<include layout="@layout/component_appbar" />
|
||||
|
||||
<!-- Settlement Card positioned to overlap -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:background="#E53E3E">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="-70dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="6dp">
|
||||
|
||||
<!-- 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 -->
|
||||
<androidx.cardview.widget.CardView
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#4299E1"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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="16sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SETTLEMENT"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
<!-- 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:fontFamily="@font/inter"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintTop_toBottomOf="@id/settlement_title"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
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" />
|
||||
<!-- Total Amount -->
|
||||
<TextView
|
||||
android:id="@+id/tv_total_amount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="3.506.500"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toBottomOf="@id/settlement_desc"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_total_amount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="3.506.500"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
<!-- 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"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_total_amount"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:orientation="horizontal">
|
||||
<!-- 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:fontFamily="@font/inter"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toBottomOf="@id/label_jumlah"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Jumlah Transaksi"
|
||||
android:textColor="#E0FFFFFF"
|
||||
android:textSize="12sp" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_total_transactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="65"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
</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>
|
||||
395
app/src/main/res/layout/activity_settlement_detail.xml
Normal file
395
app/src/main/res/layout/activity_settlement_detail.xml
Normal 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>
|
||||
47
app/src/main/res/layout/component_appbar_small.xml
Normal file
47
app/src/main/res/layout/component_appbar_small.xml
Normal 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>
|
||||
@@ -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">
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Left Content -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
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" />
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:id="@+id/tv_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.5"
|
||||
android:text="-"
|
||||
android:textColor="#000000"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="normal"
|
||||
android:textSize="10sp"
|
||||
android:gravity="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Right Content -->
|
||||
<TextView
|
||||
android:id="@+id/tv_status"
|
||||
android:layout_width="wrap_content"
|
||||
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" />
|
||||
|
||||
<!-- Garis pemisah -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#DDDDDD" />
|
||||
</LinearLayout>
|
||||
@@ -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>
|
||||
69
app/src/main/res/layout/item_history_list.xml
Normal file
69
app/src/main/res/layout/item_history_list.xml
Normal 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>
|
||||
@@ -7,25 +7,41 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="1dp">
|
||||
|
||||
<!-- Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
<!-- Icon Container -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="#F7FAFC"
|
||||
android:padding="8dp"
|
||||
android:src="@android:drawable/ic_menu_gallery"
|
||||
app:tint="#E53E3E" />
|
||||
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">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_menu_gallery"
|
||||
app:tint="#E53E3E" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Content -->
|
||||
<!-- 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,8 +82,10 @@
|
||||
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>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
87
app/src/main/res/layout/item_settlement_detail.xml
Normal file
87
app/src/main/res/layout/item_settlement_detail.xml
Normal 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>
|
||||
Reference in New Issue
Block a user