Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
41f5dfb
enhance(UTXO 선택 모드): 하단 버튼 ui 변경(보내기, 태그 적용 추가)
roy-noncelab Feb 20, 2026
eb150c0
enhance(UTXO 선택 모드): 앱 바 선택 모드 버튼 ui 수정
roy-noncelab Feb 20, 2026
9bd05a7
enhance(UTXO 선택 모드): 선택 상태 체크 표시 추가 및 ui 수정
roy-noncelab Feb 20, 2026
e018c4a
refactor(UTXO 선택 모드): 불필요한 utxoTags 파라미터 제거
roy-noncelab Feb 20, 2026
2d5b795
enhance(UTXO 선택 모드): UTXO 선택 필요 토스트 알림
roy-noncelab Feb 20, 2026
01c9bf8
enhance(UTXO 선택 모드): Tag 적용 기능
roy-noncelab Feb 24, 2026
ce8d227
enhance(UTXO 선택 모드): 보내기 버튼 기능 추가
roy-noncelab Feb 24, 2026
e77d396
enhance(UTXO 선택 모드): 사용 잠금된 utxo 사용 불가
roy-noncelab Feb 24, 2026
8b4f5b3
Merge branch 'develop' into enhance/579-bulk-apply-UTXO-tags
roy-noncelab Feb 25, 2026
837f37b
refactor(UTXO 선택 모드): 중복된 tag_bottom_sheet 리팩토링 1차
roy-noncelab Feb 25, 2026
07c99be
refactor(UTXO 선택 모드): utxo_detail_screen 리팩토링
roy-noncelab Feb 25, 2026
a3d5915
fix(UTXO 선택 모드): 태그된 utxo가 사용 후에도 데이터가 남아있는 문제 수정
roy-noncelab Feb 25, 2026
60c61cd
Merge branch 'develop' into enhance/579-bulk-apply-UTXO-tags
roy-noncelab Feb 26, 2026
1cb5b9d
enhance(UTXO 선택 모드): UI 수정
roy-noncelab Feb 26, 2026
bcab80a
chore(UTXO 선택 모드): 이름 수정
roy-noncelab Feb 26, 2026
bf901a4
refactor(UTXO 선택 모드): 코드, 파일 구조 개선
roy-noncelab Feb 27, 2026
6f7445b
fix(UTXO 선택 모드): UTXO 상세 화면에서 태그 적용 바로 안되는 버그 수정
roy-noncelab Feb 27, 2026
2f11b66
enhance(UTXO 선택 모드): 태그 UI 수정
roy-noncelab Feb 27, 2026
3dbc7dd
enhance(UTXO 선택 모드): 태그 텍스트 굵기 유지
roy-noncelab Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/i18n/en.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,10 @@ address_search_screen:
change_address: "Change Address"
utxo_list_screen:
total_balance: "Total Balance"
tag_apply: "Apply tag"
utxo_select_required: "Please select a UTXO first."
utxo_tag_updated: "UTXO tag has been successfully updated."
send_locked_utxo: "Locked UTXOs cannot be sent."
utxo_locked_button: "Lock"
utxo_unlocked_button: "Unlock"
pending_utxo: "UTXOs that are already being sent cannot be selected."
Expand Down Expand Up @@ -769,6 +773,7 @@ security_self_check_bottom_sheet:
tag_bottom_sheet:
title_new_tag: "Add Tag"
title_edit_tag: "Edit Tag"
title_apply_tag: "Apply Tag"
tag_list: "Tag List"
add_new_tag: "Add Tag"
delete_tag: "Delete Tag"
Expand Down
5 changes: 5 additions & 0 deletions assets/i18n/es.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,10 @@ address_search_screen:
change_address: "Dirección de cambio"
utxo_list_screen:
total_balance: "Saldo total"
tag_apply: "Aplicar etiqueta"
utxo_select_required: "Por favor, seleccione primero el UTXO."
utxo_tag_updated: "La etiqueta de UTXO se ha actualizado correctamente."
send_locked_utxo: "No se pueden enviar UTXO bloqueados para uso."
utxo_locked_button: "Bloquear"
utxo_unlocked_button: "Desbloquear"
pending_utxo: "Los UTXO que ya están en proceso de envío no se pueden seleccionar."
Expand Down Expand Up @@ -773,6 +777,7 @@ security_self_check_bottom_sheet:
tag_bottom_sheet:
title_new_tag: "Agregar etiqueta"
title_edit_tag: "Editar etiqueta"
title_apply_tag: "Aplicar etiqueta"
tag_list: "Lista de etiquetas"
add_new_tag: "Agregar etiqueta"
delete_tag: "Eliminar etiqueta"
Expand Down
7 changes: 6 additions & 1 deletion assets/i18n/jp.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,12 @@ address_search_screen:
change_address: "おつりアドレス"
utxo_list_screen:
total_balance: "総残高"
tag_apply: "タグを適用"
utxo_select_required: "まずUTXOを選択してください。"
utxo_tag_updated: "UTXOのタグが正常に更新されました。"
send_locked_utxo: "使用がロックされたUTXOは送信できません。"
utxo_locked_button: "使用ロック"
utxo_unlocked_button: "使用可能にする"
utxo_unlocked_button: "ロック解除"
pending_utxo: "すでに送信中のUTXOは選択できません。"

