Skip to content

Commit

Permalink
Merge branch 'release/1.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
woin2ee committed Feb 15, 2024
2 parents 37cd1e9 + 6e14106 commit 2832f46
Show file tree
Hide file tree
Showing 106 changed files with 1,404 additions and 691 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,6 @@ Resources/InfoPlist/Info.plist

### Using tests
Tests/**/*.plist

### Changelog
Changelog/next.md
8 changes: 8 additions & 0 deletions Changelog/1.7.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## New features
- Added to change theme feature.

## Enhancements
- Prevent adding duplecate word.

## Fixed
- Fixed memory leak for view controller.
3 changes: 0 additions & 3 deletions Changelog/next.md

This file was deleted.

178 changes: 55 additions & 123 deletions Project.swift

Large diffs are not rendered by default.

21 changes: 16 additions & 5 deletions QA.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
#Version
1.5.0
1.7.0

##Common
- Localization 적용 확인

##WordCheking
## WordCheking

- 단어 추가 시 리스트에 반영
- 단어 암기 완료 시 리스트에 반영
- 단어 삭제 시 리스트에 반영
- 단어 앞, 뒤 이동 정상 작동
- 마지막 단어 암기 완료 표시 시 '단어 없음' 문구 출력
- 마지막 단어 삭제 시 '단어 없음' 문구 출력
- 중복 단어 추가 시 중복 단어 Toast 메세지 표시 확인

##WordList
## WordList

- 현재 단어 수정 시 암기 화면에 반영
- 현재 단어 삭제 시 암기 화면에 반영
- 현재 단어 암기 완료 표시 시 암기 화면에 반영
- 현재 단어 없을 때 단어 암기중 표시 시 화면에 반영

##WordSearch
## WordAddition

- 단어 추가 시 중복 단어일 때 중복 경고 메세지 표시 확인
- 단어 추가 시 입력된 단어가 없을 때 완료 버튼 비활성화 확인

## WordDetail

- 단어 편집 시 중복 단어일 때 중복 경고 메세지 표시 확인
- 수정 사항이 존재할 때 취소 확인 ActionSheet 표시 확인

## WordSearch

- 단어 검색 이상 여부 확인
- 단어 검색 후 수정 시 리스트&암기 화면에 바로 적용 여부 확인

##Settings
## Settings

- Source language / Translation language 변경시 번역 사이트에 정상 적용 확인
- 구글 드라이브 로그인 여부에 따라 로그아웃 버튼 표시
Expand Down
12 changes: 11 additions & 1 deletion Resources/iOSSupport/Localization/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ all = "All";
there_are_no_words = "There are no words.";
quick_add_word = "Quick add word";
"%@_added_successfully" = "[ %@ ] added successfully";
already_added_word = "Already added word.";
"%@_added_failed" = "[ %@ ] added failed.";

settings = "Settings";
translation_language = "Translation language";

Expand Down Expand Up @@ -66,9 +69,16 @@ dailyReminderFooter = "Sends a daily push notification at the time you set.";
general = "General";
haptics = "Haptics";
hapticsSettingsFooterTextWhenHapticsIsOn = "Enable haptics for interactions.";
hapticsSettingsFooterTextWhenHapticsIsOff = "Disable haptics for interactions";
hapticsSettingsFooterTextWhenHapticsIsOff = "Disable haptics for interactions.";

theme = "Theme";
system_mode = "System mode";
light_mode = "Light mode";
dark_mode = "Dark mode";

more_menu = "More menu";
memorize_words = "Memorize words";
next_word = "Next word";
previous_word = "Previous word";

duplicate_word = "Duplicate word.";
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ all = "전체";
there_are_no_words = "단어가 없습니다.";
quick_add_word = "빠른 단어 추가";
"%@_added_successfully" = "[ %@ ] 추가 성공";
already_added_word = "이미 추가된 단어입니다.";
"%@_added_failed" = "[ %@ ] 단어 추가 실패";

settings = "설정";
languages = "언어";
Expand Down Expand Up @@ -68,7 +70,14 @@ haptics = "진동";
hapticsSettingsFooterTextWhenHapticsIsOn = "상호 작용에 대한 진동을 사용합니다.";
hapticsSettingsFooterTextWhenHapticsIsOff = "상호 작용에 대한 진동을 사용하지 않습니다.";

theme = "테마";
system_mode = "시스템 설정 모드";
light_mode = "라이트 모드";
dark_mode = "다크 모드";

more_menu = "메뉴 더보기";
memorize_words = "단어 암기";
next_word = "다음 단어";
previous_word = "이전 단어";

