1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 19:09:43 +00:00

Uninstallation experience enhancement

This commit is contained in:
Tw93
2025-11-20 15:15:33 +08:00
parent ab3f8cc129
commit 3ddf2dd3b9
5 changed files with 87 additions and 75 deletions

View File

@@ -381,8 +381,11 @@ load_applications() {
apps_data=()
selection_state=()
# Read apps into array
# Read apps into array, skip non-existent apps
while IFS='|' read -r epoch app_path app_name bundle_id size last_used size_kb; do
# Skip if app path no longer exists
[[ ! -e "$app_path" ]] && continue
apps_data+=("$epoch|$app_path|$app_name|$bundle_id|$size|$last_used|${size_kb:-0}")
selection_state+=(false)
done < "$apps_file"

View File

@@ -356,25 +356,25 @@ paginated_multi_select() {
for ((i = 0; i < items_per_page + 2; i++)); do
printf "${clear_line}\n" >&2
done
printf "${clear_line}${GRAY}Type to filter${NC} ${GRAY}|${NC} ${GRAY}Delete${NC} Backspace ${GRAY}|${NC} ${GRAY}Enter${NC} Apply ${GRAY}|${NC} ${GRAY}ESC${NC} Cancel\n" >&2
printf "${clear_line}Type to filter | Delete | Enter | / Exit | ESC\n" >&2
printf "${clear_line}" >&2
return
else
if [[ "$searching" == "true" ]]; then
printf "${clear_line}${GRAY}Searching…${NC}\n" >&2
printf "${clear_line}Searching…\n" >&2
for ((i = 0; i < items_per_page + 2; i++)); do
printf "${clear_line}\n" >&2
done
printf "${clear_line}${GRAY}${ICON_NAV_UP}/${ICON_NAV_DOWN}${NC} Nav ${GRAY}|${NC} ${GRAY}Space${NC} Select ${GRAY}|${NC} ${GRAY}Enter${NC} Confirm ${GRAY}|${NC} ${GRAY}/${NC} Filter ${GRAY}|${NC} ${GRAY}S${NC} Sort ${GRAY}|${NC} ${GRAY}Q${NC} Quit\n" >&2
printf "${clear_line}${ICON_NAV_UP}/${ICON_NAV_DOWN} | Space | Enter | / Filter | Q Exit\n" >&2
printf "${clear_line}" >&2
return
else
# Post-search: truly empty list
printf "${clear_line}${GRAY}No items available${NC}\n" >&2
printf "${clear_line}No items available\n" >&2
for ((i = 0; i < items_per_page + 2; i++)); do
printf "${clear_line}\n" >&2
done
printf "${clear_line}${GRAY}${ICON_NAV_UP}/${ICON_NAV_DOWN}${NC} Nav ${GRAY}|${NC} ${GRAY}Space${NC} Select ${GRAY}|${NC} ${GRAY}Enter${NC} Confirm ${GRAY}|${NC} ${GRAY}/${NC} Filter ${GRAY}|${NC} ${GRAY}S${NC} Sort ${GRAY}|${NC} ${GRAY}Q${NC} Quit\n" >&2
printf "${clear_line}${ICON_NAV_UP}/${ICON_NAV_DOWN} | Space | Enter | / Filter | Q Exit\n" >&2
printf "${clear_line}" >&2
return
fi
@@ -419,56 +419,70 @@ paginated_multi_select() {
name) sort_label="Name" ;;
size) sort_label="Size" ;;
esac
local arrow="↑"
[[ "$sort_reverse" == "true" ]] && arrow="↓"
local sort_status="${sort_label} ${arrow}"
local sort_status="${sort_label}"
local filter_status=""
if [[ "$filter_mode" == "true" ]]; then
filter_status="${YELLOW}${filter_query:-}${NC}"
filter_status="${filter_query:-_}"
elif [[ -n "$applied_query" ]]; then
filter_status="${GREEN}${applied_query}${NC}"
filter_status="${applied_query}"
else
filter_status="${GRAY}${NC}"
filter_status=""
fi
# Footer with two lines: basic controls and advanced options
local sep=" ${GRAY}|${NC} "
# Footer: single line with controls
local sep=" | "
if [[ "$filter_mode" == "true" ]]; then
# Filter mode: single line with all filter controls
# Filter mode: simple controls without sort
local -a _segs_filter=(
"${GRAY}Filter Input:${NC} ${filter_status}"
"${GRAY}Delete${NC} Back"
"${GRAY}Enter${NC} Apply"
"${GRAY}/${NC} Clear"
"${GRAY}ESC${NC} Cancel"
"Filter: ${filter_status}"
"Delete"
"Enter"
"/ Exit"
"ESC"
)
_print_wrapped_controls "$sep" "${_segs_filter[@]}"
else
# Normal mode
# Normal mode - single line compact format
local reverse_arrow="↑"
[[ "$sort_reverse" == "true" ]] && reverse_arrow="↓"
# Determine filter text based on whether filter is active
local filter_text="/ Filter"
[[ -n "$applied_query" ]] && filter_text="/ Clear"
if [[ "$has_metadata" == "true" ]]; then
# With metadata: two lines (basic + advanced)
local -a _segs_basic=(
"${GRAY}${ICON_NAV_UP}/${ICON_NAV_DOWN}${NC} Nav"
"${GRAY}Space${NC} Select"
"${GRAY}Enter${NC} Confirm"
"${GRAY}Q${NC} Quit"
)
_print_wrapped_controls "$sep" "${_segs_basic[@]}"
local -a _segs_advanced=(
"${GRAY}S${NC} ${sort_status}"
"${GRAY}R${NC} Reverse"
"${GRAY}/${NC} Filter"
)
_print_wrapped_controls "$sep" "${_segs_advanced[@]}"
if [[ -n "$applied_query" ]]; then
# Filtering: hide sort controls
local -a _segs_all=(
"${ICON_NAV_UP}/${ICON_NAV_DOWN}"
"Space"
"Enter"
"${filter_text}"
"Q Exit"
)
_print_wrapped_controls "$sep" "${_segs_all[@]}"
else
# Normal: show full controls
local -a _segs_all=(
"${ICON_NAV_UP}/${ICON_NAV_DOWN}"
"Space"
"Enter"
"${filter_text}"
"S ${sort_status}"
"R ${reverse_arrow}"
"Q Exit"
)
_print_wrapped_controls "$sep" "${_segs_all[@]}"
fi
else
# Without metadata: single line (basic only)
# Without metadata: basic controls
local -a _segs_simple=(
"${GRAY}${ICON_NAV_UP}/${ICON_NAV_DOWN}${NC} Nav"
"${GRAY}Space${NC} Select"
"${GRAY}Enter${NC} Confirm"
"${GRAY}/${NC} Filter"
"${GRAY}Q${NC} Quit"
"${ICON_NAV_UP}/${ICON_NAV_DOWN}"
"Space"
"Enter"
"${filter_text}"
"Q Exit"
)
_print_wrapped_controls "$sep" "${_segs_simple[@]}"
fi
@@ -568,13 +582,23 @@ paginated_multi_select() {
fi
;;
"FILTER")
# Trigger filter mode with /
filter_mode="true"
export MOLE_READ_KEY_FORCE_CHAR=1
filter_query=""
top_index=0
cursor_pos=0
rebuild_view
# / key: toggle between filter and return
if [[ -n "$applied_query" ]]; then
# Already filtering, clear and return to full list
applied_query=""
filter_query=""
top_index=0
cursor_pos=0
rebuild_view
else
# Enter filter mode
filter_mode="true"
export MOLE_READ_KEY_FORCE_CHAR=1
filter_query=""
top_index=0
cursor_pos=0
rebuild_view
fi
;;
"CHAR:f" | "CHAR:F")
if [[ "$filter_mode" == "true" ]]; then
@@ -603,9 +627,12 @@ paginated_multi_select() {
CHAR:*)
if [[ "$filter_mode" == "true" ]]; then
local ch="${key#CHAR:}"
# Special handling for /: clear filter
# Special handling for /: exit filter mode
if [[ "$ch" == "/" ]]; then
filter_mode="false"
unset MOLE_READ_KEY_FORCE_CHAR
filter_query=""
applied_query=""
rebuild_view
# avoid accidental leading spaces
elif [[ -n "$filter_query" || "$ch" != " " ]]; then

