Skip to content

Commit

Permalink
[#24] Logout UI integration with test
Browse files Browse the repository at this point in the history
[#22] Rebase fix test

[#21] Rebase

[#23] Fix KIF tests

[#23] Fix KIF tests

[#24] Logout UI integration with test
  • Loading branch information
blyscuit committed Jan 3, 2023
1 parent 454586f commit f16de65
Show file tree
Hide file tree
Showing 22 changed files with 336 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,27 @@
// Copyright © 2022 Nimble. All rights reserved.
//

import Shared
import SwiftUI

struct AccountView: View {

let account: AccountUiModel

var body: some View {
ZStack {
Rectangle()
.foregroundColor(.black.opacity(0.9))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
VStack(alignment: .leading) {
// TODO: Use real data from ViewModel
HStack(alignment: .firstTextBaseline) {
Text("Mai")
Text(account.name)
.font(.boldLarge)
.lineLimit(1)
.accessibility(.account(.profileText))
Spacer()
Assets.background.image
Image.url(account.imageUrl.string)
.resizable()
.frame(width: 36.0, height: 36.0)
.cornerRadius(18.0)
Expand All @@ -43,16 +45,14 @@ struct AccountView: View {
Button {
// TODO: Add logout action
} label: {
// TODO: Use localize from KMM
Text("Logout")
Text(String.localizeId.account_logout_button())
.font(.regularLarge)
.foregroundColor(.white)
.opacity(0.5)
}
.accessibility(.account(.logoutButton))
Spacer()
// TODO: Use real data from ViewModel
Text("1.0.0")
Text(account.appVersion)
.font(.regularTiny)
.foregroundColor(.white)
.opacity(0.5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import SwiftUI

struct SurveySelectionContainerView: View {

@StateObject private var dataSource = SurveySelectionView.DataSource()

@State var isShowingAccountView = false

var body: some View {
ZStack {
SurveySelectionView($isShowingAccountView)
SurveySelectionView(
isShowingAccountView: $isShowingAccountView,
dataSource: dataSource
)

if isShowingAccountView {
Button {
Expand All @@ -29,8 +34,8 @@ struct SurveySelectionContainerView: View {

HStack {
Spacer()
if isShowingAccountView {
AccountView()
if isShowingAccountView, let account = dataSource.viewState.accountUiModel {
AccountView(account: account)
.gesture(
DragGesture(minimumDistance: 50)
.onEnded { gesture in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI

struct SurveySelectionView: View {

@StateObject private var dataSource = DataSource()
@ObservedObject private var dataSource: DataSource

@Binding var isShowingAccountView: Bool

Expand Down Expand Up @@ -84,6 +84,7 @@ struct SurveySelectionView: View {
VStack {
if let surveyHeader = dataSource.viewState.surveyHeaderUiModel {
SurveyHeaderView(surveyHeader: surveyHeader) {
guard dataSource.viewState.accountUiModel != nil else { return }
withAnimation {
isShowingAccountView.toggle()
}
Expand All @@ -108,7 +109,8 @@ struct SurveySelectionView: View {
}
}

init(_ isShowingAccountView: Binding<Bool>) {
init(isShowingAccountView: Binding<Bool>, dataSource: DataSource) {
_isShowingAccountView = isShowingAccountView
self.dataSource = dataSource
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,13 @@ protocol GetProfileUseCaseKMM: GetProfileUseCase {
@escaping (Error?, KotlinUnit) -> KotlinUnit
) -> () -> KotlinUnit
}

// sourcery: AutoMockable
protocol GetAppVersionUseCaseKMM: GetAppVersionUseCase {

func invoke() -> Kotlinx_coroutines_coreFlow
func invokeNative() -> (
@escaping (AppVersion, KotlinUnit) -> KotlinUnit,
@escaping (Error?, KotlinUnit) -> KotlinUnit
) -> () -> KotlinUnit
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class SurveySelectionViewDataSourceSpec: QuickSpec {

var getCurrentDateUseCase: GetCurrentDateUseCaseKMMMock!
var getProfileUseCase: GetProfileUseCaseKMMMock!
var getAppVersionUseCase: GetAppVersionUseCaseKMMMock!
var surveySelectionViewModel: SurveySelectionViewModel!
var dataSource: SurveySelectionView.DataSource!

Expand All @@ -26,9 +27,11 @@ final class SurveySelectionViewDataSourceSpec: QuickSpec {
beforeEach {
getCurrentDateUseCase = GetCurrentDateUseCaseKMMMock()
getProfileUseCase = GetProfileUseCaseKMMMock()
getAppVersionUseCase = GetAppVersionUseCaseKMMMock()
surveySelectionViewModel = SurveySelectionViewModel(
getCurrentDateUseCase: getCurrentDateUseCase,
getProfileUseCase: getProfileUseCase,
getAppVersionUseCase: getAppVersionUseCase,
dateTimeFormatter: DateTimeFormatterImpl()
)
dataSource = .init(
Expand All @@ -52,10 +55,12 @@ final class SurveySelectionViewDataSourceSpec: QuickSpec {
describe("its fetch") {

let user = User(name: "name", avatarUrl: "avatarUrl")
let appVersion = AppVersion(appVersion: "", buildNumber: "")

beforeEach {
getCurrentDateUseCase.invokeReturnValue = AnyFlow(result: KotlinLong(1))
getProfileUseCase.invokeReturnValue = AnyFlow(result: user)
getAppVersionUseCase.invokeReturnValue = AnyFlow(result: appVersion)
delayFetch()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.nimblehq.blisskmmic.domain.platform

actual class VersionCodeImpl: VersionCode {
// TODO: implement version for Android
actual override val versionCode = ""
actual override val buildNumber = ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package co.nimblehq.blisskmmic.data.repository

import co.nimblehq.blisskmmic.domain.platform.VersionCode
import co.nimblehq.blisskmmic.domain.repository.AppInfoRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf

class AppInfoRepositoryImpl(
private val version: VersionCode
): AppInfoRepository {

override fun getAppVersion(): Flow<String> {
return flowOf(version.versionCode)
}

override fun getAppBuildNumber(): Flow<String> {
return flowOf(version.buildNumber)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package co.nimblehq.blisskmmic.di.koin.modules

import co.nimblehq.blisskmmic.domain.platform.VersionCode
import co.nimblehq.blisskmmic.domain.platform.VersionCodeImpl
import co.nimblehq.blisskmmic.domain.platform.datetime.DateTimeFormatter
import co.nimblehq.blisskmmic.domain.platform.datetime.DateTimeFormatterImpl
import org.koin.dsl.module

val domainHelperModule = module {
single<DateTimeFormatter> { DateTimeFormatterImpl() }
single<VersionCode> { VersionCodeImpl() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package co.nimblehq.blisskmmic.di.koin.modules
import co.nimblehq.blisskmmic.data.repository.*
import co.nimblehq.blisskmmic.di.koin.constants.NETWORK_CLIENT_KOIN
import co.nimblehq.blisskmmic.di.koin.constants.TOKENIZED_NETWORK_CLIENT_KOIN
import co.nimblehq.blisskmmic.domain.platform.VersionCode
import co.nimblehq.blisskmmic.domain.platform.VersionCodeImpl
import co.nimblehq.blisskmmic.domain.repository.*
import kotlinx.datetime.Clock
import org.koin.core.qualifier.named
Expand All @@ -14,4 +16,5 @@ val repositoryModule = module {
single<SurveyRepository> { SurveyRepositoryImpl(get(named(TOKENIZED_NETWORK_CLIENT_KOIN))) }
single<DeviceInfoRepository> { DeviceInfoRepositoryImpl(Clock.System) }
single<UserRepository> { UserRepositoryImpl(get(named(TOKENIZED_NETWORK_CLIENT_KOIN))) }
single<AppInfoRepository> { AppInfoRepositoryImpl(get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ val useCaseModule = module {
single<CheckLoginUseCase> { CheckLoginUseCaseImpl(get()) }
single<GetCurrentDateUseCase> { GetCurrentDateUseCaseImpl(get()) }
single<GetProfileUseCase> { GetProfileUseCaseImpl(get()) }
single<GetAppVersionUseCase> { GetAppVersionUseCaseImpl(get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package co.nimblehq.blisskmmic.domain.model

data class AppVersion (
val appVersion: String,
val buildNumber: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.nimblehq.blisskmmic.domain.platform

interface VersionCode {
val versionCode: String
val buildNumber: String
}

expect class VersionCodeImpl(): VersionCode {
override val versionCode: String
override val buildNumber: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package co.nimblehq.blisskmmic.domain.repository

import kotlinx.coroutines.flow.Flow

interface AppInfoRepository {
fun getAppVersion(): Flow<String>
fun getAppBuildNumber(): Flow<String>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package co.nimblehq.blisskmmic.domain.usecase

import co.nimblehq.blisskmmic.domain.model.AppVersion
import co.nimblehq.blisskmmic.domain.repository.AppInfoRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.zip

interface GetAppVersionUseCase {

operator fun invoke(): Flow<AppVersion>
}

class GetAppVersionUseCaseImpl(
private val appInfoRepository: AppInfoRepository
) : GetAppVersionUseCase {

override fun invoke(): Flow<AppVersion> {
return appInfoRepository
.getAppVersion()
.zip(appInfoRepository.getAppBuildNumber()) { version, build ->
AppVersion(version, build)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.nimblehq.blisskmmic.presentation.model

data class AccountUiModel (
val imageUrl: String?,
val name: String,
val appVersion: String
)
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package co.nimblehq.blisskmmic.presentation.modules.surveyselection

import co.nimblehq.blisskmmic.MR
import co.nimblehq.blisskmmic.domain.model.AppVersion
import co.nimblehq.blisskmmic.domain.model.User
import co.nimblehq.blisskmmic.domain.platform.datetime.DateFormat
import co.nimblehq.blisskmmic.domain.platform.datetime.DateTimeFormatter
import co.nimblehq.blisskmmic.domain.usecase.GetAppVersionUseCase
import co.nimblehq.blisskmmic.domain.usecase.GetCurrentDateUseCase
import co.nimblehq.blisskmmic.domain.usecase.GetProfileUseCase
import co.nimblehq.blisskmmic.presentation.model.AccountUiModel
import co.nimblehq.blisskmmic.presentation.model.SurveyHeaderUiModel
import co.nimblehq.blisskmmic.presentation.modules.BaseViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

data class SurveySelectionViewState(
val isLoading: Boolean = true,
val surveyHeaderUiModel: SurveyHeaderUiModel? = null
val surveyHeaderUiModel: SurveyHeaderUiModel? = null,
val accountUiModel: AccountUiModel? = null
) {
constructor() : this(true)
}

class SurveySelectionViewModel(
private val getCurrentDateUseCase: GetCurrentDateUseCase,
private val getProfileUseCase: GetProfileUseCase,
private val getAppVersionUseCase: GetAppVersionUseCase,
private val dateTimeFormatter: DateTimeFormatter
) : BaseViewModel() {

Expand All @@ -34,7 +39,10 @@ class SurveySelectionViewModel(
viewModelScope.launch {
getProfile()
.combine(getDate()) { user, dateText -> Pair(user, dateText) }
.collect { updateHeaderState(it.first, it.second) }
.combine(getAppVersion()) { userDate, version ->
Triple(userDate.first, userDate.second, version)
}
.collect { updateHeaderState(it.first, it.second, it.third) }
}
}

Expand All @@ -54,6 +62,14 @@ class SurveySelectionViewModel(
}
}

private fun getAppVersion(): Flow<String> {
return flow {
getAppVersionUseCase()
.catch { emit("()") }
.collect { emit(handleVersionSuccess(it)) }
}
}

private fun setStateLoading() {
mutableViewState.update {
SurveySelectionViewState(isLoading = true)
Expand All @@ -64,12 +80,23 @@ class SurveySelectionViewModel(
return dateTimeFormatter.getFormattedString(timeInterval, DateFormat.DayOfWeekMonthDay)
}

private fun updateHeaderState(user: User?, dateText: String) {
private fun handleVersionSuccess(appVersion: AppVersion): String {
return "v${appVersion.appVersion} (${appVersion.buildNumber})"
}

private fun updateHeaderState(user: User?, dateText: String, version: String) {
val surveyHeader = SurveyHeaderUiModel(
user?.avatarUrl,
dateText,
MR.strings.common_today
)
mutableViewState.update { SurveySelectionViewState(false, surveyHeader) }
val account = AccountUiModel(
user?.avatarUrl,
user?.name ?: "",
version
)
mutableViewState.update {
SurveySelectionViewState(false, surveyHeader, account)
}
}
}
1 change: 1 addition & 0 deletions shared/src/commonMain/resources/MR/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
<string name="reset_password_button_reset">Reset</string>
<string name="reset_password_success_notification_title">Check your email.</string>
<string name="reset_password_success_notification_message">We’ve email you instructions to reset your password.</string>
<string name="account_logout_button">Logout</string>
</resources>
Loading

0 comments on commit f16de65

Please sign in to comment.