[FEAT] Phase2 發票:完整移植 Travis 發票設定新流程

- 新增 InvoiceTypeDialog(統一類型入口:載具/捐贈/統編)
- 新增 InvoiceDonationDialog + RecyclerDonationListAdpter(捐贈機構 RecyclerView)
- 新增 dialog_invoice_type / dialog_donation 版面 + bg_selected_item
- BuyDialog 發票入口改走 InvoiceTypeDialog(取代直接進 InvoiceBarcodeDialog)
- InvoiceBarcodeDialog:捐贈鈕改導向新 InvoiceDonationDialog,移除舊 inline
  showDonationDialog(其引用的舊 dialog_donation id 已不存在)
- 沿用現有 main 發票資料流(getInvoiceData/setLoveCode、ReportFlowInfoData)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-10 16:18:21 +08:00
parent 73e2b55c53
commit 33d79076a6
8 changed files with 947 additions and 161 deletions

View File

@ -437,7 +437,7 @@ public class BuyDialog extends DialogAbstract {
} else {
if (MyApp.getInstance().getDevSet().isShoppingCar()) {
if (MyApp.getInstance().getDevSet().isInvoice()) {
changeDialog(InvoiceBarcodeDialog.class);
changeDialog(InvoiceTypeDialog.class);
} else {
changeCheckout();
}
@ -447,12 +447,12 @@ public class BuyDialog extends DialogAbstract {
try {
SlotField slotField = SlotField.getSlotField(buyData.getField());
if (slotField.isInvoice()) {
changeDialog(InvoiceBarcodeDialog.class);
changeDialog(InvoiceTypeDialog.class);
} else {
changeCheckout();
}
} catch (LogsEmptyException e) {
changeDialog(InvoiceBarcodeDialog.class);
changeDialog(InvoiceTypeDialog.class);
}
} else {
changeCheckout();

View File

@ -179,7 +179,7 @@ public class InvoiceBarcodeDialog extends DialogAbstract {
binding.buttonCustomerIdentifier.setOnClickListener(v -> changeCustomerIdentifier());
// binding.buttonDonation.setOnClickListener(v -> changeDonation());
binding.buttonDonation.setOnClickListener(v -> showDonationDialog());
binding.buttonDonation.setOnClickListener(v -> changeDialog(InvoiceDonationDialog.class));
binding.buttonCancel.setOnClickListener(v -> changeBuy());
binding.buttonInputVehicle.setOnClickListener(v -> showInputVehicleDialog());
@ -295,116 +295,6 @@ public class InvoiceBarcodeDialog extends DialogAbstract {
changeCheckout();
}
private void showDonationDialog() {
closeDialogCountdown.shutdown();
AlertDialog.Builder builder = new AlertDialog.Builder(getCtx());
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_donation, null);
builder.setView(dialogView);
CheckBox checkboxDefault = dialogView.findViewById(R.id.checkboxDefault);
Spinner spinnerCharity = dialogView.findViewById(R.id.spinnerCharity);
TextView tvlovecode = dialogView.findViewById(R.id.tvlovecode);
// 1. 建立捐贈機構對應表
// 假設這是你的捐贈機構清單
List<String> organizationNames = new ArrayList<>(donationMap.keySet());
ArrayAdapter<String> adapter = new ArrayAdapter<>( getCtx(),
R.layout.spinner_item, // 改成自訂 layout
R.id.spinnerText,
organizationNames);
adapter.setDropDownViewResource(R.layout.spinner_item); // 下拉選單也套用
spinnerCharity.setAdapter(adapter);
// 2. 預設勾選 checkbox並禁用 spinner
checkboxDefault.setChecked(true);
spinnerCharity.setEnabled(false);
// 3. 隨機一組愛心碼並立刻寫入
Map.Entry<String, String> randomEntry = getRandomDonationEntry(donationMap);
if (randomEntry != null) {
MyApp.getInstance().getInvoiceData().setLoveCode(randomEntry.getValue());
MyApp.getInstance().getReportFlowInfoData().setInvoiceInfo("loveCode:" + MyApp.getInstance().getInvoiceData().getLoveCode());
// Toast.makeText(getCtx(), "預設捐贈:" + randomEntry.getKey(), Toast.LENGTH_SHORT).show();
tvlovecode.setText("捐贈機構:"+randomEntry.getKey());
}
// 自訂 Title
TextView title = new TextView(getCtx());
title.setText("選擇捐贈方式");
title.setTextSize(24); // 放大字體
title.setPadding(40, 40, 40, 40);
title.setGravity(Gravity.CENTER);
donationDialog = new AlertDialog.Builder(getCtx())
.setCustomTitle(title) // 使用自訂 Title
.setView(dialogView)
.setPositiveButton("確定", (d, which) -> {
if (!checkboxDefault.isChecked()) {
String selectedName = (String) spinnerCharity.getSelectedItem();
String loveCode = donationMap.get(selectedName);
MyApp.getInstance().getInvoiceData().setLoveCode(loveCode);
MyApp.getInstance().getReportFlowInfoData().setInvoiceInfo("loveCode:" + MyApp.getInstance().getInvoiceData().getLoveCode());
Toast.makeText(getCtx(), "選擇:" + selectedName, Toast.LENGTH_SHORT).show();
}
// 若是勾選預設之前就寫入過不需處理
closeDialogCountdown.start(40); // 再次開始倒數
changeCheckout();
})
.setNegativeButton("取消", (d, which) -> {
// 使用者取消後重新開始倒數 40
closeDialogCountdown.start(40);
})
.create();
donationDialog.show();
Button positive = donationDialog.getButton(AlertDialog.BUTTON_POSITIVE);
Button negative = donationDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
if (positive != null) {
positive.setTextSize(24);
positive.setPadding(30, 20, 130, 20);
}
if (negative != null) {
negative.setTextSize(24);
negative.setPadding(30, 20, 30, 20);
}
// 放大框架 (寬度 90%)
Window window = donationDialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.copyFrom(window.getAttributes());
params.width = (int) (getCtx().getResources().getDisplayMetrics().widthPixels * 0.9);
params.height = (int) (getCtx().getResources().getDisplayMetrics().heightPixels * 0.6); // 高度 60%
window.setAttributes(params);
}
// 加入20秒後自動關閉 Dialog
Handler handler = new Handler();
Runnable timeoutRunnable = () -> {
if (donationDialog.isShowing()) {
donationDialog.dismiss();
Toast.makeText(getCtx(), "已逾時取消", Toast.LENGTH_SHORT).show();
}
};
handler.postDelayed(timeoutRunnable, 10000); // 20秒
// 4. Checkbox 切換時控制 Spinner
checkboxDefault.setOnCheckedChangeListener((buttonView, isChecked) -> {
spinnerCharity.setEnabled(!isChecked);
handler.removeCallbacks(timeoutRunnable); // 取消 timeout
});
spinnerCharity.setOnTouchListener((v, event) -> {
handler.removeCallbacks(timeoutRunnable); // 取消 timeout
return false;
});
}
private void showInputVehicleDialog() {
closeDialogCountdown.shutdown(); // 停止倒數避免中途跳掉

View File

@ -0,0 +1,334 @@
package com.unibuy.smartdevice.ui.dialog;
import android.content.Context;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.databinding.DialogDonationBinding;
import com.unibuy.smartdevice.ui.recycler.RecyclerDonationListAdpter;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class InvoiceDonationDialog extends DialogAbstract {
public enum Option {
TOAST(0),
COUNTDOWN_CANCEL(1),
SET_COUNTDOWN_TEXT(2),
INVOICE_SUCCESSFUL(4),
INVOICE_FAILED(5),
CHANGE_DISPATCH(6),
CHANGE_BUY(7),
CHANGE_CHECKOUT(8),
;
private int option;
public int getOption() {
return option;
}
Option(int option) {
this.option = option;
}
}
public static final Map<Integer, Option> optionMap = new HashMap<>();
static {
for (Option option : Option.values()) {
optionMap.put(option.getOption(), option);
}
}
@Override
protected Context setCtx() {
return getContext();
}
@Override
protected Class<? extends DialogAbstract> setCls() {
return getClass();
}
@Override
protected HandlerMain setHandlerMain() {
return new ToastHandlerMain(getCtx(), getLogs()) {
@Override
protected void execute(Context context, int commandCode, String message) {
if (MyApp.getInstance().getBuyList().size() <= 0) {
dialogCancel();
} else {
Option option = optionMap.get(commandCode);
switch (option) {
case TOAST:
super.execute(context, commandCode, message);
break;
case COUNTDOWN_CANCEL:
cancelOnThreadCountdown();
break;
case SET_COUNTDOWN_TEXT:
setButtonCancelText(Integer.valueOf(message));
break;
case INVOICE_SUCCESSFUL:
break;
case INVOICE_FAILED:
break;
case CHANGE_DISPATCH:
changeDispatch();
break;
case CHANGE_BUY:
changeBuy();
break;
case CHANGE_CHECKOUT:
changeCheckout();
break;
}
}
}
};
}
private DialogDonationBinding binding;
private CloseDialogOnThreadHandler closeDialogCountdown;
private LinkedHashMap<String, String> donationMap;
private List<String> organizationNames;
private RecyclerDonationListAdpter donationAdapter;
public InvoiceDonationDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
}
@Override
protected void onCreate(Context context) {
binding = DialogDonationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
donationMap = new LinkedHashMap<>();
intiDonationMap();
binding.buttonFinish.setOnClickListener(v -> changeOk());
binding.buttonCancel.setOnClickListener(v -> changeBuy());
// 隱藏切換 Tab 按鈕 InvoiceTypeDialog 統一管理入口
binding.buttonCustomerIdentifier.setVisibility(View.GONE);
binding.buttonDonation.setVisibility(View.GONE);
binding.buttonInputVehicle.setVisibility(View.GONE);
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
setupDonationSpinner();
}
private void updateButtonState(com.google.android.material.button.MaterialButton selectedButton) {
binding.buttonInputVehicle.setChecked(selectedButton.getId() == R.id.buttonInputVehicle);
binding.buttonDonation.setChecked(selectedButton.getId() == R.id.buttonDonation);
binding.buttonCustomerIdentifier.setChecked(selectedButton.getId() == R.id.buttonCustomerIdentifier);
}
public void intiDonationMap(){
donationMap.put("社團法人嘉義市腦性麻痺協會", "2855669");
donationMap.put("社團法人桃園市唐氏症家長協會", "520321");
donationMap.put("社團法人中華民國自閉症總會", "1577");
donationMap.put("社團法人臺北市蒲公英聽語協會", "71521");
donationMap.put("社團法人桃園市失智症關懷協會", "8185");
donationMap.put("財團法人台灣痲瘋救濟基金會", "1954");
donationMap.put("社團法人中華小腦萎縮症病友協會", "9972");
donationMap.put("社團法人高雄市唐氏症歡喜協會", "7717299");
donationMap.put("社團法人屏東縣腦性麻痺服務協會", "115");
donationMap.put("社團法人台南市智障者福利家長協進會", "2070");
donationMap.put("財團法人台灣肯納自閉症基金會", "1018");
donationMap.put("社團法人高雄市腦性麻痺服務協會", "29636");
donationMap.put("社團法人高雄市自閉症協進會", "8585885");
donationMap.put("社團法人臺灣自閉兒家庭關懷協會", "338518");
donationMap.put("社團法人臺東縣自閉症協進會", "3685");
donationMap.put("社團法人中華民國外傷性腦損傷照顧者關懷協會", "5841");
donationMap.put("財團法人天主教靈醫會私立聖嘉民啟智中心", "57880");
donationMap.put("社團法人新竹市智障福利協進會", "6684");
donationMap.put("社團法人屏東縣自閉症協進會", "7351024");
donationMap.put("社團法人台北市自閉症家長協會", "772");
donationMap.put("社團法人屏東縣啟智協進會", "159");
donationMap.put("社團法人花蓮縣自閉症協會", "8588");
donationMap.put("澎湖縣私立財團法人天主教澎湖教區附設惠民啟智中心", "9261790");
donationMap.put("財團法人新竹市私立天主教仁愛啟智中心", "227");
donationMap.put("社團法人中華民國唐氏症關愛者協會", "88321");
donationMap.put("財團法人天主教會台中教區附設南投縣私立復活啟智中心", "7717");
donationMap.put("財團法人喜憨兒社會福利基金會", "88432");
donationMap.put("社團法人花蓮縣智障福利協進會", "7756");
donationMap.put("社團法人雲林縣啟智協會", "1311");
donationMap.put("社團法人新竹縣智障福利協進會", "3170");
donationMap.put("社團法人台南市腦性麻痺之友協會", "815");
donationMap.put("社團法人台南市癲癇之友協會", "2350253");
donationMap.put("新北市自閉症服務協進會", "7688");
donationMap.put("社團法人台中市?智協進會", "450");
donationMap.put("社團法人台中市自閉症教育協進會", "1127");
donationMap.put("財團法人中華民國自閉症基金會", "402");
donationMap.put("社團法人台灣流浪貓狗關懷協會", "9958");
donationMap.put("社團法人台灣愛貓協會", "921314");
donationMap.put("社團法人台灣幸褔狗流浪中途協會", "856203");
donationMap.put("社團法人新北市流浪貓狗再生保護協會", "8866");
donationMap.put("社團法人台灣流浪動物希望協會", "119");
donationMap.put("社團法人高雄市幸福狗尾巴友善動物協會", "6848");
donationMap.put("社團法人臺南市貓狗流浪終點協會", "5866");
donationMap.put("社團法人台灣黑糖流浪動物救援協會", "958");
donationMap.put("社團法人台灣動物輔助活動及治療協會", "916");
donationMap.put("社團法人台灣友善動物協會", "9305");
donationMap.put("社團法人中華民國反棄養寵物協會", "11289");
donationMap.put("社團法人台灣流浪貓關懷協會", "71706");
donationMap.put("社團法人台灣浪愛不流浪關懷動物協會", "9458");
donationMap.put("台灣米樂流浪動物中途協會", "9925");
donationMap.put("社團法人中華民國寵物友善協會", "52982");
donationMap.put("社團法人台灣愛兔協會", "5822");
donationMap.put("社團法人台灣拾貓生命教育創新協會", "880222");
donationMap.put("社團法人台灣防止虐待動物協會", "1772");
donationMap.put("社團法人好好善待動物協會", "6666666");
donationMap.put("社團法人臺北市毛小孩幸福聯盟協會", "99891");
donationMap.put("社團法人台南市流浪動物愛護協會", "17966");
donationMap.put("中華民國流浪動物花園協會", "528");
donationMap.put("台灣流浪兔保護協會", "17922");
donationMap.put("社團法人台北市愛兔協會", "5222");
donationMap.put("社團法人臺灣照顧生命協會", "589");
donationMap.put("新竹市保護動物協會", "860713");
donationMap.put("社團法人中華民國動物福利環保協進會", "529529");
donationMap.put("財團法人中華民國兒童癌症基金會", "88888");
donationMap.put("中華民國癌病腫瘤患者扶助協會", "6088");
donationMap.put("財團法人臺灣癌症基金會", "1799");
}
private void setupDonationSpinner() {
organizationNames = new ArrayList<>(donationMap.keySet());
donationAdapter = new RecyclerDonationListAdpter(organizationNames, (name, position) -> {
closeDialogCountdown.setCountdown(40);
});
binding.recyclerViewCharity.setAdapter(donationAdapter);
// 隨機預選一個捐贈機構
int randomIndex = new Random().nextInt(organizationNames.size());
donationAdapter.setSelectedPosition(randomIndex);
binding.recyclerViewCharity.scrollToPosition(randomIndex);
}
public void changeInvoiceBarcode() {
closeDialogCountdown.shutdown();
new InvoiceBarcodeDialog(getCtx(), getSrcHandlerMain()).show();
cancel();
}
public void changeCustomerIdentifier() {
closeDialogCountdown.shutdown();
new InvoiceCustomerIdentifierDialog(getCtx(), getSrcHandlerMain()).show();
cancel();
}
public void changeDispatch() {
closeDialogCountdown.shutdown();
DispatchDialog dispatchDialog = new DispatchDialog(getContext(), getSrcHandlerMain());
dispatchDialog.show();
cancel();
}
public void changeOk() {
int selectedPosition = donationAdapter.getSelectedPosition();
if (selectedPosition < 0) {
Toast.makeText(getCtx(), "請選擇捐贈機構", Toast.LENGTH_SHORT).show();
return;
}
String selectedName = organizationNames.get(selectedPosition);
String loveCode = donationMap.get(selectedName);
MyApp.getInstance().getInvoiceData().setLoveCode(loveCode);
MyApp.getInstance().getReportFlowInfoData().setInvoiceInfo("loveCode:" + loveCode);
Toast.makeText(getCtx(), "選擇:" + selectedName, Toast.LENGTH_SHORT).show();
changeCheckout();
}
public void changeBuy() {
closeDialogCountdown.shutdown();
new InvoiceTypeDialog(getContext(), getSrcHandlerMain()).show();
cancel();
}
public void changeCheckout() {
closeDialogCountdown.shutdown();
int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType();
switch (flowType) {
case 1:
changeDialog(CheckoutNfcCardDialog.class);
break;
case 2:
changeDialog(CheckoutNfcRechargeDialog.class);
break;
case 3:
changeDialog(CheckoutEsunPayDialog.class);
break;
case 4:
changeDialog(CheckoutNfcPhoneDialog.class);
break;
case 9:
changeDialog(CashPayDialog.class);
break;
case 30:
changeDialog(CheckoutTapPayDialog.class);
break;
case 70:
changeDialog(CheckoutLinePayDialog.class);
break;
}
cancel();
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void cancelOnThreadCountdown() {
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("返回發票選擇(" + countdown + ")");
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {
public CloseDialogOnThreadHandler(HandlerMain handlerMain) {
super(handlerMain);
handlerMain.start(getClass().getSimpleName(), CashPayDialog.Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(getSrcCountdown()));
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void close(HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.COUNTDOWN_CANCEL.getOption(), "close dialog");
}
@Override
protected void execute(long countdown, HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(countdown));
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
}

View File

@ -0,0 +1,171 @@
package com.unibuy.smartdevice.ui.dialog;
import android.content.Context;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.databinding.DialogInvoiceTypeBinding;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import java.util.HashMap;
import java.util.Map;
/**
* 選擇發票開立方式介面
* 提供三個選項輸入載具 / 捐贈發票 / 公司統編
* 位於 BuyDialog選擇支付方式與各發票設定頁面之間
*/
public class InvoiceTypeDialog extends DialogAbstract {
public enum Option {
TOAST(0),
COUNTDOWN_CANCEL(1),
SET_COUNTDOWN_TEXT(2),
;
private int option;
public int getOption() {
return option;
}
Option(int option) {
this.option = option;
}
}
public static final Map<Integer, Option> optionMap = new HashMap<>();
static {
for (Option option : Option.values()) {
optionMap.put(option.getOption(), option);
}
}
@Override
protected Context setCtx() {
return getContext();
}
@Override
protected Class<? extends DialogAbstract> setCls() {
return getClass();
}
@Override
protected HandlerMain setHandlerMain() {
return new ToastHandlerMain(getCtx(), getLogs()) {
@Override
protected void execute(Context context, int commandCode, String message) {
if (MyApp.getInstance().getBuyList().size() <= 0) {
dialogCancel();
} else {
Option option = optionMap.get(commandCode);
switch (option) {
case TOAST:
super.execute(context, commandCode, message);
break;
case COUNTDOWN_CANCEL:
cancelOnThreadCountdown();
break;
case SET_COUNTDOWN_TEXT:
setButtonCancelText(Integer.valueOf(message));
break;
}
}
}
};
}
private DialogInvoiceTypeBinding binding;
private CloseDialogOnThreadHandler closeDialogCountdown;
public InvoiceTypeDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
}
@Override
protected void onCreate(Context context) {
binding = DialogInvoiceTypeBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 選擇輸入載具手機條碼
binding.cardVehicle.setOnClickListener(v -> {
closeDialogCountdown.shutdown();
changeDialog(InvoiceBarcodeDialog.class);
});
// 選擇捐贈發票
binding.cardDonation.setOnClickListener(v -> {
closeDialogCountdown.shutdown();
changeDialog(InvoiceDonationDialog.class);
});
// 選擇公司統編
binding.cardCustomerIdentifier.setOnClickListener(v -> {
closeDialogCountdown.shutdown();
changeDialog(InvoiceCustomerIdentifierDialog.class);
});
// 返回支付方式
binding.buttonCancel.setOnClickListener(v -> changeBuy());
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
}
/**
* 返回 BuyDialog選擇支付方式
*/
public void changeBuy() {
closeDialogCountdown.shutdown();
new BuyDialog(getContext(), getSrcHandlerMain()).show();
cancel();
}
/**
* 倒數逾時時清空購物車並關閉
*/
public void cancelOnThreadCountdown() {
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("返回支付方式(" + countdown + ")");
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {
public CloseDialogOnThreadHandler(HandlerMain handlerMain) {
super(handlerMain);
handlerMain.start(getClass().getSimpleName(), Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(getSrcCountdown()));
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void close(HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), Option.COUNTDOWN_CANCEL.getOption(), "close dialog");
}
@Override
protected void execute(long countdown, HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(countdown));
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
}

View File

@ -0,0 +1,84 @@
package com.unibuy.smartdevice.ui.recycler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.unibuy.smartdevice.R;
import java.util.List;
public class RecyclerDonationListAdpter extends RecyclerView.Adapter<RecyclerDonationListAdpter.ViewHolder> {
private List<String> organizationNames;
private int selectedPosition = -1;
private OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(String name, int position);
}
public RecyclerDonationListAdpter(List<String> organizationNames, OnItemClickListener listener) {
this.organizationNames = organizationNames;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.spinner_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
String name = organizationNames.get(position);
holder.textView.setText(name);
// 高亮選中項
if (position == selectedPosition) {
holder.textView.setBackgroundResource(R.drawable.bg_selected_item);
holder.textView.setTextColor(holder.itemView.getContext().getResources().getColor(R.color.white));
} else {
holder.textView.setBackgroundResource(android.R.color.transparent);
holder.textView.setTextColor(holder.itemView.getContext().getResources().getColor(R.color.black));
}
holder.itemView.setOnClickListener(v -> {
int previousPosition = selectedPosition;
selectedPosition = position;
notifyItemChanged(previousPosition);
notifyItemChanged(selectedPosition);
if (listener != null) {
listener.onItemClick(name, position);
}
});
}
@Override
public int getItemCount() {
return organizationNames.size();
}
public void setSelectedPosition(int position) {
this.selectedPosition = position;
notifyDataSetChanged();
}
public int getSelectedPosition() {
return selectedPosition;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.spinnerText);
}
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorPrimary" />
<corners android:radius="8dp" />
</shape>

View File

@ -1,47 +1,168 @@
<?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:padding="24dp"
android:orientation="vertical">
<!-- ✅ 將 CheckBox 和 tvlovecode 包在同一個水平 LinearLayout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/checkboxDefault"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="預設隨機捐贈"
android:textSize="22dp"
android:checked="true" />
<TextView
android:id="@+id/tvlovecode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="捐贈機構:"
android:textSize="20dp"
android:layout_marginStart="16dp" />
</LinearLayout>
<!-- 下拉選單區 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="選擇捐贈機構"
android:textSize="20dp"
android:layout_marginTop="16dp"
android:layout_marginStart="10dp" />
<Spinner
android:id="@+id/spinnerCharity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp" />
</LinearLayout>
<?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="wrap_content"
android:background="@color/light_gray">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/surface"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@android:color/transparent"
android:gravity="center"
android:text="@string/invoice_account"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonInputVehicle"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:checkable="true"
android:text="@string/input_vehicle"
android:textColor="?attr/colorPrimary"
android:textSize="18sp"
app:strokeColor="?attr/colorPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonDonation"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:checkable="true"
android:checked="true"
android:text="@string/donation_invoice"
android:textColor="?attr/colorPrimary"
android:textSize="18sp"
app:strokeColor="?attr/colorPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonCustomerIdentifier"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_weight="1"
android:checkable="true"
android:text="@string/unified_number"
android:textColor="?attr/colorPrimary"
android:textSize="18sp"
app:strokeColor="?attr/colorPrimary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginBottom="1dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/textInvoiceCarrier"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="請選擇捐贈機構"
android:textColor="?attr/colorPrimary"
android:textSize="30sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearMark1"
android:layout_width="match_parent"
android:layout_height="500dp"
android:background="@color/white"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingTop="8dp"
android:paddingBottom="16dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:cardUseCompatPadding="true"
app:strokeWidth="0dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewCharity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="8dp"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:scrollbarSize="4dp"
android:scrollbarThumbVertical="?attr/colorPrimary"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/spinner_item" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonCancel"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_weight="1"
android:layout_marginEnd="16dp"
android:backgroundTint="@color/warning"
android:text="返回支付方式"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonFinish"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
android:layout_width="0dp"
android:layout_height="64dp"
android:layout_weight="1"
android:backgroundTint="?attr/colorPrimary"
android:text="確定"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,181 @@
<?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:id="@+id/linearLayoutInvoiceType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/surface"
android:orientation="vertical">
<!-- 標題 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="選擇發票開立方式"
android:textSize="30sp"
android:textColor="@color/primary"
android:padding="16dp"
android:gravity="center"
android:textStyle="bold" />
<!-- 三張卡片選項區 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingBottom="16dp">
<!-- 卡片1輸入載具 -->
<androidx.cardview.widget.CardView
android:id="@+id/cardVehicle"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingVertical="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="輸入載具"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="手機條碼 / 自然人憑證"
android:textColor="@color/black"
android:textSize="16sp"
android:gravity="center" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 卡片2捐贈發票 -->
<androidx.cardview.widget.CardView
android:id="@+id/cardDonation"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingVertical="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="捐贈發票"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="選擇捐贈機構"
android:textColor="@color/black"
android:textSize="16sp"
android:gravity="center" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 卡片3公司統編 -->
<androidx.cardview.widget.CardView
android:id="@+id/cardCustomerIdentifier"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingVertical="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="公司統編"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="輸入統一編號"
android:textColor="@color/black"
android:textSize="16sp"
android:gravity="center" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
<!-- 底部:返回按鈕 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingBottom="12dp"
android:gravity="center"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonCancel"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
android:layout_width="match_parent"
android:layout_height="64dp"
android:backgroundTint="@color/warning"
android:text="返回支付方式(40)"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />
</LinearLayout>
</LinearLayout>