diff --git a/mobile/Heygo/ViewModels/LinkViewModel.swift b/mobile/Heygo/ViewModels/LinkViewModel.swift index 90afc91..14cde90 100644 --- a/mobile/Heygo/ViewModels/LinkViewModel.swift +++ b/mobile/Heygo/ViewModels/LinkViewModel.swift @@ -16,6 +16,9 @@ class LinkViewModel: ObservableObject { sortLinks() } } + @Published var sortAscending: Bool = true { + didSet { sortLinks() } + } @Published var selectedTag: Tag? { didSet { filterLinks() @@ -251,17 +254,18 @@ class LinkViewModel: ObservableObject { } private func sortLinks() { + let ascending = sortAscending switch selectedSortOption { case .alias: - filteredLinks.sort { $0.alias < $1.alias } + filteredLinks.sort { ascending ? ($0.alias < $1.alias) : ($0.alias > $1.alias) } case .type: - filteredLinks.sort { $0.linkType < $1.linkType } + filteredLinks.sort { ascending ? ($0.linkType < $1.linkType) : ($0.linkType > $1.linkType) } case .clicks: - filteredLinks.sort { $0.clickCount > $1.clickCount } + filteredLinks.sort { ascending ? ($0.clickCount < $1.clickCount) : ($0.clickCount > $1.clickCount) } case .dateCreated: - filteredLinks.sort { $0.createdAt > $1.createdAt } + filteredLinks.sort { ascending ? ($0.createdAt < $1.createdAt) : ($0.createdAt > $1.createdAt) } case .dateUpdated: - filteredLinks.sort { $0.updatedAt > $1.updatedAt } + filteredLinks.sort { ascending ? ($0.updatedAt < $1.updatedAt) : ($0.updatedAt > $1.updatedAt) } } } diff --git a/mobile/Heygo/Views/LinkListView.swift b/mobile/Heygo/Views/LinkListView.swift index 7b44c2f..988473d 100644 --- a/mobile/Heygo/Views/LinkListView.swift +++ b/mobile/Heygo/Views/LinkListView.swift @@ -1,4 +1,5 @@ import SwiftUI +import UIKit struct LinkListView: View { @EnvironmentObject var linkViewModel: LinkViewModel @@ -14,42 +15,43 @@ struct LinkListView: View { VStack { // Search bar SearchBar(text: $linkViewModel.searchText) + .environmentObject(linkViewModel) .padding(.horizontal) // Filter and sort bar HStack { - Button(action: { - showingFilterSheet = true - }) { + Button(action: { showingFilterSheet = true }) { HStack { Image(systemName: "line.horizontal.3.decrease.circle") Text("filter") - if linkViewModel.selectedTag != nil { - Text("•") - .foregroundColor(.blue) - } + if linkViewModel.selectedTag != nil { Text("•").foregroundColor(.blue) } } } Spacer() - Menu { - ForEach(LinkViewModel.SortOption.allCases, id: \.self) { option in - Button(action: { - linkViewModel.selectedSortOption = option - }) { - HStack { - Text(option.displayName) - if linkViewModel.selectedSortOption == option { - Image(systemName: "checkmark") + // Sorting controls + HStack(spacing: 8) { + Button(action: { linkViewModel.sortAscending = true }) { + Image(systemName: "arrow.up") + .foregroundColor(linkViewModel.sortAscending ? .blue : .gray) + } + Button(action: { linkViewModel.sortAscending = false }) { + Image(systemName: "arrow.down") + .foregroundColor(!linkViewModel.sortAscending ? .blue : .gray) + } + Menu { + ForEach(LinkViewModel.SortOption.allCases, id: \.self) { option in + Button(action: { linkViewModel.selectedSortOption = option }) { + HStack { + Text(option.displayName) + if linkViewModel.selectedSortOption == option { Image(systemName: "checkmark") } } } } - } - } label: { - HStack { - Image(systemName: "arrow.up.arrow.down") + } label: { Text(linkViewModel.selectedSortOption.displayName) + .foregroundColor(.blue) } } } @@ -61,7 +63,6 @@ struct LinkListView: View { HStack(spacing: 6) { let filtered = linkViewModel.filteredLinks.count let total = linkViewModel.links.count - // Use monospace digits for subtle alignment Text("\(filtered)") .font(.caption) .fontWeight(.semibold) @@ -70,35 +71,25 @@ struct LinkListView: View { .background(Color.secondary.opacity(0.15)) .foregroundColor(.secondary) .cornerRadius(4) - .accessibilityLabel("Filtered links count") if filtered != total && total > 0 { Text("of \(total)") .font(.caption2) .foregroundColor(.secondary) - .transition(.opacity) } Spacer() } .padding(.horizontal) .padding(.bottom, 2) - .transition(.opacity) } // Links list if linkViewModel.isLoading { - ProgressView() - .frame(maxWidth: .infinity, maxHeight: .infinity) + ProgressView().frame(maxWidth: .infinity, maxHeight: .infinity) } else if linkViewModel.filteredLinks.isEmpty { VStack { - Image(systemName: "link.badge.plus") - .font(.system(size: 60)) - .foregroundColor(.secondary) - Text("no_links_found") - .font(.title2) - .foregroundColor(.secondary) - Text("tap_plus_to_add_link") - .font(.caption) - .foregroundColor(.secondary) + Image(systemName: "link.badge.plus").font(.system(size: 60)).foregroundColor(.secondary) + Text("no_links_found").font(.title2).foregroundColor(.secondary) + Text("tap_plus_to_add_link").font(.caption).foregroundColor(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { @@ -108,78 +99,33 @@ struct LinkListView: View { markdownLink = customLink showingMarkdownContent = true } - .onTapGesture { - selectedLink = link - } - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button("delete", role: .destructive) { - linkViewModel.deleteLink(link) - } - - Button("edit") { - selectedLink = link - showingAddLink = true - } - .tint(.blue) - } + .onTapGesture { selectedLink = link } + .swipeActions(edge: .trailing, allowsFullSwipe: false) { + Button("delete", role: .destructive) { linkViewModel.deleteLink(link) } + Button("edit") { selectedLink = link; showingAddLink = true }.tint(.blue) + } } } - .refreshable { - linkViewModel.fetchLinks() - } + .refreshable { linkViewModel.fetchLinks() } } } .navigationTitle("") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - HStack { - Image(systemName: "dog.fill") - .foregroundColor(.black) - Text("Heygo") - .font(.headline) - .foregroundColor(.primary) - } + HStack { Image(systemName: "dog.fill").foregroundColor(.black); Text("Heygo").font(.headline).foregroundColor(.primary) } } ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { - selectedLink = nil - showingAddLink = true - }) { - Image(systemName: "plus") - } - } - } - .sheet(isPresented: $showingAddLink) { - LinkFormView(link: selectedLink) - .environmentObject(linkViewModel) - } - .sheet(isPresented: $showingFilterSheet) { - FilterView() - .environmentObject(linkViewModel) - } - .sheet(item: $selectedLink) { link in - LinkDetailView(link: link) - .environmentObject(linkViewModel) - } - .sheet(isPresented: $showingMarkdownContent) { - if let markdownLink = markdownLink { - MarkdownContentView(link: markdownLink) + Button(action: { selectedLink = nil; showingAddLink = true }) { Image(systemName: "plus") } } } + .sheet(isPresented: $showingAddLink) { LinkFormView(link: selectedLink).environmentObject(linkViewModel) } + .sheet(isPresented: $showingFilterSheet) { FilterView().environmentObject(linkViewModel) } + .sheet(item: $selectedLink) { link in LinkDetailView(link: link).environmentObject(linkViewModel) } + .sheet(isPresented: $showingMarkdownContent) { if let markdownLink = markdownLink { MarkdownContentView(link: markdownLink) } } } - .onAppear { - if linkViewModel.links.isEmpty { - linkViewModel.fetchLinks() - } - } - .alert("Error", isPresented: .constant(linkViewModel.errorMessage != nil)) { - Button("OK") { - linkViewModel.errorMessage = nil - } - } message: { - Text(linkViewModel.errorMessage ?? "") - } + .onAppear { if linkViewModel.links.isEmpty { linkViewModel.fetchLinks() } } + .alert("Error", isPresented: .constant(linkViewModel.errorMessage != nil)) { Button("OK") { linkViewModel.errorMessage = nil } } message: { Text(linkViewModel.errorMessage ?? "") } } }