View File

@@ -168,7 +168,7 @@ paginated_multi_select() {
# Clear any remaining lines at bottom
printf "${clear_line}\n" >&2
printf "${clear_line}${GRAY}${ICON_NAV_UP}/${ICON_NAV_DOWN}${NC} Nav ${GRAY}|${NC} ${GRAY}Space${NC} Select ${GRAY}|${NC} ${GRAY}Enter${NC} Confirm ${GRAY}|${NC} ${GRAY}Q${NC} Quit\n" >&2
printf "${clear_line}${ICON_NAV_UP}/${ICON_NAV_DOWN} | Space | Enter | Q Exit\n" >&2
# Clear one more line to ensure no artifacts
printf "${clear_line}" >&2

View File

@@ -92,29 +92,6 @@ format_size_kb() {
fi
}
# Check startup items count
check_startup_items() {
local count=0
local dirs=(
"$HOME/Library/LaunchAgents"
"/Library/LaunchAgents"
)
for dir in "${dirs[@]}"; do
if [[ -d "$dir" ]]; then
local dir_count
dir_count=$(find "$dir" -maxdepth 1 -type f -name "*.plist" 2> /dev/null | wc -l)
count=$((count + dir_count))
fi
done
if [[ $count -gt 5 ]]; then
local suggested=$((count / 2))
[[ $suggested -lt 1 ]] && suggested=1
echo "startup_items|Startup Items|${count} items (suggest disable ${suggested})|false"
fi
}
# Check cache size
check_cache_refresh() {
local cache_dir="$HOME/Library/Caches"
@@ -240,8 +217,6 @@ EOF
# Conditional items
local item
item=$(check_startup_items || true)
[[ -n "$item" ]] && items+=("$item")
item=$(check_cache_refresh || true)
[[ -n "$item" ]] && items+=("$item")
item=$(check_mail_downloads || true)

View File

@@ -136,6 +136,7 @@ batch_uninstall_applications() {
fi
echo -ne "${PURPLE}${ICON_ARROW}${NC} ${removal_note} ${GREEN}Enter${NC} confirm, ${GRAY}ESC${NC} cancel: "
drain_pending_input # Clean up any pending input before confirmation
IFS= read -r -s -n1 key || key=""
drain_pending_input # Clean up any escape sequence remnants
case "$key" in
@@ -316,6 +317,12 @@ batch_uninstall_applications() {
sudo_keepalive_pid=""
fi
# Invalidate cache if any apps were successfully uninstalled
if [[ $success_count -gt 0 ]]; then
local cache_file="$HOME/.cache/mole/app_scan_cache"
rm -f "$cache_file" 2> /dev/null || true
fi
((total_size_cleaned += total_size_freed))
unset failed_items
}