transaction_detail_screen:
Expand Down Expand Up @@ -769,6 +773,7 @@ security_self_check_bottom_sheet:
tag_bottom_sheet:
title_new_tag: "タグ追加"
title_edit_tag: "タグ編集"
title_apply_tag: "タグを適用"
tag_list: "タグ一覧"
add_new_tag: "タグ追加"
delete_tag: "タグ削除"
Expand Down
7 changes: 6 additions & 1 deletion assets/i18n/kr.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -662,8 +662,12 @@ address_search_screen:
change_address: "잔돈 주소"
utxo_list_screen:
total_balance: "총 잔액"
tag_apply: "태그 적용"
utxo_select_required: "UTXO를 먼저 선택해 주세요."
utxo_tag_updated: "UTXO 태그가 성공적으로 업데이트되었습니다."
send_locked_utxo: "사용 잠금된 UTXO는 보낼 수 없어요."
utxo_locked_button: "사용 잠금"
utxo_unlocked_button: "사용 가능으로 전환"
utxo_unlocked_button: "잠금 해제"
pending_utxo: "이미 전송 중인 UTXO는 선택할 수 없어요."

transaction_detail_screen:
Expand Down Expand Up @@ -772,6 +776,7 @@ security_self_check_bottom_sheet:
tag_bottom_sheet:
title_new_tag: "태그 추가"
title_edit_tag: "태그 편집"
title_apply_tag: "태그 적용"
tag_list: "태그 목록"
add_new_tag: "태그 추가하기"
delete_tag: "태그 삭제하기"
Expand Down
10 changes: 10 additions & 0 deletions assets/svg/circle-check-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions assets/svg/circle-check-outline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/svg/circle-minus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/svg/circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/svg/lock_simple.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions assets/svg/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/svg/tag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/svg/unlock_simple.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ class _CoconutWalletAppState extends State<CoconutWalletApp> {
walletId: args['walletId'],
sendEntryPoint: args['sendEntryPoint'],
transactionDraftId: args['transactionDraftId'],
selectedUtxoList: args['selectedUtxoList'],
),
),
'/utxo-tag': (context) => buildScreenWithArgs(context, (args) => UtxoTagCrudScreen(id: args['id'])),
Expand Down
39 changes: 36 additions & 3 deletions lib/providers/utxo_tag_provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:coconut_wallet/model/utxo/utxo_tag.dart';
import 'package:coconut_wallet/repository/realm/service/realm_id_service.dart';
import 'package:coconut_wallet/repository/realm/utxo_repository.dart';
import 'package:coconut_wallet/screens/common/tag_apply_bottom_sheet.dart';
import 'package:coconut_wallet/utils/logger.dart';
import 'package:flutter/cupertino.dart';
import 'package:uuid/uuid.dart';
Expand Down Expand Up @@ -145,15 +146,15 @@ class UtxoTagProvider extends ChangeNotifier {
notifyListeners();
}

