Skip to content
Open
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
56 changes: 33 additions & 23 deletions iosApp/iosApp/Sources/ui/home/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,44 @@ class HomeViewModel: ObservableObject {
viewState = .initialLoading

do {
async let userDefered = userUseCase.findForIos()
async let pointDefered = pointUseCase.findForIos()
async let historyDefered = historyUseCase.findAllForIos()
async let userDefered = userUseCase.find()
async let pointDefered = pointUseCase.find()
async let historyDefered = historyUseCase.findAll()

let (userResult, pointResult, historiesResult) = try await (userDefered, pointDefered, historyDefered)
viewState = .loaded(
user: userResult,
point: Int(pointResult.balance)
)
historyState = .loaded(histories: historiesResult)

var user: User?

switch asEnumResult(result: userResult) {
case .success(let userResult):
user = userResult
case .error(let error):
viewState = .error(message: error.message)
}

switch asEnumResult(result: pointResult) {
case .success(let pointResult):
viewState = .loaded(
user: user!,
point: Int(pointResult.balance)
)
case .error(let error):
viewState = .error(message: error.message)
}

switch asEnumResult(result: historiesResult) {
case .success(let data):
// Kotlin/Native の List<T> は Swift 側では [T] ではない場合があります。
// そのため、ここは既存動作を保ちつつ安全にキャストを試みます。
if let historiesResult = data as? [PointHistory] {
historyState = .loaded(histories: historiesResult)
}
case .error(let error):
viewState = .error(message: error.message)
}
} catch is CancellationError {
// no operation(not change UI)
return
} catch let e as AppError {
switch (e) {
case is AppError.NetworkError:
viewState = .error(message: e.message)
break
case is AppError.ProgramError:
viewState = .error(message: e.message)
break
case is AppError.UnknownError:
viewState = .error(message: e.message)
break
default:
viewState = .error(message: e.message)
break
}
} catch {
viewState = .error(message: "Unknown Error")
}
Expand Down
20 changes: 12 additions & 8 deletions iosApp/iosApp/Sources/ui/pointget/PointGetViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ class PointGetViewModel: ObservableObject {
@MainActor
func load() async {
do {
let point = try await pointUseCase.findForIos()
viewState = .success(
currentPoint: point,
inputPoint: 0,
errorMessage: nil,
isEnableConfirm: false
)
let result = try await pointUseCase.find()
switch asEnumResult(result: result) {
case .success(let point):
viewState = .success(
currentPoint: point,
inputPoint: 0,
errorMessage: nil,
isEnableConfirm: false
)
case .error(error: let error):
viewState = .error(message: error.message)
}
} catch {
viewState = .error(message: "ポイント残高の取得に失敗しました。")
}
Expand Down Expand Up @@ -80,4 +85,3 @@ enum PointAcquireEventState: Equatable {
case success
case error(message: String)
}

15 changes: 10 additions & 5 deletions iosApp/iosApp/Sources/ui/slpash/SplashViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ class SplashViewModel: ObservableObject {
func load() async {
viewState = .loading
do {
let user = try await userUseCase.findForIos()
if user.isInitialized(), let userId = user.userId {
self.viewState = .loaded(userId: userId)
} else {
self.viewState = .firstTime
let result = try await userUseCase.find()
switch asEnumResult(result: result) {
case .success(data: let user):
if user.isInitialized(), let userId = user.userId {
self.viewState = .loaded(userId: userId)
} else {
self.viewState = .firstTime
}
case .error(error: let error):
self.viewState = .error(message: error.message)
}
} catch is CancellationError {
// no operation(not change UI)
Expand Down
10 changes: 8 additions & 2 deletions iosApp/iosApp/Sources/ui/start/StartViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ class StartViewModel: ObservableObject {
viewState = .loading

do {
try await userUseCase.registerUserForIos(nickname: nickname, email: email)
viewState = .success
let result = try await userUseCase.registerUser(nickname: nickname, email: email)
switch asEnumComplete(complete: result) {
case .complete:
viewState = .success
case .error(let error):
viewState = .idle
errorAlertItem = StartErrorAlertItem(message: error.message)
}
} catch is CancellationError {
// no operation(not change UI)
return
Expand Down
56 changes: 56 additions & 0 deletions iosApp/iosApp/Sources/util/Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Extension.swift
// iosApp
//
import Foundation
import shared

// Sealed Classが使いづらいのでAppResultをEnumにする
enum AppResultEx<T> {
case success(data: T)
case error(error: AppError)
}

func asEnumResult<T>(result: AppResult<T>) -> AppResultEx<T> {
if let success = result as? AppResultSuccess<T> {
// Kotlin/Native ではジェネリクス T が Swift 側で T? として表現されることがあるため明示的にアンラップ
if let data = success.data {
return .success(data: data)
}
fatalError(#function + ": Success の data が nil です(想定外)")
}
if let failure = result as? AppResultError {
return .error(error: failure.error)
}
fatalError(#function + ": 想定外のAppResultです")
}

// Sealed Classが使いづらいのでAppCompleteをEnumにする
enum AppCompleteEx {
case complete
case error(error: AppError)
}

func asEnumComplete(complete: AppComplete) -> AppCompleteEx {
if complete is AppComplete.Complete {
return .complete
}
if let failure = complete as? AppComplete.Error {
return .error(error: failure.error)
}
fatalError(#function + ": 想定外のAppCompleteです")
}

// KMPのBooleanをSwiftのBoolに変換する拡張関数(オプショナル型)
extension Optional where Wrapped == shared.KotlinBoolean {
var asBool: Bool {
return self?.boolValue ?? false
}
}

// KMPのBooleanをSwiftのBoolに変換する拡張関数
extension shared.KotlinBoolean {
var asBool: Bool {
return self.boolValue
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package jp.hotdrop.considercline.model

import kotlin.coroutines.cancellation.CancellationException

sealed class AppError {
data class NetworkError(val error: Throwable): AppError()
data class UnknownError(val error: Throwable): AppError()
Expand All @@ -15,17 +13,4 @@ sealed class AppError {
}
}

fun mapToDomain(appError: AppError): Throwable =
when (appError) {
is AppError.NetworkError -> appError.error
is AppError.UnknownError -> appError.error
is AppError.ProgramError -> ProgramException(appError.message)
}

fun mapToThrowable(t: Throwable): AppError =
when (t) {
is CancellationException -> throw t
else -> AppError.UnknownError(t)
}

data class ProgramException(val errorMessage: String): Exception(errorMessage)
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package jp.hotdrop.considercline.usecase

import jp.hotdrop.considercline.model.AppError
import jp.hotdrop.considercline.model.AppResult
import jp.hotdrop.considercline.model.PointHistory
import jp.hotdrop.considercline.model.mapToDomain
import jp.hotdrop.considercline.repository.HistoryRepository
import kotlin.coroutines.cancellation.CancellationException

class HistoryUseCase(
private val repository: HistoryRepository
) {
suspend fun findAll(): AppResult<List<PointHistory>> {
return fetchHistories()
}

suspend fun findAllForIos(): List<PointHistory> {
return when(val result = fetchHistories()) {
is AppResult.Success -> result.data
is AppResult.Error -> throw mapToDomain(result.error)
return runCatching {
repository.findAll()
}.getOrElse { throwable ->
if (throwable is CancellationException) {
throw throwable
}
AppResult.Error(AppError.UnknownError(throwable))
}
}

private suspend fun fetchHistories(): AppResult<List<PointHistory>> {
return repository.findAll()
}
}
Original file line number Diff line number Diff line change
@@ -1,63 +1,52 @@
package jp.hotdrop.considercline.usecase

import jp.hotdrop.considercline.model.AppComplete
import jp.hotdrop.considercline.model.AppError
import jp.hotdrop.considercline.model.AppResult
import jp.hotdrop.considercline.model.Point
import jp.hotdrop.considercline.model.flatMap
import jp.hotdrop.considercline.model.mapToDomain
import jp.hotdrop.considercline.repository.HistoryRepository
import jp.hotdrop.considercline.repository.PointRepository
import kotlin.coroutines.cancellation.CancellationException

class PointUseCase(
private val pointRepository: PointRepository,
private val historyRepository: HistoryRepository
) {
suspend fun find(): AppResult<Point> {
return fetchPoint()
}

suspend fun findForIos(): Point {
return when(val result = fetchPoint()) {
is AppResult.Success -> result.data
is AppResult.Error -> throw mapToDomain(result.error)
return runCatching {
pointRepository.find()
}.getOrElse { throwable ->
if (throwable is CancellationException) {
throw throwable
}
AppResult.Error(AppError.UnknownError(throwable))
}
}

private suspend fun fetchPoint(): AppResult<Point> {
return pointRepository.find()
}

suspend fun acquire(inputPoint: Int): AppComplete {
return acquireToRepo(inputPoint)
}

suspend fun acquireForIos(inputPoint: Int) {
return when(val result = acquireToRepo(inputPoint)) {
is AppComplete.Complete -> Unit
is AppComplete.Error -> throw mapToDomain(result.error)
}
}

private suspend fun acquireToRepo(inputPoint: Int): AppComplete {
return pointRepository.acquire(inputPoint).flatMap {
historyRepository.saveAcquire(inputPoint)
return runCatching {
pointRepository.acquire(inputPoint).flatMap {
historyRepository.saveAcquire(inputPoint)
}
}.getOrElse { throwable ->
if (throwable is CancellationException) {
throw throwable
}
AppComplete.Error(AppError.UnknownError(throwable))
}
}

suspend fun use(inputPoint: Int): AppComplete {
return useToRepo(inputPoint)
}

suspend fun useForIos(inputPoint: Int) {
return when(val result = useToRepo(inputPoint)) {
is AppComplete.Complete -> Unit
is AppComplete.Error -> throw mapToDomain(result.error)
}
}

private suspend fun useToRepo(inputPoint: Int): AppComplete {
return pointRepository.use(inputPoint).flatMap {
historyRepository.saveUse(inputPoint)
return runCatching {
pointRepository.use(inputPoint).flatMap {
historyRepository.saveUse(inputPoint)
}
}.getOrElse { throwable ->
if (throwable is CancellationException) {
throw throwable
}
AppComplete.Error(AppError.UnknownError(throwable))
}
}
}
Loading