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
Binary file removed .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ SourcePackages/
.swiftpm

.build/*
**/.build/
.DS_Store
**/.DS_Store

# CocoaPods
#
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#if os(iOS)
import ActivityKit
import Foundation

public struct TimeWatcherWidgetAttributes: ActivityAttributes {

public struct ContentState: Codable, Hashable {

public init(timeLapse: TimeInterval, currentDate: Date, timeLapseString: String, timerStatus: TimerStatus) {

let minusMilliSec = Calendar.current.date(byAdding: -timeLapse.milliSec,
to: currentDate)
let startRangeDate = Calendar.current.date(byAdding: [
.hour: -timeLapse.hour,
.minute: -timeLapse.minute,
.second: -timeLapse.seconds
],
to: minusMilliSec)
let endRangeDate = Calendar.current.date(byAdding: .hour,
value: AppConstants.maxDisplayTime,
to: currentDate) ?? currentDate

self.timeLapse = startRangeDate...endRangeDate
self.timeLapseString = timeLapseString
self.timerStatus = timerStatus
}

public init(timeLapse: ClosedRange<Date>, timeLapseString: String, timerStatus: TimerStatus) {

self.timeLapse = timeLapse
self.timeLapseString = timeLapseString
self.timerStatus = timerStatus
}

/// 経過時間
public var timeLapse: ClosedRange<Date>
/// 経過時間の文字列
public var timeLapseString: String
/// タイマーの状態
public var timerStatus: TimerStatus

public var useableActions: [TimerActionType] {

return timerStatus.useableActions
}

public var statusIcon: String {

return timerStatus.icon
}
}

public init() {}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

extension TimeInterval {

public var hour: Int {

return abs(Int(self / 3600))
}

public var minute: Int {

return Int(self / 60) % 60
}

public var seconds: Int {

return Int(self) % 60
}

public var milliSec: Int {

let timeLapse = self * 1000
let roundTimeLapse = timeLapse.rounded()
return Int(roundTimeLapse) % 1000
}

/// HH:mm:ss.SSS形式の経過時間の文字列を返す
public var timeLapseFullString: String {

guard AppConstants.maxDisplayTimeLapse > self else {

return AppConstants.maxDisplayTimeString
}

return String(format: "%02d:%02d:%02d.%03d", self.hour, self.minute, self.seconds, self.milliSec)
}

/// HH:mm:ss形式の経過時間文字列を返す
public var timeLapseShortString: String {

guard AppConstants.maxDisplayTimeLapse > self else {

return AppConstants.maxDisplayShortTimeString
}

return String(format: "%02d:%02d:%02d", self.hour, self.minute, self.seconds)
}
}
33 changes: 33 additions & 0 deletions LocalPackage/Sources/TimeWatcherCore/Utility/Logger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation
import OSLog

public let logger = CustomLogger()

public struct CustomLogger {

#if DEBUG
private let logger = Logger(subsystem: "taichi.satou.TimeWatcher", category: "DEBUG CONFIG")
#else
private let logger = Logger(subsystem: "taichi.satou.TimeWatcher", category: "PRD CONFIG")
#endif

public func debug(_ message: String, file: String = #fileID, function: String = #function, line: Int = #line) {

logger.debug("🟩 [DEBUG] [\(file):\(function) \(line)]: \(message)")
}

public func info(_ message: String, file: String = #fileID, function: String = #function, line: Int = #line) {

logger.info("🟪 [INFO] [\(file):\(function) \(line)]: \(message)")
}

public func warning(_ message: String, file: String = #fileID, function: String = #function, line: Int = #line) {

logger.warning("🟨 [WARNING] [\(file):\(function) \(line)]: \(message)")
}

public func error(_ message: String, file: String = #fileID, function: String = #function, line: Int = #line) {

logger.error("🟥 [ERROR] [\(file):\(function) \(line)]: \(message)")
}
}
123 changes: 123 additions & 0 deletions LocalPackage/Sources/TimeWatcherFeature/MainTimer/MainTimerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import SwiftUI
import TimeWatcherCore
import TimeWatcherExternalResouce

public struct MainTimerView: View {

@StateObject var viewModel: MainTimerViewModel
@EnvironmentObject var openUrlViewModel: OpenUrlViewModel

// MARK: - layout property

private let actionButtonTopPadding: CGFloat = 50
private let emergencyTextTopPadding: CGFloat = 20
private let emergencyTextHorizontalPadding: CGFloat = 30
private let actionButtonSpacing: CGFloat = 100
private let viewBottomPadding: CGFloat = 15
private let timerTextBottomPadding: CGFloat = 20

private let actionButtonSize: CGFloat = 80

private let displayTimeFontSize: CGFloat = 40
private let emergencyTextFontSize: CGFloat = 18
private let actionButtonTextFontSize: CGFloat = 18

private let actionButtonShadowRadius: CGFloat = 5
private let actionButtonShadowY: CGFloat = 4

public init(viewModel: MainTimerViewModel) {

self._viewModel = StateObject(wrappedValue: viewModel)
}

// MARK: - view body property

public var body: some View {
NavigationStack {
ZStack {
Color(CustomColor.primaryBackgroundColor)
.ignoresSafeArea()
VStack(spacing: .zero) {
createTimerDisplayView()
.frame(maxWidth: .infinity,
maxHeight: .infinity)
Spacer()
.frame(height: timerTextBottomPadding)
Divider()
Spacer()
.frame(height: actionButtonTopPadding)
createTimerActionView()
Spacer(minLength: viewBottomPadding)
}
}
}
.onAppear {
viewModel.onAppear()
}
.onReceive(openUrlViewModel.$widgetUrlKey.compactMap { $0 }) { widgetUrl in
viewModel.onOpenLiveActivityUrl(widgetUrl)
}
}
}

// MARK: - private method

private extension MainTimerView {

func createTimerDisplayView() -> some View {
ZStack {
TimerClockAnimationView(progress: viewModel.timeProgressPerMinute,
size: .infinity)
.padding()
VStack(spacing: .zero) {
Text(viewModel.currentTimeString)
.font(.system(size: displayTimeFontSize,
weight: .bold))
.foregroundStyle(Color(asset: CustomColor.timerTextColor))
if viewModel.isOverMaxTime {
Spacer()
.frame(height: emergencyTextTopPadding)
Text("最大表経過時間を超過しているため、これ以上は計測できません。")
.font(.system(size: emergencyTextFontSize))
.foregroundStyle(Color(CustomColor.emergencyColor))
.padding(.horizontal, emergencyTextHorizontalPadding)
}
}
}
}

func createTimerActionView() -> some View {
HStack(spacing: actionButtonSpacing) {
ForEach(viewModel.timerStatus.useableActions, id: \.self) {
createTimerActionButton($0)
}
}
.animation(.spring, value: viewModel.timerStatus)
}

func createTimerActionButton(_ type: TimerActionType) -> some View {
Button {
viewModel.tappedTimerActionButton(type)
} label: {
Text(type.buttonTitle)
.font(.system(size: actionButtonTextFontSize,
weight: .bold))
.frame(width: actionButtonSize,
height: actionButtonSize)
}
.frameButtonStyle(radius: actionButtonSize / 2)
.shadow(radius: actionButtonShadowRadius,
y: actionButtonShadowY)
.accessibilityIdentifier("ActionButton_\(type.buttonTitle)")
}
}

#Preview("通常時") {
MainTimerView(viewModel: MainTimerViewModel())
.environmentObject(OpenUrlViewModel())
}

#Preview("最大表示可能経過時間超過時") {
MainTimerView(viewModel: MainTimerViewModel(isOverMaxTime: true))
.environmentObject(OpenUrlViewModel())
}
Loading
Loading