From a93b275326ff54b05833a8527a80c5966f467724 Mon Sep 17 00:00:00 2001 From: EA Date: Tue, 21 May 2024 12:19:42 +0600 Subject: [PATCH] Add ability to reorder Watchlist in manual sorting --- .../Core/Managers/WatchlistManager.swift | 4 +++ .../Market/Coins/MarketCoinsView.swift | 2 +- .../Watchlist/MarketWatchlistView.swift | 31 +++++++++++++++++-- .../Watchlist/MarketWatchlistViewModel.swift | 25 ++++++++++++++- .../Modules/SendNew/BitcoinSendHandler.swift | 2 +- .../SwiftUI/SecondaryCircleButtonStyle.swift | 21 ++++++++----- .../UserInterface/SwiftUI/ThemeList.swift | 12 +++++-- 7 files changed, 82 insertions(+), 15 deletions(-) diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/WatchlistManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/WatchlistManager.swift index 1f3a85a911..3d76891e8c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/WatchlistManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/WatchlistManager.swift @@ -68,6 +68,10 @@ extension WatchlistManager { coinUidsSubject.eraseToAnyPublisher() } + func set(coinUids: [String]) { + self.coinUids = coinUids + } + func add(coinUid: String) { guard !coinUids.contains(coinUid) else { return diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift index c92d9c5213..8e0ceaf7af 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Coins/MarketCoinsView.swift @@ -34,7 +34,7 @@ struct MarketCoinsView: View { } } .sheet(item: $presentedFullCoin) { fullCoin in - CoinPageViewNew(coinUid: fullCoin.coin.uid) + CoinPageViewNew(coinUid: fullCoin.coin.uid).ignoresSafeArea() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift index ba8f17d1c5..832092ca69 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift @@ -7,9 +7,10 @@ struct MarketWatchlistView: View { @State private var sortBySelectorPresented = false @State private var timePeriodSelectorPresented = false - @State private var presentedFullCoin: FullCoin? + @State private var editMode: EditMode = .inactive + var body: some View { ThemeView { switch viewModel.state { @@ -36,7 +37,7 @@ struct MarketWatchlistView: View { } } .sheet(item: $presentedFullCoin) { fullCoin in - CoinPageViewNew(coinUid: fullCoin.coin.uid) + CoinPageViewNew(coinUid: fullCoin.coin.uid).ignoresSafeArea() } } @@ -51,6 +52,20 @@ struct MarketWatchlistView: View { .buttonStyle(SecondaryButtonStyle(style: .default, rightAccessory: .dropDown)) .disabled(disabled) + if viewModel.sortBy == .manual { + Button(action: { + if editMode == .active { + editMode = .inactive + } else { + editMode = .active + } + }) { + Image("edit2_20").renderingMode(.template) + } + .buttonStyle(SecondaryCircleButtonStyle(style: .default, isActive: editMode == .active)) + .disabled(disabled) + } + Button(action: { timePeriodSelectorPresented = true }) { @@ -107,7 +122,12 @@ struct MarketWatchlistView: View { } @ViewBuilder private func list(marketInfos: [MarketInfo], signals: [String: TechnicalAdvice.Advice]) -> some View { - ThemeList(items: marketInfos) { marketInfo in + ThemeList( + items: marketInfos, + onMove: viewModel.sortBy == .manual ? { source, destination in + viewModel.move(source: source, destination: destination) + } : nil + ) { marketInfo in let coin = marketInfo.fullCoin.coin ClickableRow(action: { @@ -133,9 +153,14 @@ struct MarketWatchlistView: View { } } .themeListStyle(.transparent) + .environment(\.editMode, $editMode) .refreshable { await viewModel.refresh() } + .animation(.default, value: editMode) + .onChange(of: viewModel.sortBy) { _ in + editMode = .inactive + } } @ViewBuilder private func loadingList() -> some View { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift index 6fb95c4a70..510d97b0fb 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift @@ -49,7 +49,13 @@ class MarketWatchlistViewModel: ObservableObject { } private func syncCoinUids() { - coinUids = watchlistManager.coinUids + let coinUids = watchlistManager.coinUids + + guard coinUids != self.coinUids else { + return + } + + self.coinUids = coinUids if case let .loaded(marketInfos, signals) = internalState { let newMarketInfos = marketInfos.filter { marketInfo in @@ -144,6 +150,23 @@ extension MarketWatchlistViewModel { func remove(coinUid: String) { watchlistManager.remove(coinUid: coinUid) } + + func move(source: IndexSet, destination: Int) { + guard case let .loaded(marketInfos, signals) = internalState else { + return + } + + var newCoinUids = coinUids + var newMarketInfos = marketInfos + + newCoinUids.move(fromOffsets: source, toOffset: destination) + newMarketInfos.move(fromOffsets: source, toOffset: destination) + + coinUids = newCoinUids + internalState = .loaded(marketInfos: newMarketInfos, signals: signals) + + watchlistManager.set(coinUids: coinUids) + } } extension MarketWatchlistViewModel { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/BitcoinSendHandler.swift b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/BitcoinSendHandler.swift index 2201ac3666..3990ee9c65 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/BitcoinSendHandler.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/SendNew/BitcoinSendHandler.swift @@ -27,7 +27,7 @@ extension BitcoinSendHandler: ISendHandler { let satoshiPerByte = transactionSettings?.satoshiPerByte var feeData: BitcoinFeeData? var transactionError: Error? - var params = params.copy() + let params = params.copy() if let satoshiPerByte { params.feeRate = satoshiPerByte diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/SecondaryCircleButtonStyle.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/SecondaryCircleButtonStyle.swift index c506e248fb..398b89e911 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/SecondaryCircleButtonStyle.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/SecondaryCircleButtonStyle.swift @@ -1,15 +1,21 @@ import SwiftUI struct SecondaryCircleButtonStyle: ButtonStyle { - let style: Style + private let style: Style + private let isActive: Bool @Environment(\.isEnabled) private var isEnabled + init(style: Style = .default, isActive: Bool = false) { + self.style = style + self.isActive = isActive + } + func makeBody(configuration: Configuration) -> some View { configuration.label .padding(.margin4) - .foregroundColor(style.foregroundColor(isEnabled: isEnabled, isPressed: configuration.isPressed)) - .background(style.backgroundColor(isEnabled: isEnabled, isPressed: configuration.isPressed)) + .foregroundColor(style.foregroundColor(isEnabled: isEnabled, isActive: isActive, isPressed: configuration.isPressed)) + .background(style.backgroundColor(isEnabled: isEnabled, isActive: isActive, isPressed: configuration.isPressed)) .clipShape(Circle()) .animation(.easeOut(duration: 0.2), value: configuration.isPressed) } @@ -19,17 +25,18 @@ struct SecondaryCircleButtonStyle: ButtonStyle { case transparent case red - func foregroundColor(isEnabled: Bool, isPressed: Bool) -> Color { + func foregroundColor(isEnabled: Bool, isActive: Bool, isPressed: Bool) -> Color { switch self { - case .default: return isEnabled ? (isPressed ? .themeGray : .themeLeah) : .themeGray50 + case .default: return isEnabled ? (isActive ? .themeDark : (isPressed ? .themeGray : .themeLeah)) : .themeGray50 case .transparent: return isEnabled ? (isPressed ? .themeGray50 : .themeGray) : .themeGray50 case .red: return isEnabled ? (isPressed ? .themeRed50 : .themeLucian) : .themeGray50 } } - func backgroundColor(isEnabled _: Bool, isPressed: Bool) -> Color { + func backgroundColor(isEnabled _: Bool, isActive: Bool, isPressed: Bool) -> Color { switch self { - case .default, .red: return isPressed ? .themeSteel10 : .themeSteel20 + case .default: return isActive ? (isPressed ? .themeYellow50 : .themeYellow) : (isPressed ? .themeSteel10 : .themeSteel20) + case .red: return isPressed ? .themeSteel10 : .themeSteel20 case .transparent: return .clear } } diff --git a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ThemeList.swift b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ThemeList.swift index 211a80fb4a..a28676cfa6 100644 --- a/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ThemeList.swift +++ b/UnstoppableWallet/UnstoppableWallet/UserInterface/SwiftUI/ThemeList.swift @@ -1,11 +1,18 @@ import SwiftUI struct ThemeList: View { - let items: [Item] - @ViewBuilder let itemContent: (Item) -> Content + private let items: [Item] + private let onMove: ((IndexSet, Int) -> Void)? + private let itemContent: (Item) -> Content @Environment(\.themeListStyle) var themeListStyle + init(items: [Item], onMove: ((IndexSet, Int) -> Void)? = nil, @ViewBuilder itemContent: @escaping (Item) -> Content) { + self.items = items + self.onMove = onMove + self.itemContent = itemContent + } + var body: some View { switch themeListStyle { case .lawrence, .bordered, .transparentInline, .borderedLawrence: @@ -26,6 +33,7 @@ struct ThemeList: View { .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) } + .onMove(perform: onMove) Spacer() .frame(height: .margin16)