Skip to content

Commit 9c38bf7

Browse files
committed
Merge branch 'release/v1.2.0'
2 parents d12f73f + e6f110b commit 9c38bf7

File tree

16 files changed

+330
-202
lines changed

16 files changed

+330
-202
lines changed

.github/workflows/create_new_release.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ on:
55
# branches:
66
# - main
77
tags:
8-
- 'v*'
8+
- 'v*.*.*'
99

1010
jobs:
1111
create_new_release:
1212
name: Create new release
1313
runs-on: ubuntu-latest
14+
env:
15+
CURRENT_TAG: ${{ github.ref_name }}
1416
permissions:
1517
contents: write
1618
steps:
1719
- uses: actions/checkout@v3
1820
- name: Create Release
1921
uses: ncipollo/[email protected]
22+
with:
23+
bodyFile: "Changelog/$CURRENT_TAG.md"

Changelog/v1.2.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## Features
2+
- Add activity indicator to translation web view controller.
3+
- Add feature to disable translation button when no word.
4+
5+
## Enhancement
6+
- Add exception processing that the device unconnected network.
7+
- Change text color of `no Word` label

Resources/iOSCore/Localization/en.lproj/Localizable.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,5 @@ google_drive_logout = "Google Drive Logout";
5454
signed_out_of_google_drive = "Signed out of Google Drive.";
5555

5656
synchronize_to_google_drive = "Synchronize to Google Drive.";
57+
58+
please_check_your_network_connection = "Please check your network connection.";

Resources/iOSCore/Localization/ko.lproj/Localizable.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,5 @@ google_drive_logout = "구글 드라이브 로그아웃";
5454
signed_out_of_google_drive = "구글 드라이브에서 로그아웃되었습니다.";
5555

5656
synchronize_to_google_drive = "구글 드라이브에 동기화 합니다.";
57+
58+
please_check_your_network_connection = "네트워크 연결 상태를 확인해 주세요.";

Sources/Domain/UseCases/WordRxUseCase.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import Foundation
99
import RxSwift
10-
import Utility
1110