duplicate_word = "중복 단어입니다.";
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ public protocol LocalNotificationService {

/// 매일 알림을 설정한 마지막 시각을 반환합니다.
///
/// /// - throws: 저장된 시각이 없을 때 Error 를 던집니다.
/// - throws: 저장된 시각이 없을 때 Error 를 던집니다.
func getLatestDailyReminderTime() throws -> DateComponents
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// ExternalStoreUseCaseError.swift
// Domain
//
// Created by Jaewon Yun on 2/12/24.
// Copyright © 2024 woin2ee. All rights reserved.
//

import Foundation

public enum ExternalStoreUseCaseError: Error {

case noCurrentUser

case noPresentingConfiguration

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// NotificationsUseCaseError.swift
// Domain
//
// Created by Jaewon Yun on 2/12/24.
// Copyright © 2024 woin2ee. All rights reserved.
//

import Foundation

public enum NotificationsUseCaseError: Error {
case noWordsToMemorize
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// UserSettingsUseCaseError.swift
// Domain
//
// Created by Jaewon Yun on 2/12/24.
// Copyright © 2024 woin2ee. All rights reserved.
//

import Foundation

public enum UserSettingsUseCaseError: Error {
case noPendingDailyReminder
case noNotificationAuthorization
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ public protocol UserSettingsUseCaseProtocol {

func offHaptics() -> Single<Void>

func updateThemeStyle(_ style: ThemeStyle) -> Single<Void>

}
41 changes: 41 additions & 0 deletions Sources/Domain/Interfaces/UseCases/Word/WordUseCaseError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// WordUseCaseError.swift
// Domain
//
// Created by Jaewon Yun on 2/12/24.
// Copyright © 2024 woin2ee. All rights reserved.
//

import Foundation

public enum WordUseCaseError: Error {

/// `saveFailed` 에러가 발생한 이유입니다.
public enum SaveFailureReason {

/// 저장하려는 단어가 이미 암기 완료 상태입니다.
case wordStateInvalid

/// 저장하려는 단어가 중복 단어입니다.
case duplicatedWord(word: String)

}

/// `retrieveFailed` 에러가 발생한 이유입니다.
public enum RetrieveFailureReason {

/// 해당 UUID 와 일치하는 단어가 없습니다.
case uuidInvaild(uuid: UUID)

}

/// 단어 저장 실패
case saveFailed(reason: SaveFailureReason)

/// 단어 검색 실패
case retrieveFailed(reason: RetrieveFailureReason)

/// 현재 암기중인 단어가 없음
case noMemorizingWords

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import RxSwift
public protocol WordUseCaseProtocol {

/// 새 단어를 추가합니다.
///
/// - Returns: 단어 추가에 성공하면 Next 이벤트를, 어떠한 이유로 인해 실패하면 `WordUseCaseError` 타입의 Error 이벤트를 방출하는 Sequence 를 반환합니다.
func addNewWord(_ word: Word) -> Single<Void>

/// 단어를 삭제합니다.
Expand Down Expand Up @@ -42,4 +44,9 @@ public protocol WordUseCaseProtocol {

func getCurrentUnmemorizedWord() -> Single<Word>

/// `word` 파라미터로 전달된 단어가 이미 저장되어 있는 단어인지 검사합니다.
///
/// - Returns: 반환된 Sequence 는 `ture` or `false` 값을 가진 next 이벤트만 방출됩니다. error 이벤트는 방출되지 않습니다.
func isWordDuplicated(_ word: String) -> Single<Bool>

}
8 changes: 0 additions & 8 deletions Sources/Domain/UseCases/ExternalStoreUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,3 @@ public final class ExternalStoreUseCase: ExternalStoreUseCaseProtocol {
}

}

enum ExternalStoreUseCaseError: Error {

case noCurrentUser

case noPresentingConfiguration

}
13 changes: 4 additions & 9 deletions Sources/Domain/UseCases/NotificationsUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@
//

import Foundation
import FoundationPlus
import RxSwift
import RxUtility
import Then
import UserNotifications

enum NotificationsUseCaseError: Error {
case noWordsToMemorize
}

final class NotificationsUseCase: NotificationsUseCaseProtocol {

/// Notification request 의 고유 ID
Expand Down Expand Up @@ -59,10 +55,9 @@ final class NotificationsUseCase: NotificationsUseCaseProtocol {
let setDailyReminderSequence: Single<Void> = .create { observer in
let unmemorizedWordCount = self.wordRepository.getUnmemorizedList().count

var content: UNMutableNotificationContent = .init().then {
$0.title = DomainString.daily_reminder
$0.sound = .default
}
var content: UNMutableNotificationContent = .init()
content.title = DomainString.daily_reminder
content.sound = .default

if unmemorizedWordCount == 0 {
content.body = DomainString.daily_reminder_body_message_when_no_words_to_memorize
Expand Down
19 changes: 12 additions & 7 deletions Sources/Domain/UseCases/UserSettingsUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ import RxRelay
import RxUtility
import Utility

enum UserSettingsUseCaseError: Error {
case noPendingDailyReminder
case noNotificationAuthorization
}

public final class UserSettingsUseCase: UserSettingsUseCaseProtocol {

let userSettingsRepository: UserSettingsRepositoryProtocol
Expand Down Expand Up @@ -100,10 +95,20 @@ public final class UserSettingsUseCase: UserSettingsUseCaseProtocol {
translationTargetLocale = .english
}

let userSettings: UserSettings = .init(translationSourceLocale: .english, translationTargetLocale: translationTargetLocale, hapticsIsOn: true) // FIXME: 처음에 Source Locale 설정 가능하게 (현재 .english 고정)
let initialUserSettings: UserSettings = .init(translationSourceLocale: .english, translationTargetLocale: translationTargetLocale, hapticsIsOn: true, themeStyle: .system) // FIXME: 처음에 Source Locale 설정 가능하게 (현재 .english 고정)

return self.userSettingsRepository.saveUserSettings(initialUserSettings)
}
}

return self.userSettingsRepository.saveUserSettings(userSettings)
public func updateThemeStyle(_ style: ThemeStyle) -> Single<Void> {
return userSettingsRepository.getUserSettings()
.map { currentSettings in
var newSettings = currentSettings
newSettings.themeStyle = style
return newSettings
}
.flatMap { self.userSettingsRepository.saveUserSettings($0) }
}

}
41 changes: 26 additions & 15 deletions Sources/Domain/UseCases/WordUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ public final class WordUseCase: WordUseCaseProtocol {
public func addNewWord(_ word: Word) -> RxSwift.Single<Void> {
return .create { single in
guard word.memorizedState != .memorized else {
single(.failure(WordUseCaseError.canNotSaveWord(reason: "Can only add word with a memorization state of `.memorizing`.")))
single(.failure(WordUseCaseError.saveFailed(reason: .wordStateInvalid)))
return Disposables.create()
}

let allWords = self.wordRepository.getAllWords()
if allWords.contains(where: { $0.word.lowercased() == word.word.lowercased() }) {
single(.failure(WordUseCaseError.saveFailed(reason: .duplicatedWord(word: word.word))))
return Disposables.create()
}

Expand Down Expand Up @@ -85,14 +91,23 @@ public final class WordUseCase: WordUseCaseProtocol {
if let word = self.wordRepository.getWord(by: uuid) {
single(.success(word))
} else {
single(.failure(WordUseCaseError.invalidUUID(uuid)))
single(.failure(WordUseCaseError.retrieveFailed(reason: .uuidInvaild(uuid: uuid))))
}

return Disposables.create()
}
}

public func updateWord(by uuid: UUID, to newWord: Word) -> RxSwift.Single<Void> {
guard let originWord = wordRepository.getWord(by: uuid) else {
return .error(WordUseCaseError.retrieveFailed(reason: .uuidInvaild(uuid: uuid)))
}

let allWords = self.wordRepository.getAllWords()
if (originWord.word != newWord.word) && allWords.contains(where: { $0.word.lowercased() == newWord.word.lowercased() }) {
return .error(WordUseCaseError.saveFailed(reason: .duplicatedWord(word: newWord.word)))
}

return .create { single in
let updateTarget: Word = .init(
uuid: uuid,
Expand Down Expand Up @@ -151,7 +166,7 @@ public final class WordUseCase: WordUseCaseProtocol {
public func markCurrentWordAsMemorized(uuid: UUID) -> RxSwift.Single<Void> {
return .create { single in
guard let currentWord = self.wordRepository.getWord(by: uuid) else {
single(.failure(WordUseCaseError.invalidUUID(uuid)))
single(.failure(WordUseCaseError.retrieveFailed(reason: .uuidInvaild(uuid: uuid))))
return Disposables.create()
}

Expand All @@ -174,17 +189,13 @@ public final class WordUseCase: WordUseCaseProtocol {
return .just(currentWord)
}

}

enum WordUseCaseError: Error {

/// 해당되는 단어가 없는 UUID
case invalidUUID(UUID)

/// 단어를 저장할 수 없음
case canNotSaveWord(reason: String)

/// 현재 암기중인 단어가 없음
case noMemorizingWords
public func isWordDuplicated(_ word: String) -> Single<Bool> {
let allWords = self.wordRepository.getAllWords()
if allWords.contains(where: { $0.word.lowercased() == word.lowercased() }) {
return .just(true)
} else {
return .just(false)
}
}

}
Loading

0 comments on commit 2832f46

Please sign in to comment.