void updateUtxoTagIdList({required int walletId, required String utxoId, required List<String> selectedTagNames}) {
final updateUtxoTagListResult = _utxoRepository.updateUtxoTagList(walletId, utxoId, selectedTagNames);
void updateUtxoTagIdList({required int walletId, required String utxoId, required List<String> tagNamesToApply}) {
final updateUtxoTagListResult = _utxoRepository.updateUtxoTagList(walletId, utxoId, tagNamesToApply);
if (updateUtxoTagListResult.isFailure) {
Logger.log('-----------------------------------------------------------');
Logger.log(
'updateUtxoTagIdList('
'walletId: $walletId,'
'txHashIndex: $utxoId,'
'selectedTagNames: $selectedTagNames,'
'tagNamesToApply: $tagNamesToApply,'
')',
);
Logger.log(updateUtxoTagListResult.error);
Expand All @@ -162,4 +163,36 @@ class UtxoTagProvider extends ChangeNotifier {
_isUpdatedTagList = true;
notifyListeners();
}

List<String> calculateUpdatedTags({
required List<String> currentTagNames,
required Map<String, TagApplyState> tagStates,
}) {
final Set<String> updatedTagsSet = currentTagNames.toSet();

tagStates.forEach((tagName, state) {
if (state == TagApplyState.checked) {
updatedTagsSet.add(tagName);
} else if (state == TagApplyState.unchecked) {
updatedTagsSet.remove(tagName);
}
});

return updatedTagsSet.toList();
}

Future<void> applyTagsToUtxos({
required int walletId,
required List<String> selectedUtxoIds,
required Map<String, TagApplyState> tagStates,
required List<String> Function(String utxoId) getCurrentTagsCallback,
}) async {
for (final utxoId in selectedUtxoIds) {
final currentTagNames = getCurrentTagsCallback(utxoId);

final finalTags = calculateUpdatedTags(currentTagNames: currentTagNames, tagStates: tagStates);

updateUtxoTagIdList(walletId: walletId, utxoId: utxoId, tagNamesToApply: finalTags);
}
}
}
89 changes: 11 additions & 78 deletions lib/providers/view_model/wallet_detail/utxo_detail_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'package:coconut_wallet/providers/transaction_provider.dart';
import 'package:coconut_wallet/providers/utxo_tag_provider.dart';
import 'package:coconut_wallet/providers/wallet_provider.dart';
import 'package:coconut_wallet/model/node/wallet_update_info.dart';
import 'package:coconut_wallet/screens/common/tag_bottom_sheet.dart';
import 'package:coconut_wallet/utils/datetime_util.dart';
import 'package:coconut_wallet/utils/logger.dart';
import 'package:coconut_wallet/utils/transaction_util.dart';
Expand All @@ -29,7 +28,7 @@ class UtxoDetailViewModel extends ChangeNotifier {
StreamSubscription<WalletUpdateInfo>? _syncWalletStateSubscription;

List<UtxoTag> _utxoTagList = [];
List<UtxoTag> _selectedUtxoTagList = [];
List<UtxoTag> _appliedUtxoTagList = [];
late final List<String> _dateString;
late final TransactionRecord? _transaction;

Expand All @@ -49,7 +48,7 @@ class UtxoDetailViewModel extends ChangeNotifier {
) {
_utxoId = _utxo.utxoId;
_utxoTagList = _tagProvider.getUtxoTagList(_walletId);
_selectedUtxoTagList = _tagProvider.getUtxoTagsByUtxoId(_walletId, _utxoId);
_appliedUtxoTagList = _tagProvider.getUtxoTagsByUtxoId(_walletId, _utxoId);

_transaction = _txProvider.getTransaction(_walletId, _utxo.transactionHash);
_dateString = _transaction != null ? DateTimeUtil.formatTimestamp(_transaction.timestamp) : ['-', '-'];
Expand Down Expand Up @@ -89,7 +88,7 @@ class UtxoDetailViewModel extends ChangeNotifier {
}

List<String> get dateString => _dateString;
List<UtxoTag> get selectedUtxoTagList => _selectedUtxoTagList;
List<UtxoTag> get appliedUtxoTagList => _appliedUtxoTagList;
UtxoTagProvider? get tagProvider => _tagProvider;

TransactionRecord? get transaction => _transaction;
Expand All @@ -103,86 +102,20 @@ class UtxoDetailViewModel extends ChangeNotifier {
String getOutputAddress(int index) => TransactionUtil.getOutputAddress(_transaction, index);
int getOutputAmount(int index) => TransactionUtil.getOutputAmount(_transaction, index);

void updateUtxoTags(
String utxoId,
List<String> selectedTagNames,
List<UtxoTag> updatedTagList,
UtxoTagEditMode editMode,
) {
final addedTags =
updatedTagList
.where((updatedTag) => !_utxoTagList.any((currentTag) => currentTag.name == updatedTag.name))
.toList();
final removedTags =
_utxoTagList
.where((currentTag) => !updatedTagList.any((updatedTag) => updatedTag.name == currentTag.name))
.toList();

switch (editMode) {
case UtxoTagEditMode.add:
if (addedTags.isNotEmpty) {
for (int i = 0; i < addedTags.length; i++) {
_tagProvider.addUtxoTag(_walletId, addedTags[i]);
}
}
break;
case UtxoTagEditMode.delete:
if (removedTags.isNotEmpty) {
for (int i = 0; i < removedTags.length; i++) {
_tagProvider.deleteUtxoTag(_walletId, removedTags[i]);
}
}
break;
case UtxoTagEditMode.changAppliedTags:
_tagProvider.updateUtxoTagIdList(walletId: _walletId, utxoId: utxoId, selectedTagNames: selectedTagNames);
break;
case UtxoTagEditMode.update:
final List<UtxoTag> modifiedTags = [];
final currentTagsById = {for (var tag in _utxoTagList) tag.id: tag};
final updatedTagsById = {for (var tag in updatedTagList) tag.id: tag};

for (String id in currentTagsById.keys) {
if (updatedTagsById.containsKey(id)) {
final currentTag = currentTagsById[id]!;
final updatedTag = updatedTagsById[id]!;

if (currentTag.name != updatedTag.name || currentTag.colorIndex != updatedTag.colorIndex) {
modifiedTags.add(updatedTag);
}
}
}
if (modifiedTags.isNotEmpty) {
for (var modifiedTag in modifiedTags) {
_tagProvider.updateUtxoTag(_walletId, modifiedTag);
}
}
}

_utxoTagList = _tagProvider.getUtxoTagList(_walletId);
_selectedUtxoTagList = _tagProvider.getUtxoTagsByUtxoId(_walletId, utxoId);
notifyListeners();
}

void _initUtxoInOutputList() {
if (_transaction == null) return;

_utxoInputMaxCount =
_transaction.inputAddressList.length <= kInputMaxCount ? _transaction.inputAddressList.length : kInputMaxCount;
_utxoOutputMaxCount =
_transaction.outputAddressList.length <= kOutputMaxCount
? _transaction.outputAddressList.length
: kOutputMaxCount;
if (_transaction.inputAddressList.length <= utxoInputMaxCount) {
_utxoInputMaxCount = _transaction.inputAddressList.length;
}
if (_transaction.outputAddressList.length <= utxoOutputMaxCount) {
_utxoOutputMaxCount = _transaction.outputAddressList.length;
}
final inputCount = _transaction.inputAddressList.length;
final outputCount = _transaction.outputAddressList.length;

_utxoInputMaxCount = inputCount <= kInputMaxCount ? inputCount : kInputMaxCount;
_utxoOutputMaxCount = outputCount <= kOutputMaxCount ? outputCount : kOutputMaxCount;
}

List<UtxoTag> refreshTagList(walletId) {
void refreshTagList() {
_utxoTagList = _tagProvider.getUtxoTagList(_walletId);
return _utxoTagList;
_appliedUtxoTagList = _tagProvider.getUtxoTagsByUtxoId(_walletId, _utxoId);
notifyListeners();
}

@override
Expand Down
Loading