Skip to content

Commit

Permalink
Add watchlist swipe actions to new Market module
Browse files Browse the repository at this point in the history
  • Loading branch information
ealymbaev committed May 16, 2024
1 parent 692c37f commit c8357ad
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 50 deletions.
20 changes: 20 additions & 0 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2982,6 +2982,10 @@
D31C4764238BF176008CB818 /* FeeRateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31C475A238BF175008CB818 /* FeeRateState.swift */; };
D339A93D29126D0F00B895BE /* HsCryptoKit in Frameworks */ = {isa = PBXBuildFile; productRef = D339A93C29126D0F00B895BE /* HsCryptoKit */; };
D339A93F29126D2A00B895BE /* HsCryptoKit in Frameworks */ = {isa = PBXBuildFile; productRef = D339A93E29126D2A00B895BE /* HsCryptoKit */; };
D3402AEE2BF5D58B003BF6F8 /* FavoritesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AED2BF5D58B003BF6F8 /* FavoritesViewModel.swift */; };
D3402AEF2BF5D58B003BF6F8 /* FavoritesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AED2BF5D58B003BF6F8 /* FavoritesViewModel.swift */; };
D3402AF12BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF02BF5D59D003BF6F8 /* FavoritesModifier.swift */; };
D3402AF22BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AF02BF5D59D003BF6F8 /* FavoritesModifier.swift */; };
D3447DEA25E38300009928D9 /* WalletConnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B968B299A67FC7FEAE3 /* WalletConnectManager.swift */; };
D3447DEB25E38300009928D9 /* WalletConnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B968B299A67FC7FEAE3 /* WalletConnectManager.swift */; };
D34903172BE8DF48005F147B /* BinanceSendHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34903162BE8DF48005F147B /* BinanceSendHandler.swift */; };
Expand Down Expand Up @@ -4885,6 +4889,8 @@
D3285F5120BD158F00644076 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D3373D9420BEC7B30082BC4A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
D3373DB120C52F640082BC4A /* LaunchScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = "<group>"; };
D3402AED2BF5D58B003BF6F8 /* FavoritesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewModel.swift; sourceTree = "<group>"; };
D3402AF02BF5D59D003BF6F8 /* FavoritesModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModifier.swift; sourceTree = "<group>"; };
D34903162BE8DF48005F147B /* BinanceSendHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinanceSendHandler.swift; sourceTree = "<group>"; };
D34903192BE8DF5F005F147B /* BinancePreSendHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinancePreSendHandler.swift; sourceTree = "<group>"; };
D34E941B21F86C3500AD8E90 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7722,6 +7728,7 @@
58AAA9EB9618EBC895D0B123 /* Market */ = {
isa = PBXGroup;
children = (
D3402AEC2BF5D569003BF6F8 /* Favorites */,
D3833AFA2BF335B800ACECFB /* News */,
D3833AF02BF20B7200ACECFB /* Pairs */,
D3833AEC2BF1F0AC00ACECFB /* Platform */,
Expand Down Expand Up @@ -9288,6 +9295,15 @@
path = UnstoppableWallet;
sourceTree = "<group>";
};
D3402AEC2BF5D569003BF6F8 /* Favorites */ = {
isa = PBXGroup;
children = (
D3402AED2BF5D58B003BF6F8 /* FavoritesViewModel.swift */,
D3402AF02BF5D59D003BF6F8 /* FavoritesModifier.swift */,
);
path = Favorites;
sourceTree = "<group>";
};
D3833AD52BEE1A2900ACECFB /* Watchlist */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -10619,12 +10635,14 @@
11B35052C5059CDD8E4BA940 /* BackupMnemonicWordCell.swift in Sources */,
11B35DF1D8B5125CF13A1812 /* RestoreMnemonicHintView.swift in Sources */,
11B35C8A1082D0A8F0B354B1 /* RestoreMnemonicHintCell.swift in Sources */,
D3402AF22BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */,
11B35AC9650545DEBC6C2C90 /* EvmAccountRestoreState.swift in Sources */,
11B35EBC6D5608F23DF8581E /* EvmAccountRestoreStateManager.swift in Sources */,
11B35C43886D9A0F0C69EF33 /* EvmAccountRestoreStateStorage.swift in Sources */,
11B35B100187D9909A8490A7 /* NftAdapterManager.swift in Sources */,
11B35EC3B9E9C778183E1136 /* EvmNftAdapter.swift in Sources */,
11B350F58D6907C9A9A79F6B /* NftViewController.swift in Sources */,
D3402AEF2BF5D58B003BF6F8 /* FavoritesViewModel.swift in Sources */,
11B3593134900A8FC7C075B6 /* NftService.swift in Sources */,
11B35B6C74E672B2699E8207 /* NftModule.swift in Sources */,
11B35F8649859802080BA580 /* NftViewModel.swift in Sources */,
Expand Down Expand Up @@ -12177,12 +12195,14 @@
6BCD53022A161F4100993F20 /* ICloudBackupTermsViewModel.swift in Sources */,
11B35066480B6EB9F124EBC0 /* ExtendedKeyService.swift in Sources */,
11B359601D8967EEE00B5991 /* BackupMnemonicWordsCell.swift in Sources */,
D3402AF12BF5D59D003BF6F8 /* FavoritesModifier.swift in Sources */,
11B3590E40C88ADD16DEEABB /* BackupMnemonicWordCell.swift in Sources */,
11B35FD18C255E2C6D75F38A /* RestoreMnemonicHintView.swift in Sources */,
11B356BCDD5E64C6D6F49489 /* RestoreMnemonicHintCell.swift in Sources */,
11B35B152001ADE5E98D1414 /* EvmAccountRestoreState.swift in Sources */,
11B3573F7ED8577EF9F12EF9 /* EvmAccountRestoreStateManager.swift in Sources */,
11B3563B5D19C7A4EDFC8FC1 /* EvmAccountRestoreStateStorage.swift in Sources */,
D3402AEE2BF5D58B003BF6F8 /* FavoritesViewModel.swift in Sources */,
11B356C6C07BE3588A5D52DE /* NftAdapterManager.swift in Sources */,
11B3583C1A73A11974ADAEBB /* EvmNftAdapter.swift in Sources */,
11B3575108E28705A2F47BA9 /* NftViewController.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,43 @@ class FavoriteCoinRecordStorage {
}

extension FavoriteCoinRecordStorage {
var favoriteCoinRecords: [FavoriteCoinRecord] {
try! dbPool.read { db in
func favoriteCoinRecords() throws -> [FavoriteCoinRecord] {
try dbPool.read { db in
try FavoriteCoinRecord.fetchAll(db)
}
}

func save(favoriteCoinRecord: FavoriteCoinRecord) {
_ = try! dbPool.write { db in
func save(favoriteCoinRecord: FavoriteCoinRecord) throws {
_ = try dbPool.write { db in
try favoriteCoinRecord.insert(db)
}
}

func save(favoriteCoinRecords: [FavoriteCoinRecord]) {
_ = try? dbPool.write { db in
func save(favoriteCoinRecords: [FavoriteCoinRecord]) throws {
_ = try dbPool.write { db in
for record in favoriteCoinRecords {
try record.insert(db)
}
}
}

func deleteAll() {
_ = try! dbPool.write { db in
func deleteAll() throws {
_ = try dbPool.write { db in
try FavoriteCoinRecord
.deleteAll(db)
}
}

func deleteFavoriteCoinRecord(coinUid: String) {
_ = try! dbPool.write { db in
func deleteFavoriteCoinRecord(coinUid: String) throws {
_ = try dbPool.write { db in
try FavoriteCoinRecord
.filter(FavoriteCoinRecord.Columns.coinUid == coinUid)
.deleteAll(db)
}
}

func favoriteCoinRecordExists(coinUid: String) -> Bool {
try! dbPool.read { db in
func favoriteCoinRecordExists(coinUid: String) throws -> Bool {
try dbPool.read { db in
try FavoriteCoinRecord
.filter(FavoriteCoinRecord.Columns.coinUid == coinUid)
.fetchCount(db) > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class AppBackupProvider {
let syncSources = EvmSyncSourceManager.SyncSourceBackup(selected: selected, custom: [])
return RawFullBackup(
accounts: accounts,
watchlistIds: favoritesManager.allCoinUids,
watchlistIds: Array(favoritesManager.coinUids),
contacts: contactManager.backupContactBook?.contacts ?? [],
settings: settings(evmSyncSources: syncSources),
customSyncSources: custom
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Combine
import Foundation
import MarketKit
import RxRelay
Expand All @@ -6,7 +7,7 @@ import RxSwift
class CoinPageService {
let fullCoin: FullCoin
private let favoritesManager: FavoritesManager
private let disposeBag = DisposeBag()
private var cancellables = Set<AnyCancellable>()

private let favoriteRelay = PublishRelay<Bool>()
private(set) var favorite: Bool = false {
Expand All @@ -21,7 +22,9 @@ class CoinPageService {
self.fullCoin = fullCoin
self.favoritesManager = favoritesManager

subscribe(disposeBag, favoritesManager.coinUidsUpdatedObservable) { [weak self] in self?.syncFavorite() }
favoritesManager.coinUidsPublisher
.sink { [weak self] _ in self?.syncFavorite() }
.store(in: &cancellables)

syncFavorite()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,65 @@
import RxCocoa
import RxSwift
import Combine
import WidgetKit

class FavoritesManager {
private let storage: FavoriteCoinRecordStorage
private let sharedStorage: SharedLocalStorage

private let coinUidsUpdatedRelay = PublishRelay<Void>()
private let coinUidsSubject = PassthroughSubject<Set<String>, Never>()

var coinUids: Set<String> {
didSet {
coinUidsSubject.send(coinUids)
syncSharedStorage()
}
}

init(storage: FavoriteCoinRecordStorage, sharedStorage: SharedLocalStorage) {
self.storage = storage
self.sharedStorage = sharedStorage

do {
let records = try storage.favoriteCoinRecords()
coinUids = Set(records.map(\.coinUid))
} catch {
coinUids = Set()
}

syncSharedStorage()
}

private func syncSharedStorage() {
sharedStorage.set(value: allCoinUids, for: AppWidgetConstants.keyFavoriteCoinUids)
sharedStorage.set(value: Array(coinUids), for: AppWidgetConstants.keyFavoriteCoinUids)
WidgetCenter.shared.reloadTimelines(ofKind: AppWidgetConstants.watchlistWidgetKind)
}
}

extension FavoritesManager {
var coinUidsUpdatedObservable: Observable<Void> {
coinUidsUpdatedRelay.asObservable()
}

var allCoinUids: [String] {
storage.favoriteCoinRecords.map(\.coinUid)
var coinUidsPublisher: AnyPublisher<Set<String>, Never> {
coinUidsSubject.eraseToAnyPublisher()
}

func add(coinUid: String) {
storage.save(favoriteCoinRecord: FavoriteCoinRecord(coinUid: coinUid))
coinUidsUpdatedRelay.accept(())
syncSharedStorage()
coinUids.insert(coinUid)
try? storage.save(favoriteCoinRecord: FavoriteCoinRecord(coinUid: coinUid))
}

func add(coinUids: [String]) {
storage.save(favoriteCoinRecords: coinUids.map { FavoriteCoinRecord(coinUid: $0) })
coinUidsUpdatedRelay.accept(())
syncSharedStorage()
self.coinUids.formUnion(coinUids)
try? storage.save(favoriteCoinRecords: coinUids.map { FavoriteCoinRecord(coinUid: $0) })
}

func removeAll() {
storage.deleteAll()
coinUids = Set()
try? storage.deleteAll()
}

func remove(coinUid: String) {
storage.deleteFavoriteCoinRecord(coinUid: coinUid)
coinUidsUpdatedRelay.accept(())
syncSharedStorage()
coinUids.remove(coinUid)
try? storage.deleteFavoriteCoinRecord(coinUid: coinUid)
}

func isFavorite(coinUid: String) -> Bool {
storage.favoriteCoinRecordExists(coinUid: coinUid)
coinUids.contains(coinUid)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftUI

struct MarketCoinsView: View {
@ObservedObject var viewModel: MarketCoinsViewModel
@StateObject var favoritesViewModel = FavoritesViewModel()

@State private var sortBySelectorPresented = false
@State private var topSelectorPresented = false
Expand Down Expand Up @@ -107,11 +108,11 @@ struct MarketCoinsView: View {

@ViewBuilder private func list(marketInfos: [MarketInfo]) -> some View {
ThemeList(items: marketInfos) { marketInfo in
let coin = marketInfo.fullCoin.coin

ClickableRow(action: {
presentedFullCoin = marketInfo.fullCoin
}) {
let coin = marketInfo.fullCoin.coin

itemContent(
imageUrl: URL(string: coin.imageUrl),
code: coin.code,
Expand All @@ -121,6 +122,7 @@ struct MarketCoinsView: View {
diff: marketInfo.priceChangeValue(timePeriod: viewModel.timePeriod)
)
}
.favoriteSwipeActions(viewModel: favoritesViewModel, coinUid: coin.uid)
}
.themeListStyle(.transparent)
.refreshable {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import SwiftUI

struct FavoritesModifier: ViewModifier {
@ObservedObject var viewModel: FavoritesViewModel
let coinUid: String

func body(content: Content) -> some View {
content
.swipeActions {
if viewModel.coinUids.contains(coinUid) {
Button {
viewModel.remove(coinUid: coinUid)
} label: {
Image("star_off_24").renderingMode(.template)
}
.tint(.themeLucian)
} else {
Button {
viewModel.add(coinUid: coinUid)
} label: {
Image("star_24").renderingMode(.template)
}
.tint(.themeJacob)
}
}
}
}

extension View {
func favoriteSwipeActions(viewModel: FavoritesViewModel, coinUid: String) -> some View {
modifier(FavoritesModifier(viewModel: viewModel, coinUid: coinUid))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Combine

class FavoritesViewModel: ObservableObject {
private let favoritesManager = App.shared.favoritesManager
private var cancellables = Set<AnyCancellable>()

@Published var coinUids: Set<String>

init() {
coinUids = favoritesManager.coinUids

favoritesManager.coinUidsPublisher
.sink { [weak self] in self?.coinUids = $0 }
.store(in: &cancellables)
}
}

extension FavoritesViewModel {
func add(coinUid: String) {
favoritesManager.add(coinUid: coinUid)
}

func remove(coinUid: String) {
favoritesManager.remove(coinUid: coinUid)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class MarketWatchlistService: IMarketSingleSortHeaderService {
}

private func syncCoinUids() {
coinUids = favoritesManager.allCoinUids
coinUids = Array(favoritesManager.coinUids)

if case let .loaded(marketInfos, _, _) = state {
let newMarketInfos = marketInfos.filter { marketInfo in
Expand Down Expand Up @@ -107,7 +107,10 @@ extension MarketWatchlistService: IMarketListService {
}
.store(in: &cancellables)

subscribe(disposeBag, favoritesManager.coinUidsUpdatedObservable) { [weak self] in self?.syncCoinUids() }
favoritesManager.coinUidsPublisher
.sink { [weak self] _ in self?.syncCoinUids() }
.store(in: &cancellables)

subscribe(disposeBag, appManager.willEnterForegroundObservable) { [weak self] in self?.syncMarketInfos() }

syncCoinUids()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ struct MarketWatchlistView: View {

@ViewBuilder private func list(marketInfos: [MarketInfo], signals: [String: TechnicalAdvice.Advice]) -> some View {
ThemeList(items: marketInfos) { marketInfo in
let coin = marketInfo.fullCoin.coin

ClickableRow(action: {
presentedFullCoin = marketInfo.fullCoin
}) {
let coin = marketInfo.fullCoin.coin

itemContent(
imageUrl: URL(string: coin.imageUrl),
code: coin.code,
Expand All @@ -123,6 +123,14 @@ struct MarketWatchlistView: View {
signal: viewModel.showSignals ? signals[coin.uid] : nil
)
}
.swipeActions {
Button(role: .destructive) {
viewModel.remove(coinUid: coin.uid)
} label: {
Image("star_off_24").renderingMode(.template)
}
.tint(.themeLucian)
}
}
.themeListStyle(.transparent)
.refreshable {
Expand Down
Loading

0 comments on commit c8357ad

Please sign in to comment.