Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#24] [iOS] [Integrate] As a user, I can log out, Part: ViewModel #101

Merged
merged 8 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions iosApp/Survey.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
09A9F8D92952B431009DE583 /* GenericScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9F8D72952B42D009DE583 /* GenericScreen.swift */; };
09A9F8DC2952B574009DE583 /* SurveyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9F8DA2952B54F009DE583 /* SurveyScreen.swift */; };
09A9F8DF2952B585009DE583 /* AccountScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9F8DD2952B582009DE583 /* AccountScreen.swift */; };
09A9F8F329544F5E009DE583 /* AccountView+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9F8F229544F5E009DE583 /* AccountView+DataSource.swift */; };
09A9F8F8295479CA009DE583 /* AccountViewDataSourceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9F8F5295479C3009DE583 /* AccountViewDataSourceSpec.swift */; };
09A9F8FB29555505009DE583 /* KMPNative+Guaranteed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9F8FA29555505009DE583 /* KMPNative+Guaranteed.swift */; };
09C2F3FD2930943700F44818 /* ResetPasswordViewDataSourceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C2F3FB2930943200F44818 /* ResetPasswordViewDataSourceSpec.swift */; };
09C2F4032934588B00F44818 /* TimeInterval+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C2F4012934588400F44818 /* TimeInterval+Constants.swift */; };
09C2F4062934680000F44818 /* ScreenProtocol+Permission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C2F404293467FA00F44818 /* ScreenProtocol+Permission.swift */; };
Expand Down Expand Up @@ -249,6 +252,9 @@
09A9F8D72952B42D009DE583 /* GenericScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericScreen.swift; sourceTree = "<group>"; };
09A9F8DA2952B54F009DE583 /* SurveyScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyScreen.swift; sourceTree = "<group>"; };
09A9F8DD2952B582009DE583 /* AccountScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScreen.swift; sourceTree = "<group>"; };
09A9F8F229544F5E009DE583 /* AccountView+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountView+DataSource.swift"; sourceTree = "<group>"; };
09A9F8F5295479C3009DE583 /* AccountViewDataSourceSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewDataSourceSpec.swift; sourceTree = "<group>"; };
09A9F8FA29555505009DE583 /* KMPNative+Guaranteed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KMPNative+Guaranteed.swift"; sourceTree = "<group>"; };
09C2F3FB2930943200F44818 /* ResetPasswordViewDataSourceSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordViewDataSourceSpec.swift; sourceTree = "<group>"; };
09C2F4012934588400F44818 /* TimeInterval+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Constants.swift"; sourceTree = "<group>"; };
09C2F404293467FA00F44818 /* ScreenProtocol+Permission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScreenProtocol+Permission.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -699,6 +705,22 @@
path = Screens;
sourceTree = "<group>";
};
09A9F8F4295479B4009DE583 /* Account */ = {
isa = PBXGroup;
children = (
09A9F8F5295479C3009DE583 /* AccountViewDataSourceSpec.swift */,
);
path = Account;
sourceTree = "<group>";
};
09A9F8F9295554EF009DE583 /* KMPNative */ = {
isa = PBXGroup;
children = (
09A9F8FA29555505009DE583 /* KMPNative+Guaranteed.swift */,
);
path = KMPNative;
sourceTree = "<group>";
};
09C2F3FA293093F600F44818 /* ResetPassword */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -874,13 +896,15 @@
isa = PBXGroup;
children = (
09E6AC0F2951D8B5007F1EE3 /* AccountView.swift */,
09A9F8F229544F5E009DE583 /* AccountView+DataSource.swift */,
);
path = AccountView;
sourceTree = "<group>";
};
09FE26CE294B2639005A7F85 /* SurveySelection */ = {
isa = PBXGroup;
children = (
09A9F8F4295479B4009DE583 /* Account */,
09FE26CF294B264B005A7F85 /* SurveySelectionViewDataSourceSpec.swift */,
);
path = SurveySelection;
Expand Down Expand Up @@ -914,6 +938,7 @@
0F3BE49893E55BC4638B90F0 /* Extensions */ = {
isa = PBXGroup;
children = (
09A9F8F9295554EF009DE583 /* KMPNative */,
0982A7F4292371F000FC1976 /* MokoResources */,
09636AF728D47A3800A5CB97 /* SwiftUI */,
0F4C0078B1B4D7C1871DDE54 /* Foundation */,
Expand Down Expand Up @@ -1777,10 +1802,12 @@
09636B3028D8267D00A5CB97 /* ViewId+General.swift in Sources */,
09636B0228D4876100A5CB97 /* PrimaryButton.swift in Sources */,
09CE770C28E191B400EAA9EE /* AppCoordinator.swift in Sources */,
09A9F8FB29555505009DE583 /* KMPNative+Guaranteed.swift in Sources */,
09495FCF29110E820036BDFB /* String+URL.swift in Sources */,
09495F0728E40E130036BDFB /* FadePaginationView.swift in Sources */,
0982A80229278E9000FC1976 /* SurveySelectionView.swift in Sources */,
0982A80629278E9000FC1976 /* SurveyItemLoading.swift in Sources */,
09A9F8F329544F5E009DE583 /* AccountView+DataSource.swift in Sources */,
09495F0928E410A00036BDFB /* PageControlView.swift in Sources */,
09636AFD28D484CA00A5CB97 /* R+SwiftUI.swift in Sources */,
09495FCB291107760036BDFB /* Image+Constants.swift in Sources */,
Expand Down Expand Up @@ -1862,6 +1889,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
09A9F8F8295479CA009DE583 /* AccountViewDataSourceSpec.swift in Sources */,
09C2F413293777ED00F44818 /* Combine+Collect.swift in Sources */,
0964B1DD2947021400946FA1 /* SplashViewDataSourceSpec.swift in Sources */,
5BBBFAF49689F25A8C1D57AB /* AutoMockable.generated.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI

struct AppCoordinator: View {

@ObservedObject var coordinator = RouteCoordinator()
@StateObject var coordinator = RouteCoordinator()

var body: some View {
Router($coordinator.routes) { screen, _ in
Expand All @@ -23,7 +23,7 @@ struct AppCoordinator: View {
case .splash:
SplashView(coordinator: coordinator)
case .surveySelection:
SurveySelectionContainerView()
SurveySelectionContainerView(coordinator: coordinator)
}
}
minhnimble marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ protocol BaseCoordinator {
class RouteCoordinator: ObservableObject {

@Published var routes: Routes<Screen> = [.root(.splash, embedInNavigationView: true)]

func showLogin() {
routes = [.root(.login, embedInNavigationView: true)]
}
}

extension RouteCoordinator: BaseCoordinator {
Expand All @@ -32,8 +36,8 @@ extension RouteCoordinator: BaseCoordinator {

extension RouteCoordinator: SplashCoordinator {

func showLogin() {
routes = [.root(.login, embedInNavigationView: true)]
func showLoginFromSplash() {
showLogin()
}
}

Expand All @@ -47,3 +51,10 @@ extension RouteCoordinator: LoginCoordinator {
routes = [.root(.surveySelection)]
}
}

extension RouteCoordinator: AccountCoordinator {

func showLoginFromAccount() {
showLogin()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension ResetPasswordView {
}
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
guard let self = self else { return }
guard let self else { return }
self.updateStates(value)
guard let notification = value.successNotification else { return }
self.scheduleNotification(notification)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import SwiftUI
// sourcery: AutoMockable
protocol SplashCoordinator {

func showLogin()
func showLoginFromSplash()
func showHome()
}

Expand Down Expand Up @@ -43,7 +43,7 @@ extension SplashView {
}
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
guard let self = self else { return }
guard let self else { return }
self.updateStates(value)
}
.store(in: &cancellables)
Expand All @@ -60,7 +60,7 @@ extension SplashView {
if viewState.isLogin {
coordinator.showHome()
} else {
coordinator.showLogin()
coordinator.showLoginFromSplash()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@
import Shared
import SwiftUI

// sourcery: AutoMockable
protocol AccountCoordinator {

func showLoginFromAccount()
}

struct AccountView: View {

let account: AccountUiModel
@StateObject var dataSource: DataSource

var account: AccountUiModel? { dataSource.viewState.accountUiModel }

var body: some View {
ZStack {
Expand All @@ -38,16 +46,17 @@ struct AccountView: View {
.frame(width: 200.0)
.accessibilityElement(children: .contain)
.accessibility(.account(.view))
.loadingDialog(loading: $dataSource.isShowingLoading)
}

var profileSection: some View {
HStack(alignment: .firstTextBaseline) {
Text(account.name)
Text((account?.name).string)
.font(.boldLarge)
.lineLimit(1)
.accessibility(.account(.profileText))
Spacer()
Image.url(account.avatarUrl.string)
Image.url((account?.avatarUrl).string)
.resizable()
.frame(width: 36.0, height: 36.0)
.cornerRadius(18.0)
Expand All @@ -59,7 +68,7 @@ struct AccountView: View {

var logoutSection: some View {
Button {
// TODO: Add logout action
dataSource.logOut()
} label: {
Text(String.localizeId.account_logout_button())
.font(.regularLarge)
Expand All @@ -70,10 +79,19 @@ struct AccountView: View {
}

var versionSection: some View {
Text(account.appVersion)
Text((account?.appVersion).string)
.font(.regularTiny)
.foregroundColor(.white)
.opacity(0.5)
.accessibility(.account(.versionText))
}

init(account: AccountUiModel, coordinator: AccountCoordinator) {
_dataSource = StateObject(
wrappedValue: DataSource(
coordinator: coordinator,
viewModel: AccountViewModel(accountUiModel: account)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// AccountView+DataSource.swift
// Survey
//
// Created by Bliss on 22/12/22.
// Copyright © 2022 Nimble. All rights reserved.
//

import Combine
import KMPNativeCoroutinesCombine
import Shared

extension AccountView {

final class DataSource: ObservableObject {

private let viewModel: AccountViewModel
private let coordinator: AccountCoordinator

@Published private(set) var viewState = AccountViewState()
@Published var isShowingLoading = false

private var cancellables = Set<AnyCancellable>()

init(
coordinator: AccountCoordinator,
viewModel: AccountViewModel
) {
self.viewModel = viewModel
self.coordinator = coordinator
createGuaranteedPublisher(
for: viewModel.viewStateNative,
fallback: AccountViewState()
)
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
guard let self else { return }
self.updateStates(value)
}
.store(in: &cancellables)
}

func logOut() {
viewModel.logOut()
}

private func updateStates(_ state: AccountViewState) {
viewState = state
isShowingLoading = state.isLoading
if state.isLogout {
coordinator.showLoginFromAccount()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import SwiftUI

struct SurveySelectionContainerView: View {

let coordinator: AccountCoordinator

@StateObject private var dataSource = SurveySelectionView.DataSource()

@State var isShowingAccountView = false
Expand All @@ -35,7 +37,7 @@ struct SurveySelectionContainerView: View {
HStack {
Spacer()
if isShowingAccountView, let account = dataSource.viewState.accountUiModel {
AccountView(account: account)
AccountView(account: account, coordinator: coordinator)
.gesture(
DragGesture(minimumDistance: 50)
.onEnded { gesture in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension SurveySelectionView {
}
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
guard let self = self else { return }
guard let self else { return }
self.updateStates(value)
}
.store(in: &cancellables)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// KMPNative+Guaranteed.swift
// Survey
//
// Created by Bliss on 23/12/22.
// Copyright © 2022 Nimble. All rights reserved.
//

import Combine
import KMPNativeCoroutinesCombine
import KMPNativeCoroutinesCore
import Shared

public func createGuaranteedPublisher<Output, Failure: Error, Unit>(
for nativeFlow: @escaping NativeFlow<Output, Failure, Unit>,
fallback: Output
) -> AnyPublisher<Output, Just<Output>.Failure> {
return createPublisher(for: nativeFlow)
.catch { _ -> Just<Output> in
Just(fallback)
}
.eraseToAnyPublisher()
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,13 @@ protocol GetAppVersionUseCaseKMM: GetAppVersionUseCase {
@escaping (Error?, KotlinUnit) -> KotlinUnit
) -> () -> KotlinUnit
}

// sourcery: AutoMockable
protocol LogOutUseCaseKMM: LogOutUseCase {

func invoke() -> Kotlinx_coroutines_coreFlow
func invokeNative() -> (
@escaping (KotlinUnit, KotlinUnit) -> KotlinUnit,
@escaping (Error?, KotlinUnit) -> KotlinUnit
) -> () -> KotlinUnit
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import Foundation
final class AccountScreen: GenericScreen {

func navigateToHome() {
tester.tapScreen(at: .init(x: 10.0, y: 100.0))
if tester.tryFindingView(withAccessibilityIdentifier: ViewId.account(.logoutButton)()) {
tester.tapScreen(at: .init(x: 10.0, y: 100.0))
}
}

func logOut() {
tester.tapView(withAccessibilityIdentifier: ViewId.account(.logoutButton)())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ final class AccountSpec: QuickSpec {
}
}
}

describe("its logout button") {

context("when tapped") {

beforeEach {
accountScreen.logOut()
}

it("shows login screen") {
self.tester().waitForView(withAccessibilityIdentifier: ViewId.login(.loginButton)())
}
}
}
}
}
}
Expand Down
Loading