Skip to content

Commit

Permalink
Improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
Kamalifard, Mehran committed Apr 17, 2024
1 parent bfe8fd9 commit dabb921
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 97 deletions.
22 changes: 13 additions & 9 deletions EasyCrypto.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
459B45D12A0BBCD3001C93BA /* Double + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459B45D02A0BBCD3001C93BA /* Double + Extension.swift */; };
45B8DF022A0A3E8400BF2EB7 /* MockDataAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B8DF012A0A3E8400BF2EB7 /* MockDataAction.swift */; };
45B8DF062A0A6C7400BF2EB7 /* CacheStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B8DF052A0A6C7400BF2EB7 /* CacheStack.swift */; };
65239A462BCD0F3B001FC67D /* HandleViewModelStateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65239A452BCD0F3B001FC67D /* HandleViewModelStateModifier.swift */; };
A803DFC4297E92D000357A7F /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A803DFC3297E92D000357A7F /* Color.swift */; };
A803DFC9297E93B700357A7F /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A803DFC5297E93B600357A7F /* FontManager.swift */; };
A803DFD3297E943100357A7F /* Cancelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A803DFD2297E943100357A7F /* Cancelable.swift */; };
Expand Down Expand Up @@ -230,6 +231,7 @@
459B45D02A0BBCD3001C93BA /* Double + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double + Extension.swift"; sourceTree = "<group>"; };
45B8DF012A0A3E8400BF2EB7 /* MockDataAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataAction.swift; sourceTree = "<group>"; };
45B8DF052A0A6C7400BF2EB7 /* CacheStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheStack.swift; sourceTree = "<group>"; };
65239A452BCD0F3B001FC67D /* HandleViewModelStateModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleViewModelStateModifier.swift; sourceTree = "<group>"; };
A803DFBE297E91FF00357A7F /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
A803DFC3297E92D000357A7F /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; };
A803DFC5297E93B600357A7F /* FontManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -637,14 +639,6 @@
path = TabItemView;
sourceTree = "<group>";
};
457B87862A0F7ECE00B04D9E /* ViewDidLoadModifier */ = {
isa = PBXGroup;
children = (
457B87872A0F7EE400B04D9E /* ViewDidLoadModifier.swift */,
);
path = ViewDidLoadModifier;
sourceTree = "<group>";
};
457B87902A0F8E5E00B04D9E /* CoinDetail */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -672,6 +666,15 @@
path = Cache;
sourceTree = "<group>";
};
65239A482BCD104F001FC67D /* ViewModifier */ = {
isa = PBXGroup;
children = (
65239A452BCD0F3B001FC67D /* HandleViewModelStateModifier.swift */,
457B87872A0F7EE400B04D9E /* ViewDidLoadModifier.swift */,
);
path = ViewModifier;
sourceTree = "<group>";
};
A803DFB7297E918900357A7F /* Theme */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -701,7 +704,6 @@
A803DFBA297E91BF00357A7F /* Core */ = {
isa = PBXGroup;
children = (
457B87862A0F7ECE00B04D9E /* ViewDidLoadModifier */,
A803DFB9297E91AF00357A7F /* Components */,
027A1B0629D08301008B10D2 /* Log */,
02D8622229BBAE4500F77BC7 /* WorkScheduler */,
Expand Down Expand Up @@ -772,6 +774,7 @@
A803DFCD297E93F600357A7F /* Support */ = {
isa = PBXGroup;
children = (
65239A482BCD104F001FC67D /* ViewModifier */,
A851A2342989155F007F4CF9 /* MessageHelper */,
A851A22F29891214007F4CF9 /* Extension */,
);
Expand Down Expand Up @@ -1203,6 +1206,7 @@
A851A22B29890C0B007F4CF9 /* Configuration.swift in Sources */,
A8D4A10E299E0E1300C11107 /* AppRouter.swift in Sources */,
02BC668029B882F400785196 /* PlaceholderModifier.swift in Sources */,
65239A462BCD0F3B001FC67D /* HandleViewModelStateModifier.swift in Sources */,
A8D4A109299E0A6F00C11107 /* CoinDetailCoordinator.swift in Sources */,
02593C53298E992500ADDA20 /* MarketPriceRepository.swift in Sources */,
A851A21029890B75007F4CF9 /* NetworkClientManager.swift in Sources */,
Expand Down
52 changes: 20 additions & 32 deletions EasyCrypto/Base/BaseViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,71 +7,59 @@

import Foundation
import Combine
import SwiftUI

enum ViewModelStatus: Equatable {
case loadStart
case dismissAlert
case emptyStateHandler(title: String, isShow: Bool)
case emptyStateHandler(title: String)
}

protocol BaseViewModelEventSource: AnyObject {
var loadingState: CurrentValueSubject<ViewModelStatus, Never> { get }
}

protocol ViewModelService: AnyObject {
func callWithProgress<ReturnType>(argument: AnyPublisher<ReturnType?,
APIError>,
callback: @escaping (_ data: ReturnType?) -> Void)
func callWithoutProgress<ReturnType>(argument: AnyPublisher<ReturnType?,
APIError>,
callback: @escaping (_ data: ReturnType?) -> Void)
func call<ReturnType>(callWithIndicator: Bool,
argument: AnyPublisher<ReturnType?,
APIError>,
callback: @escaping (_ data: ReturnType?) -> Void)
}

typealias BaseViewModel = BaseViewModelEventSource & ViewModelService

open class DefaultViewModel: BaseViewModel, ObservableObject {

var loadingState = CurrentValueSubject<ViewModelStatus, Never>(.dismissAlert)
let subscriber = Cancelable()

func callWithProgress<ReturnType>(argument: AnyPublisher<ReturnType?,
APIError>,
callback: @escaping (_ data: ReturnType?) -> Void) {
self.loadingState.send(.loadStart)


func call<ReturnType>(callWithIndicator: Bool = true,
argument: AnyPublisher<ReturnType?,
APIError>,
callback: @escaping (_ data: ReturnType?) -> Void) {

if callWithIndicator {
self.loadingState.send(.loadStart)
}

let completionHandler: (Subscribers.Completion<APIError>) -> Void = { [weak self] completion in
switch completion {
case .failure(let error):
self?.loadingState.send(.dismissAlert)
self?.loadingState.send(.emptyStateHandler(title: error.desc, isShow: true))
self?.loadingState.send(.emptyStateHandler(title: error.desc))
case .finished:
self?.loadingState.send(.dismissAlert)
}
}

let resultValueHandler: (ReturnType?) -> Void = { data in
callback(data)
}

argument
.subscribe(on: WorkScheduler.backgroundWorkScheduler)
.receive(on: WorkScheduler.mainScheduler)
.sink(receiveCompletion: completionHandler, receiveValue: resultValueHandler)
.store(in: subscriber)
}

func callWithoutProgress<ReturnType>(argument: AnyPublisher<ReturnType?,
APIError>,
callback: @escaping (_ data: ReturnType?) -> Void) {

let resultValueHandler: (ReturnType?) -> Void = { data in
callback(data)
}

argument
.subscribe(on: WorkScheduler.backgroundWorkScheduler)
.receive(on: WorkScheduler.mainScheduler)
.sink(receiveCompletion: {_ in }, receiveValue: resultValueHandler)
.store(in: subscriber)
}
}
1 change: 0 additions & 1 deletion EasyCrypto/Core/Components/AlertView/CustomAlertView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ struct CustomAlertView: View {
.resizable()
.scaledToFit()
.frame(width: 80, height: 80)
// swiftlint:disable:next opening_brace
} else if let title = title {
Text(title)
.font(.headline)
Expand Down
1 change: 1 addition & 0 deletions EasyCrypto/Core/Constants/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum Constants {
enum Title {
static let mainTitle: String = "EasyCrypto"
static let detailTitle: String = "Coin Detail"
static let errorTitle: String = "Error"
}
enum PlaceHolder {
static let searchCoins: String = "Search coins"
Expand Down
25 changes: 7 additions & 18 deletions EasyCrypto/Presentation/CoinDetail/View/CoinDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ struct CoinDetailView: Coordinatable {

@ObservedObject private(set) var viewModel: CoinDetailViewModel
@State private var isLoading: Bool = false
@State private var presentAlert = false
@State private var alertMessage: String = ""

let subscriber = Cancelable()
var id: String?
Expand Down Expand Up @@ -62,8 +64,12 @@ struct CoinDetailView: Coordinatable {
.onAppear {
self.viewModel.apply(.onAppear(id: self.id.orWhenNilOrEmpty("")))
}
.handleViewModelState(viewModel: viewModel,
isLoading: $isLoading,
alertMessage: $alertMessage,
presentAlert: $presentAlert)
}
}.onAppear(perform: handleState)
}
}
}

Expand All @@ -73,23 +79,6 @@ extension CoinDetailView {
}
}

extension CoinDetailView {
private func handleState() {
self.viewModel.loadingState
.receive(on: WorkScheduler.mainThread)
.sink { state in
switch state {
case .loadStart:
self.isLoading = true
case .dismissAlert:
self.isLoading = false
case .emptyStateHandler(_, _):
self.isLoading = false
}
}.store(in: subscriber)
}
}

struct CoinDetailView_Previews: PreviewProvider {
static var previews: some View {
CoinDetailView(viewModel: CoinDetailViewModel())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension CoinDetailViewModel: DataFlowProtocol {

func getCoinDetailData(id: String) {
guard !String.isNilOrEmpty(string: id) else {return}
self.callWithProgress(argument: self.coinDetailUsecase.execute(id: id)) { [weak self] data in
self.call(argument: self.coinDetailUsecase.execute(id: id)) { [weak self] data in
guard let data = data else {return}
self?.coinData = data
}
Expand Down
4 changes: 4 additions & 0 deletions EasyCrypto/Presentation/Detail/View/DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ struct DetailView: View {
}

var body: some View {
content
}

var content: some View {
ZStack {
Color.darkBlue
.edgesIgnoringSafeArea(.all)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI
import Combine

struct MainCoordinator: CoordinatorProtocol {

@StateObject var viewModel: MainViewModel

@State var activeRoute: Destination? = Destination(route: .first(item: MarketsPrice()))
Expand Down Expand Up @@ -38,9 +38,9 @@ struct MainCoordinator: CoordinatorProtocol {

extension MainCoordinator {
struct Destination: DestinationProtocol {

var route: MainView.Routes

@ViewBuilder
var content: some View {
switch route {
Expand Down
48 changes: 19 additions & 29 deletions EasyCrypto/Presentation/Main/View/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ struct MainView: Coordinatable {

typealias Route = Routes

@StateObject var viewModel: MainViewModel
@ObservedObject var viewModel: MainViewModel

enum Constant {
static let searchHeight: CGFloat = 55
Expand All @@ -24,12 +24,16 @@ struct MainView: Coordinatable {
@State private var shouldShowDropdown = false
@State private var searchText: String = .empty
@State private var isLoading: Bool = false
@State private var presentAlert = true
@State private var alertMesagee: String = ""
@State private var presentAlert = false
@State private var alertMessage: String = ""

let subscriber = Cancelable()

var body: some View {
content
}

var content: some View {
NavigationView {
ZStack {
Color.darkBlue
Expand Down Expand Up @@ -63,15 +67,15 @@ struct MainView: Coordinatable {
.padding(.top, 20)
TabView(selection: $tabIndex) {
if tabIndex == 0 {
coinsList()
coinsList
} else {
whishList()
whishList
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
Spacer()
if !presentAlert {
self.showAlert("Error", alertMesagee)
if presentAlert {
self.showAlert(viewModel.errorTitle, alertMessage)
}
}
}
Expand All @@ -80,10 +84,14 @@ struct MainView: Coordinatable {
.onViewDidLoad {
self.viewModel.apply(.onAppear)
}
.handleViewModelState(viewModel: viewModel,
isLoading: $isLoading,
alertMessage: $alertMessage,
presentAlert: $presentAlert)
}
.onAppear(perform: handleState)
}
func coinsList() -> some View {

var coinsList: some View {
ScrollView {
LazyVStack {
ForEach(viewModel.marketData, id: \.id) { item in
Expand Down Expand Up @@ -111,7 +119,8 @@ struct MainView: Coordinatable {
.padding()
}
}
func whishList() -> some View {

var whishList: some View {
ScrollView {
VStack {
ForEach(viewModel.wishListData, id: \.symbol) { item in
Expand All @@ -135,25 +144,6 @@ extension MainView {
}
}

extension MainView {
private func handleState() {
self.viewModel.loadingState
.receive(on: WorkScheduler.mainThread)
.sink { state in
switch state {
case .loadStart:
self.isLoading = true
case .dismissAlert:
self.isLoading = false
case .emptyStateHandler(let message, _):
self.isLoading = false
self.presentAlert = false
self.alertMesagee = message
}
}.store(in: subscriber)
}
}

extension MainView {
func showAlert(_ title: String, _ message: String) -> some View {
CustomAlertView(title: title, message: message, primaryButtonLabel: "Retry", primaryButtonAction: {
Expand Down
11 changes: 9 additions & 2 deletions EasyCrypto/Presentation/Main/ViewModel/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ protocol DefaultMainViewModel: MainViewModelProtocol { }
final class MainViewModel: DefaultViewModel, DefaultMainViewModel {

let title: String = Constants.Title.mainTitle
let errorTitle: String = Constants.Title.errorTitle

private let marketPriceUsecase: MarketPriceUsecaseProtocol
private let searchMarketUsecase: SearchMarketUsecaseProtocol
Expand Down Expand Up @@ -100,7 +101,13 @@ extension MainViewModel: DataFlowProtocol {
func getMarketData(vs_currency: String = "usd",
order: String = "market_cap_desc",
sparkline: Bool = false) {
self.callWithProgress(argument: self.marketPriceUsecase.execute(vs_currency: vs_currency,

// Check if the number of market data entries is already 30 to limit service calls
if marketData.count == 30 {
return
}

self.call(argument: self.marketPriceUsecase.execute(vs_currency: vs_currency,
order: order,
per_page: self.perPage,
page: self.page,
Expand All @@ -112,7 +119,7 @@ extension MainViewModel: DataFlowProtocol {
}

func searchMarketData(text: String) {
self.callWithProgress(argument: self.searchMarketUsecase.execute(text: text)) { [weak self] data in
self.call(argument: self.searchMarketUsecase.execute(text: text)) { [weak self] data in
let coin = data?.coins ?? []
self?.searchData = []
self?.searchData = coin
Expand Down
Loading

0 comments on commit dabb921

Please sign in to comment.