1211
public final class WordRxUseCase: WordRxUseCaseProtocol {
1312

@@ -69,13 +68,10 @@ public final class WordRxUseCase: WordRxUseCaseProtocol {
6968

7069
public func getWord(by uuid: UUID) -> RxSwift.Single<Word> {
7170
return .create { single in
72-
let maybeWord = self.wordUseCase.getWord(by: uuid)
73-
74-
do {
75-
let word = try unwrapOrThrow(maybeWord)
71+
if let word = self.wordUseCase.getWord(by: uuid) {
7672
single(.success(word))
77-
} catch {
78-
single(.failure(error))
73+
} else {
74+
single(.failure(WordRxUseCaseError.invalidUUID(uuid)))
7975
}
8076

8177
return Disposables.create()
@@ -137,3 +133,7 @@ public final class WordRxUseCase: WordRxUseCaseProtocol {
137133
}
138134

139135
}
136+
137+
enum WordRxUseCaseError: Error {
138+
case invalidUUID(UUID)
139+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// WordRxUseCaseFake.swift
3+
// iOSCore
4+
//
5+
// Created by Jaewon Yun on 2023/11/12.
6+
// Copyright © 2023 woin2ee. All rights reserved.
7+
//
8+
9+
@testable import Domain
10+
import Foundation
11+
import RxSwift
12+
13+
public final class WordRxUseCaseFake: WordRxUseCaseProtocol {
14+
15+
let wordUseCaseFake: WordUseCaseFake = .init()
16+
17+
public init() {
18+
}
19+
20+
public func addNewWord(_ word: Domain.Word) -> RxSwift.Single<Void> {
21+
wordUseCaseFake.addNewWord(word)
22+
return .just(())
23+
}
24+
25+
public func deleteWord(by uuid: UUID) -> RxSwift.Single<Void> {
26+
wordUseCaseFake.deleteWord(by: uuid)
27+
return .just(())
28+
}
29+
30+
public func getWordList() -> RxSwift.Single<[Domain.Word]> {
31+
let wordList = wordUseCaseFake.getWordList()
32+
return .just(wordList)
33+
}
34+
35+
public func getMemorizedWordList() -> RxSwift.Single<[Domain.Word]> {
36+
let wordList = wordUseCaseFake.getMemorizedWordList()
37+
return .just(wordList)
38+
}
39+
40+
public func getUnmemorizedWordList() -> RxSwift.Single<[Domain.Word]> {
41+
let wordList = wordUseCaseFake.getUnmemorizedWordList()
42+
return .just(wordList)
43+
}
44+
45+
public func getWord(by uuid: UUID) -> RxSwift.Single<Domain.Word> {
46+
guard let word = wordUseCaseFake.getWord(by: uuid) else {
47+
return .error(WordRxUseCaseError.invalidUUID(uuid))
48+
}
49+
return .just(word)
50+
}
51+
52+
public func updateWord(by uuid: UUID, to newWord: Domain.Word) -> RxSwift.Single<Void> {
53+
wordUseCaseFake.updateWord(by: uuid, to: newWord)
54+
return .just(())
55+
}
56+
57+
public func randomizeUnmemorizedWordList() -> RxSwift.Single<Void> {
58+
wordUseCaseFake.randomizeUnmemorizedWordList()
59+
return .just(())
60+
}
61+
62+
public func updateToNextWord() -> RxSwift.Single<Void> {
63+
wordUseCaseFake.updateToNextWord()
64+
return .just(())
65+
}
66+
67+
public func updateToPreviousWord() -> RxSwift.Single<Void> {
68+
wordUseCaseFake.updateToPreviousWord()
69+
return .just(())
70+
}
71+
72+
public func markCurrentWordAsMemorized(uuid: UUID) -> RxSwift.Single<Void> {
73+
wordUseCaseFake.markCurrentWordAsMemorized(uuid: uuid)
74+
return .just(())
75+
}
76+
77+
public var currentUnmemorizedWord: Domain.Word? {
78+
wordUseCaseFake.currentUnmemorizedWord
79+
}
80+
81+
}

Sources/Utility/NetworkMonitor.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// NetworkMonitor.swift
3+
// Utility
4+
//
5+
// Created by Jaewon Yun on 2023/11/13.
6+
// Copyright © 2023 woin2ee. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Network
11+
12+
/// 인터넷 연결 상태를 모니터링하는 객체입니다.
13+
///
14+
/// 모니터링을 시작하기 위해서 `start()` 함수를 호출해야 합니다.
15+
public final class NetworkMonitor {
16+
17+
public static let shared: NetworkMonitor = .init()
18+
19+
let queue: DispatchQueue = .global()
20+
let monitor: NWPathMonitor = .init()
21+
22+
public private(set) var isEstablishedConnection: Bool = false
23+
24+
private init() {
25+
monitor.pathUpdateHandler = { [weak self] path in
26+
if path.status == .satisfied {
27+
self?.isEstablishedConnection = true
28+
} else {
29+
self?.isEstablishedConnection = false
30+
}
31+
}
32+
33+
monitor.start(queue: self.queue)
34+
}
35+
36+
public static func start() {
37+
_ = NetworkMonitor.shared
38+
}
39+
40+
}

Sources/WordChecker/Application/AppDelegate.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import iOSCore
1212
import RxSwift
1313
import RxUtility
1414
import UIKit
15+
import Utility
1516

1617
@main
1718
class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -20,6 +21,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
2021
injectDependencies()
2122
initUserSettingsIfFirstLaunch()
2223
attemptRestoreGoogleSignInState()
24+
NetworkMonitor.start()
25+
2326
return true
2427
}
2528

Sources/WordCheckerDev/Application/AppDelegate.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import iOSCore
1111
import RxSwift
1212
import RxUtility
1313
import UIKit
14+
import Utility
1415

1516
@main
1617
class AppDelegate: UIResponder, UIApplicationDelegate {
1718

1819
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
1920
injectDependencies()
2021
initUserSettingsIfFirstLaunch()
22+
NetworkMonitor.start()
2123

2224
return true
2325
}

Sources/iOSCore/Localization/WCString.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,6 @@ struct WCString {
6464
return .init(format: localizedString, arguments: [word])
6565
}
6666

67+
static let please_check_your_network_connection = NSLocalizedString("please_check_your_network_connection", bundle: Bundle.module, comment: "")
68+
6769
}

Sources/iOSCore/Scenes/Common/ActivityIndicatorViewController.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ import SnapKit
1010
import Then
1111
import UIKit
1212

13+
/// 화면 전체에 dim view 와 함께 `ActivityIndicator` 를 띄우고 싶을때 사용하는 ViewController 입니다.
14+
///
15+
/// `startAnimating(on:)` 함수를 이용하여 원하는 ViewController 에 `ActivityIndicator` 를 띄울 수 있습니다.
16+
/// 적절한 시점에 반드시 `stopAnimating(on:)` 함수를 호출하여 `ActivityIndicator` 를 화면에서 제거해야 합니다.
1317
final class ActivityIndicatorViewController: UIViewController {
1418

1519
static let shared: ActivityIndicatorViewController = .init()
1620

1721
let activityIndicatorView: UIActivityIndicatorView = .init(style: .large)
1822

19-
private init() {
23+
init() {
2024
super.init(nibName: nil, bundle: nil)
2125

2226
self.modalPresentationStyle = .overFullScreen

Sources/iOSCore/Scenes/WordChecking/Subviews/WordChecking+BottomButton.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ extension WordCheckingView {
3232
return config
3333
}
3434

35+
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
36+
// 하위뷰의 여부, hidden, alpha 값 등 hitTest 에 영향을 미치는 요소들을 무시합니다.
37+
if self.point(inside: point, with: event) {
38+
return self
39+
} else {
40+
return nil
41+
}
42+
}
43+
3544
}
3645

3746
}

Sources/iOSCore/Scenes/WordChecking/TranslationWebView/TranslationWebViewController.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,22 @@
77
//
88

99
import Domain
10+
import Then
1011
import UIKit
1112
import WebKit
1213

13-
final class TranslationWebViewController: UIViewController {
14+
final class TranslationWebViewController: BaseViewController {
1415

15-
let webView: WKWebView = .init()
16+
lazy var webView: WKWebView = .init().then {
17+
$0.navigationDelegate = self
18+
}
1619

1720
var translationSite: TranslationSite
1821

1922
var word: String = ""
2023

24+
private var activityIndicatorView: ActivityIndicatorViewController? = .init()
25+
2126
init(translationSite: TranslationSite) {
2227
self.translationSite = translationSite
2328
super.init(nibName: nil, bundle: nil)
@@ -31,6 +36,12 @@ final class TranslationWebViewController: UIViewController {
3136
self.view = webView
3237
}
3338

39+
override func viewWillDisappear(_ animated: Bool) {
40+
super.viewWillDisappear(animated)
41+
42+
activityIndicatorView = nil
43+
}
44+
3445
func loadWebView() throws {
3546
let url: String = translationSite.url(forWord: word)
3647

@@ -56,3 +67,17 @@ extension TranslationWebViewController {
5667
}
5768

5869
}
70+
71+
// MARK: - WKNavigationDelegate
72+
73+
extension TranslationWebViewController: WKNavigationDelegate {
74+
75+
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
76+
activityIndicatorView?.startAnimating(on: self)
77+
}
78+
79+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
80+
activityIndicatorView?.stopAnimating(on: self)
81+
}
82+
83+
}

Sources/iOSCore/Scenes/WordChecking/WordCheckingViewController.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import SFSafeSymbols
1313
import Then
1414
import Toast
1515
import UIKit
16+
import Utility
1617
import WebKit
1718

1819
final class WordCheckingViewController: RxBaseViewController, View {
@@ -102,13 +103,23 @@ final class WordCheckingViewController: RxBaseViewController, View {
102103

103104
rootView.translateButton.rx.tap
104105
.subscribe(with: self, onNext: { owner, _ in
106+
guard NetworkMonitor.shared.isEstablishedConnection else {
107+
owner.presentOKAlert(title: nil, message: WCString.please_check_your_network_connection)
108+
return
109+
}
110+
111+
guard let word = reactor.currentState.currentWord?.word else {
112+
assertionFailure("현재 표시된 단어가 없는데 번역 버튼이 활성화된 것으로 예상.")
113+
return
114+
}
115+
105116
let translationSite: TranslationSite = .init(
106117
translationSourceLanguage: reactor.currentState.translationSourceLanguage,
107118
translationTargetLanguage: reactor.currentState.translationTargetLanguage
108119
)
109120

110121
let translationWebViewController: TranslationWebViewController = .init(translationSite: translationSite)
111-
translationWebViewController.word = owner.rootView.wordLabel.text ?? ""
122+
translationWebViewController.word = word
112123

113124
do {
114125
try translationWebViewController.loadWebView()
@@ -127,8 +138,12 @@ final class WordCheckingViewController: RxBaseViewController, View {
127138
.drive(with: self) { owner, word in
128139
if let currentWord = word {
129140
owner.rootView.wordLabel.text = currentWord.word
141+
owner.rootView.wordLabel.textColor = .label
142+
owner.rootView.translateButton.isEnabled = true
130143
} else {
131144
owner.rootView.wordLabel.text = WCString.noWords
145+
owner.rootView.wordLabel.textColor = .systemGray2
146+
owner.rootView.translateButton.isEnabled = false
132147
}
133148
}
134149
.disposed(by: self.disposeBag)

0 commit comments

Comments
 (0)