Skip to content

Commit

Permalink
[iOS#184] 카테고리 선택 후 타이머 작동시 로직 구현 (#186)
Browse files Browse the repository at this point in the history
* feat: 카테고리 Cell 선택 및 해제시 그림자 UI 구현

* refactor: Timer API UserID 제외

* feat: 카테고리 선택 후 타이머 작동 시 로직 구현
  • Loading branch information
leemhyungyu authored Nov 23, 2023
1 parent 574595b commit 22cee70
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 47 deletions.
4 changes: 2 additions & 2 deletions iOS/FlipMate/FlipMate/Data/Network/BaseComponents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import Foundation

enum BaseURL {
static let flipmateDomain = "https://flipmate.site:3333"
static let developDomain = "https://flipmate.site:3333"
static let flipmateDomain = "https://flipmate.site:3000"
static let developDomain = "https://flipmate.site:3000"
}

enum Paths {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ struct TimerFinishRequestDTO: Encodable {
let createdAt: String
let type: String
let learningTime: Int
let userID: Int
let categoryID: Int
let categoryID: Int?

private enum CodingKeys: String, CodingKey {
case date
case createdAt = "created_at"
case type
case learningTime = "learning_time"
case userID = "user_id"
case categoryID = "category_id"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ struct TimerFinishResponseDTO: Decodable {
let createdAt: String
let type: String
let learningTime: Int
let userID: Int
let categoryID: Int
let categoryID: Int?

private enum CodingKeys: String, CodingKey {
case date
case createdAt = "created_at"
case type
case learningTime = "learning_time"
case userID = "user_id"
case categoryID = "category_id"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ struct TimerStartRequestDTO: Encodable {
let createdAt: String
let type: String
let learningTime: Int
let userID: Int
let categoryID: Int
let categoryID: Int?

private enum CodingKeys: String, CodingKey {
case date
case createdAt = "created_at"
case type
case learningTime = "learning_time"
case userID = "user_id"
case categoryID = "category_id"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ struct TimerStartResponseDTO: Decodable {
let createdAt: String
let type: String
let learningTime: Int
let userID: Int
let categoryID: Int
let categoryID: Int?

private enum CodingKeys: String, CodingKey {
case date
case createdAt = "created_at"
case type
case learningTime = "learning_time"
case userID = "user_id"
case categoryID = "category_id"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@ final class DefaultTimerRepository {
extension DefaultTimerRepository: TimerRepsoitory {
func startTimer(
startTime: Date,
userId: Int,
categoryId: Int)
categoryId: Int?)
-> AnyPublisher<Void, NetworkError> {
let requestDTO = TimerStartRequestDTO(
date: startTime.dateToString(format: .yyyyMMdd),
createdAt: startTime.dateToString(format: .yyyyMMddhhmmss),
type: StudyType.start.rawValue,
learningTime: 0,
userID: userId,
categoryID: categoryId)
let endpoint = TimerEndpoints.startTimer(with: requestDTO)

Expand All @@ -46,15 +44,13 @@ extension DefaultTimerRepository: TimerRepsoitory {
func finishTimer(
endTime: Date,
learningTime: Int,
userId: Int,
categoryId: Int)
categoryId: Int?)
-> AnyPublisher<Void, NetworkError> {
let requestDTO = TimerFinishRequestDTO(
date: endTime.dateToString(format: .yyyyMMdd),
createdAt: endTime.dateToString(format: .yyyyMMddhhmmss),
type: StudyType.finish.rawValue,
learningTime: learningTime,
userID: userId,
categoryID: categoryId)
let endpoint = TimerEndpoints.stopTimer(with: requestDTO)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ import Combine
protocol TimerRepsoitory {
func startTimer(
startTime: Date,
userId: Int,
categoryId: Int
categoryId: Int?
) -> AnyPublisher<Void, NetworkError>

func finishTimer(
endTime: Date,
learningTime: Int,
userId: Int,
categoryId: Int
categoryId: Int?
) -> AnyPublisher<Void, NetworkError>
}
12 changes: 6 additions & 6 deletions iOS/FlipMate/FlipMate/Domain/UseCases/DefaultTimerUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ class DefaultTimerUseCase: TimerUseCase {
self.timerRepository = timerRepository
}

func startTimer(startTime: Date, userId: Int, categoryId: Int) -> AnyPublisher<Void, NetworkError> {
func startTimer(startTime: Date, categoryId: Int?) -> AnyPublisher<Void, NetworkError> {
timerManager.start(startTime: startTime)
return timerRepository.startTimer(startTime: startTime, userId: userId, categoryId: categoryId)
return timerRepository.startTimer(startTime: startTime, categoryId: categoryId)
}

func resumeTimer(resumeTime: Date, userId: Int, categoryId: Int) -> AnyPublisher<Void, NetworkError> {
func resumeTimer(resumeTime: Date, categoryId: Int?) -> AnyPublisher<Void, NetworkError> {
timerManager.resume(resumeTime: resumeTime)
return timerRepository.startTimer(startTime: resumeTime, userId: userId, categoryId: categoryId)
return timerRepository.startTimer(startTime: resumeTime, categoryId: categoryId)
}

func suspendTimer(suspendTime: Date, userId: Int, categoryId: Int) -> AnyPublisher<Int, NetworkError> {
func suspendTimer(suspendTime: Date, categoryId: Int?) -> AnyPublisher<Int, NetworkError> {
let totalTime = timerManager.totalTime
timerManager.suspend()
return timerRepository.finishTimer(endTime: suspendTime, learningTime: totalTime, userId: userId, categoryId: categoryId)
return timerRepository.finishTimer(endTime: suspendTime, learningTime: totalTime, categoryId: categoryId)
.map { response -> Int in
return totalTime
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import Combine
/// 타이머 Usecase을 추상화한 프로토콜
protocol TimerUseCase {
/// 타이머 작동
func startTimer(startTime: Date, userId: Int, categoryId: Int) -> AnyPublisher<Void, NetworkError>
func startTimer(startTime: Date, categoryId: Int?) -> AnyPublisher<Void, NetworkError>
/// 타이머 재개
func resumeTimer(resumeTime: Date, userId: Int, categoryId: Int) -> AnyPublisher<Void, NetworkError>
func resumeTimer(resumeTime: Date, categoryId: Int?) -> AnyPublisher<Void, NetworkError>
/// 타이머 일시정지
func suspendTimer(suspendTime: Date, userId: Int, categoryId: Int) -> AnyPublisher<Int, NetworkError>
func suspendTimer(suspendTime: Date, categoryId: Int?) -> AnyPublisher<Int, NetworkError>
/// 타이머 종료
func stopTimer()
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@

import UIKit

let token = ""
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImltaHllb25neXUyQGdtYWlsLmNvbSIsIm5pY2tuYW1lIjoiaW1oeWVvbmd5dTJhVzFvZVdWdmJtZDVkVEpBWjIxaGFXd3VZMjl0ZFc1a1pXWnBibVZrIiwidHlwZSI6ImFjY2VzcyIsImF1dGhfdHlwZSI6Imdvb2dsZSIsImlhdCI6MTcwMDc0MzIzMywiZXhwIjoxNzAzMzM1MjMzfQ.t00zwpUfUoKgxBQ0iK6ThMompboXA0hyZCIjAPIpTHU"

enum CategorySettingSection: Hashable {
case categorySection([CategorySettingItem])
}

enum CategorySettingItem: Hashable {
case categoryCell(Category)

var category: Category {
switch self {
case .categoryCell(let category):
return category
}
}
}

extension CategorySettingSection {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ final class CategoryListCollectionViewCell: UICollectionViewCell {
subjectLabel.text = category.subject
timeLabel.text = category.studyTime?.secondsToStringTime()
}

func updateShadow() {
if isSelected {
backgroundColor = FlipMateColor.gray3.color
setShadow()
} else {
backgroundColor = .white
layer.shadowOpacity = 0
}
}
}

private extension CategoryListCollectionViewCell {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ final class TimerViewController: BaseViewController {
}
.store(in: &cancellables)

timerViewModel.categoryPublisher
timerViewModel.categoriesPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] categories in
guard let self = self else { return }
Expand All @@ -183,6 +183,19 @@ final class TimerViewController: BaseViewController {
self.dataSource?.apply(snapShot)
}
.store(in: &cancellables)

timerViewModel.categoryChangePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] categories in
guard let self = self else { return }
guard var snapShot = self.dataSource?.snapshot() else { return }
let sections: [CategorySettingSection] = [.categorySection([])]
snapShot.deleteAllItems()
snapShot.appendSections(sections)
snapShot.appendItems(categories.map { CategorySettingItem.categoryCell($0) })
self.dataSource?.apply(snapShot)
}
.store(in: &cancellables)
}
}

Expand Down Expand Up @@ -271,6 +284,20 @@ extension TimerViewController: UICollectionViewDelegateFlowLayout {
return 10
}
}

extension TimerViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? CategoryListCollectionViewCell else { return }
guard let item = dataSource?.itemIdentifier(for: indexPath) else { return }
timerViewModel.categoryDidSelected(category: item.category)
cell.updateShadow()
}

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? CategoryListCollectionViewCell else { return }
cell.updateShadow()
}
}

// MARK: - Constants
private extension TimerViewController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ protocol TimerViewModelInput {
func deviceOrientationDidChange(_ sender: DeviceOrientation)
func deviceProximityDidChange(_ sender: Bool)
func categorySettingButtoneDidTapped()
func categoryDidSelected(category: Category)
}

protocol TimerViewModelOutput {
var isDeviceFaceDownPublisher: AnyPublisher<Bool, Never> { get }
var isPresentingCategoryPublisher: AnyPublisher<Void, Never> { get }
var totalTimePublisher: AnyPublisher<Int, Never> { get }
var categoryPublisher: AnyPublisher<[Category], Never> { get }
var categoryChangePublisher: AnyPublisher<[Category], Never> { get }
var categoriesPublisher: AnyPublisher<[Category], Never> { get }
}

typealias TimerViewModelProtocol = TimerViewModelInput & TimerViewModelOutput

final class TimerViewModel: TimerViewModelProtocol {

// MARK: UseCase
private var timerUseCase: TimerUseCase
private var userInfoUserCase: StudyLogUseCase
Expand All @@ -35,15 +36,17 @@ final class TimerViewModel: TimerViewModelProtocol {
private var isDeviceFaceDownSubject = PassthroughSubject<Bool, Never>()
private var isPresentingCategorySubject = PassthroughSubject<Void, Never>()
private var totalTimeSubject = PassthroughSubject<Int, Never>()
private var categorySubject = PassthroughSubject<[Category], Never>()

private var categoryChangeSubject = PassthroughSubject<[Category], Never>()
private var categoriesSubject = PassthroughSubject<[Category], Never>()

// MARK: Properties
private var proximity: Bool?
private var orientation: DeviceOrientation = .unknown
private var timerState: TimerState = .notStarted
private var cancellables = Set<AnyCancellable>()
private var totalTime: Int = 0 // 총 공부 시간
private var categories = [Category]()
private var selectedCategory: Category?

// MARK: - init
init(timerUseCase: TimerUseCase, userInfoUserCase: StudyLogUseCase) {
Expand All @@ -64,8 +67,12 @@ final class TimerViewModel: TimerViewModelProtocol {
return totalTimeSubject.eraseToAnyPublisher()
}

var categoryPublisher: AnyPublisher<[Category], Never> {
return categorySubject.eraseToAnyPublisher()
var categoryChangePublisher: AnyPublisher<[Category], Never> {
return categoryChangeSubject.eraseToAnyPublisher()
}

var categoriesPublisher: AnyPublisher<[Category], Never> {
return categoriesSubject.eraseToAnyPublisher()
}

// MARK: Input
Expand Down Expand Up @@ -96,10 +103,15 @@ final class TimerViewModel: TimerViewModelProtocol {
} receiveValue: { [weak self] userInfo in
guard let self = self else { return }
self.totalTimeDidChange(time: userInfo.totalTime)
self.cateogoriesDidChange(categories: userInfo.category)
self.categoriesDidChange(categories: userInfo.category)
}
.store(in: &cancellables)
}

func categoryDidSelected(category: Category) {
FMLogger.timer.debug("\(category.subject)가 선택되었습니다.")
selectedCategory = category
}
}

// MARK: Private Methods
Expand Down Expand Up @@ -129,17 +141,18 @@ private extension TimerViewModel {
totalTimeSubject.send(totalTime)
}

func cateogoriesDidChange(categories: [Category]) {
func categoriesDidChange(categories: [Category]) {
self.categories = categories
categorySubject.send(categories)
categoriesSubject.send(categories)
}
}

// MARK: - Timer
private extension TimerViewModel {
/// 타이머 시작
func startTimer() {
timerUseCase.startTimer(startTime: Date(), userId: 1, categoryId: 1)
let categoryId = selectedCategory?.id
timerUseCase.startTimer(startTime: Date(), categoryId: categoryId)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
Expand All @@ -159,7 +172,8 @@ private extension TimerViewModel {

/// 타이머 재시작
func resumeTimer() {
timerUseCase.resumeTimer(resumeTime: Date(), userId: 1, categoryId: 1)
let categoryId = selectedCategory?.id
timerUseCase.resumeTimer(resumeTime: Date(), categoryId: categoryId)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
Expand All @@ -179,7 +193,8 @@ private extension TimerViewModel {

/// 타이머 일시정지
func suspendTimer() {
timerUseCase.suspendTimer(suspendTime: Date(), userId: 1, categoryId: 1)
let categoryId = selectedCategory?.id
timerUseCase.suspendTimer(suspendTime: Date(), categoryId: categoryId)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
Expand All @@ -194,6 +209,19 @@ private extension TimerViewModel {
self.timerState = .suspended
self.totalTime += learningTime
self.totalTimeSubject.send(self.totalTime)
guard let selectedCategory = selectedCategory,
let categoryStudyTime = selectedCategory.studyTime,
let categoryIndex = self.categories.firstIndex(of: selectedCategory) else { return }

let newCategory = Category(
id: selectedCategory.id,
color: selectedCategory.color,
subject: selectedCategory.subject,
studyTime: categoryStudyTime + learningTime)

categories[categoryIndex] = newCategory
categoryChangeSubject.send(categories)
self.selectedCategory = nil
}
.store(in: &cancellables)
}
Expand Down

0 comments on commit 22cee70

Please sign in to comment.