diff --git a/HANE24.xcodeproj/project.pbxproj b/HANE24.xcodeproj/project.pbxproj index 3728633..cef2964 100644 --- a/HANE24.xcodeproj/project.pbxproj +++ b/HANE24.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 0E0FE5F32BB3E6110050498E /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0FE5F22BB3E6110050498E /* CircularProgressBar.swift */; }; + 0E0FE5F42BB3E6180050498E /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0FE5F22BB3E6110050498E /* CircularProgressBar.swift */; }; 0E1654F3299A285B001E5EED /* HANE24App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1654F2299A285B001E5EED /* HANE24App.swift */; }; 0E1654F5299A285B001E5EED /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1654F4299A285B001E5EED /* ContentView.swift */; }; 0E1654F7299A285E001E5EED /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E1654F6299A285E001E5EED /* Assets.xcassets */; }; @@ -37,6 +39,9 @@ 0ED6E3FC2B354DFC0026E69D /* HANE24WidgetConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED6E3FA2B354DFC0026E69D /* HANE24WidgetConstant.swift */; }; 0ED6E3FF2B354E670026E69D /* AccumulationTimeStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED6E3FE2B354E670026E69D /* AccumulationTimeStruct.swift */; }; 0ED6E4002B354E670026E69D /* AccumulationTimeStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED6E3FE2B354E670026E69D /* AccumulationTimeStruct.swift */; }; + 0EE06CBD2BB2819500B4988C /* HomeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE06CBC2BB2819500B4988C /* HomeVM.swift */; }; + 0EE06CBF2BB2AD4C00B4988C /* HomeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE06CBE2BB2AD4C00B4988C /* HomeHeaderView.swift */; }; + 0EE06CC12BB2B2EB00B4988C /* PullToRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE06CC02BB2B2EB00B4988C /* PullToRefresh.swift */; }; 0EE58193299CC24000EE3351 /* MoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE58192299CC24000EE3351 /* MoreView.swift */; }; 0EE58198299CC74C00EE3351 /* ReissuanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE58197299CC74C00EE3351 /* ReissuanceView.swift */; }; 0EEB0E2429AE2AF700FEB700 /* CardProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EEB0E2329AE2AF700FEB700 /* CardProgressView.swift */; }; @@ -110,6 +115,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0E0FE5F22BB3E6110050498E /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; 0E1654EF299A285B001E5EED /* 24HANE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 24HANE.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0E1654F2299A285B001E5EED /* HANE24App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HANE24App.swift; sourceTree = ""; }; 0E1654F4299A285B001E5EED /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -140,6 +146,9 @@ 0ED6E3FE2B354E670026E69D /* AccumulationTimeStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccumulationTimeStruct.swift; sourceTree = ""; }; 0ED6E4032B3559B80026E69D /* 24HANE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = 24HANE.entitlements; sourceTree = ""; }; 0ED6E4042B3559DF0026E69D /* HANE24WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = HANE24WidgetExtension.entitlements; sourceTree = ""; }; + 0EE06CBC2BB2819500B4988C /* HomeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeVM.swift; sourceTree = ""; }; + 0EE06CBE2BB2AD4C00B4988C /* HomeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeHeaderView.swift; sourceTree = ""; }; + 0EE06CC02BB2B2EB00B4988C /* PullToRefresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToRefresh.swift; sourceTree = ""; }; 0EE58192299CC24000EE3351 /* MoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreView.swift; sourceTree = ""; }; 0EE58197299CC74C00EE3351 /* ReissuanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReissuanceView.swift; sourceTree = ""; }; 0EEB0E2329AE2AF700FEB700 /* CardProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardProgressView.swift; sourceTree = ""; }; @@ -281,8 +290,10 @@ children = ( 0E16550F299A5A82001E5EED /* HomeView.swift */, 9A1A4BED299ADE650076D650 /* TodayAccTimeCardView.swift */, + 0E0FE5F22BB3E6110050498E /* CircularProgressBar.swift */, 0E165507299A3C20001E5EED /* ChartView.swift */, 0E16550B299A3DF6001E5EED /* ChartDetailView.swift */, + 0EE06CBE2BB2AD4C00B4988C /* HomeHeaderView.swift */, 0E17EFA9299B5C0A0089BCD8 /* PopulationView.swift */, 2B1326102B2821E600301A8B /* ThisMonthAccTimeCardView.swift */, 2B1326122B285B1D00301A8B /* NoticeView.swift */, @@ -325,6 +336,7 @@ 0E16550D299A5887001E5EED /* MainView.swift */, 0EFAF5BD29A4D97800125948 /* LoadingView.swift */, 0E3B32A829A3815B001D10BF /* SignInWebView.swift */, + 0EE06CC02BB2B2EB00B4988C /* PullToRefresh.swift */, 0EFAF5B729A492A500125948 /* SignInView.swift */, 0EE5818F299CC01D00EE3351 /* Home */, 0EE58190299CC02A00EE3351 /* Calendar */, @@ -363,6 +375,7 @@ D6C528F72BBECE0700F51A06 /* ReissueVM.swift */, D6185AE02BB3C71800E6944A /* CalendarVM.swift */, 0E3ED8A72BB13727001B0BAE /* NetworkManager.swift */, + 0EE06CBC2BB2819500B4988C /* HomeVM.swift */, 0E6B608E29AC850D009D8BC4 /* NetworkMonitoringManager.swift */, ); path = ViewModel; @@ -608,6 +621,7 @@ 0EFDFD47299F5AF500A911F7 /* DarkMode.swift in Sources */, 0E17EFAA299B5C0A0089BCD8 /* PopulationView.swift in Sources */, 0E165510299A5A82001E5EED /* HomeView.swift in Sources */, + 0EE06CBD2BB2819500B4988C /* HomeVM.swift in Sources */, 0EEB0E2629AE303100FEB700 /* AlertView.swift in Sources */, 9AF730F1299D58F100AF2E53 /* AccTimeCardForCalendarView.swift in Sources */, 2B1326112B2821E600301A8B /* ThisMonthAccTimeCardView.swift in Sources */, @@ -628,9 +642,11 @@ D6E780C92BCCE84300FB547D /* CalendarModel.swift in Sources */, D6185AE12BB3C71800E6944A /* CalendarVM.swift in Sources */, 9AF730EF299D3A4900AF2E53 /* CalendarGridView.swift in Sources */, + 0EE06CC12BB2B2EB00B4988C /* PullToRefresh.swift in Sources */, 0E3ED8A82BB13727001B0BAE /* NetworkManager.swift in Sources */, D68CC3F02BCC1E81008D62E2 /* ReissueButtonAppearance.swift in Sources */, 0E9868252B2B6C0B00E127DC /* CalendarHeaderView.swift in Sources */, + 0EE06CBF2BB2AD4C00B4988C /* HomeHeaderView.swift in Sources */, D6E780D02BCD03AE00FB547D /* HowToReissue.swift in Sources */, D6E780D72BCD0CAE00FB547D /* AlertButtons.swift in Sources */, D68CC3F62BCC1FA4008D62E2 /* LogoutButton.swift in Sources */, @@ -654,6 +670,7 @@ D6C528F82BBECE0700F51A06 /* ReissueVM.swift in Sources */, D6E19CF32BEB427B005DF8C3 /* Error.swift in Sources */, 9AF730F3299D599300AF2E53 /* TagLogView.swift in Sources */, + 0E0FE5F32BB3E6110050498E /* CircularProgressBar.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -664,6 +681,7 @@ 0ED6E4002B354E670026E69D /* AccumulationTimeStruct.swift in Sources */, 0ED6E3F92B354DD00026E69D /* UserDefaultsExtension.swift in Sources */, 0ED6E3E82B354D320026E69D /* HANE24WidgetBundle.swift in Sources */, + 0E0FE5F42BB3E6180050498E /* CircularProgressBar.swift in Sources */, 0ED6E3EA2B354D320026E69D /* HANE24Widget.swift in Sources */, 0ED6E3F52B354DBD0026E69D /* DateExtensions.swift in Sources */, 0ED6E3F62B354DC00026E69D /* ColorExtension.swift in Sources */, diff --git a/HANE24/Model/JSONs.swift b/HANE24/Model/JSONs.swift index 8129a50..8d9f2ab 100644 --- a/HANE24/Model/JSONs.swift +++ b/HANE24/Model/JSONs.swift @@ -25,11 +25,21 @@ struct PerMonth: Codable { struct InfoMessage: Codable { let title: String let content: String + + init() { + self.title = "" + self.content = "" + } } struct InfoMessages: Codable { let fundInfoNotice: InfoMessage let tagLatencyNotice: InfoMessage + + init() { + self.fundInfoNotice = InfoMessage() + self.tagLatencyNotice = InfoMessage() + } } struct MainInfo: Codable { @@ -41,6 +51,16 @@ struct MainInfo: Codable { let gaepo: Int /// v3 let infoMessages: InfoMessages + + init() { + self.login = "" + self.profileImage = "" + self.isAdmin = false + self.inoutState = "" + self.tagAt = "" + self.gaepo = 0 + self.infoMessages = InfoMessages() + } } struct AccumulationTimes: Codable { @@ -50,6 +70,14 @@ struct AccumulationTimes: Codable { let sixMonthAccumulationTime: [Double] /// v3 let monthlyAcceptedAccumulationTime: Int64 + + init() { + self.todayAccumulationTime = 0 + self.monthAccumulationTime = 0 + self.sixWeekAccumulationTime = Array(repeating: 0, count: 6) + self.sixMonthAccumulationTime = Array(repeating: 0, count: 6) + self.monthlyAcceptedAccumulationTime = 0 + } } // 카드 신청 상태를 확인하는 API의 Response Parsing 구조체 diff --git a/HANE24/Model/Structs.swift b/HANE24/Model/Structs.swift index 346e13f..72b1e4b 100644 --- a/HANE24/Model/Structs.swift +++ b/HANE24/Model/Structs.swift @@ -34,4 +34,9 @@ struct MoreItem: Identifiable { struct Notice { var title: String var content: String + + init(title: String = "", content: String = "") { + self.title = title + self.content = content + } } diff --git a/HANE24/Utils/DateExtensions.swift b/HANE24/Utils/DateExtensions.swift index 5dc372e..350202d 100644 --- a/HANE24/Utils/DateExtensions.swift +++ b/HANE24/Utils/DateExtensions.swift @@ -164,7 +164,7 @@ extension Date { var daysOfMonth: [Date?] { var days: [Date?] = [] var day = self.firstDay - + for _ in 1.. + var percentage: Int + + init(drawingStroke: Binding, objectiveTime: Double, progressiveTime: Double) { + self.percentage = Int(progressiveTime / objectiveTime * 3600) * 100 + self.drawingStroke = drawingStroke + } + + var body: some View { + ZStack { + HStack(spacing: 0) { + Text("\(percentage)") + .font(.system(size: 32, weight: .medium, design: .default)) + .foregroundColor(.black) + Text("%") + .font(.system(size: 14, weight: .medium, design: .default)) + .foregroundColor(.black) + .padding(.top, 10) + } + Circle() + .stroke( + AngularGradient( + gradient: Gradient( + colors: [ + .gradientBlue, + .gradientWhtie, + .gradientPurple, + .gradientPurple, + .gradientWhtie, + .gradientBlue + ] + ), + center: .center, + startAngle: .zero, + endAngle: .degrees(360) + ).opacity(0.1), + style: StrokeStyle(lineWidth: 8, lineCap: .round) + ) + .overlay { + Circle() + .trim(from: 0, to: drawingStroke.wrappedValue ? CGFloat(percentage / 100) : 0) + .stroke( + AngularGradient( + gradient: Gradient(colors: [ + .gradientBlue.opacity(0.35), + .gradientWhtie, + .gradientPurple, + .gradientPurple, + .gradientWhtie, + .gradientBlue.opacity(0.35) + ]), + center: .center, + startAngle: .degrees(0), + endAngle: .degrees(360) + ), + style: StrokeStyle(lineWidth: 8, lineCap: .round)) + .rotationEffect(.degrees(270)) + } + } + } +} + +#Preview { + CircularProgressBar(drawingStroke: .constant(false), objectiveTime: 12345, progressiveTime: 1234) +} diff --git a/HANE24/View/Home/HomeHeaderView.swift b/HANE24/View/Home/HomeHeaderView.swift new file mode 100644 index 0000000..ed9ae22 --- /dev/null +++ b/HANE24/View/Home/HomeHeaderView.swift @@ -0,0 +1,66 @@ +// +// HeaderView.swift +// 24HANE +// +// Created by Katherine JANG on 3/26/24. +// + +import SwiftUI + +struct HomeHeaderView: View { + @Environment(\.colorScheme) var colorScheme + @ObservedObject var homeManager: HomeVM + + var body: some View { + VStack(alignment: .center, spacing: 20) { + HStack(alignment: .center) { + if homeManager.mainInfo.profileImage != "" { + AsyncImage(url: URL(string: homeManager.mainInfo.profileImage)) { image in + image + .resizable() + .scaledToFill() + .frame(width: 28, height: 28) + .clipShape(Circle()) + .padding(.trailing, 3) + } placeholder: { + Image(systemName: "person.circle") + .resizable() + .frame(width: 28, height: 28) + .padding(.trailing, 3) + .foregroundColor(.iconColor) + } + } else { + Image(systemName: "person.circle") + .resizable() + .frame(width: 28, height: 28) + .padding(.trailing, 3) + .foregroundColor(.iconColor) + } + + Text(homeManager.mainInfo.login) + .font(.system(size: 20, weight: .semibold, design: .rounded)) + .foregroundColor(!homeManager.isInCluster && colorScheme == .light ? .black : .white) + + if homeManager.isInCluster { + Circle() + .foregroundColor(.green) + .frame(width: 8, height: 8) + .padding(.bottom, 10) + .padding(.leading, 0) + } + + Spacer() + } + .padding(.top, 20) + .frame(height: 30) + .padding(.horizontal, 30) + } + + } +} + +#Preview { + @StateObject var homeVM = HomeVM() + + return HomeHeaderView(homeManager: homeVM) +} diff --git a/HANE24/View/Home/HomeView.swift b/HANE24/View/Home/HomeView.swift index 5c264aa..d079f64 100644 --- a/HANE24/View/Home/HomeView.swift +++ b/HANE24/View/Home/HomeView.swift @@ -7,7 +7,73 @@ import SwiftUI -func getWeeklyPeriod() -> [String] { +struct HomeView: View { +// init(homeManageR: HomeVM, fundInfo: Binding, tagLatencyInfo: Binding) { +// // UIPageControl.appearance().currentPageIndicatorTintColor = UIColor(Color.gradientPurple) +// // UIPageControl.appearance().pageIndicatorTintColor = UIColor.gray.withAlphaComponent(0.2) +// +// self._isNoticedFundInfo = fundInfo +// self._isNoticedTagLatencyInfo = tagLatencyInfo +// self._ +// } + + @State var test: Bool = true + @Environment(\.colorScheme) var colorScheme + @EnvironmentObject var hane: Hane + + @ObservedObject var homeManager: HomeVM + + @Binding var isNoticedFundInfo: Bool + @Binding var isNoticedTagLatencyInfo: Bool + + var body: some View { + ZStack { + if homeManager.isInCluster { + Image("Background") + .resizable() + .edgesIgnoringSafeArea(.top) + .opacity(0.7) + } else { + Theme.backgroundColor(forScheme: colorScheme) + .edgesIgnoringSafeArea(colorScheme == .dark ? .all : .top) + } + VStack(alignment: .center, spacing: 20) { + HomeHeaderView(homeManager: homeManager) + ScrollView { + PullToRefresh(coordinateSpaceName: "pullToRefresh") { + Task { + try await homeManager.refresh() + } + } + + VStack(spacing: 22.5) { + TodayAccTimeCardView(homeManager: homeManager, isNoticed: $isNoticedTagLatencyInfo) + .padding(.horizontal, 30) + + ThisMonthAccTimeCardView(homeManager: homeManager, isNoticed: $isNoticedFundInfo) + .padding(.horizontal, 30) + + TabView { + ChartView(item: ChartItem(id: "주", title: "최근 주간 그래프", period: getWeeklyPeriod(), data: homeManager.accumulationTimes.sixWeekAccumulationTime)) + .padding(.horizontal, 10) + ChartView(item: ChartItem(id: "개월", title: "최근 월간 그래프", period: getMonthlyPeriod(), data: homeManager.accumulationTimes.sixMonthAccumulationTime)) + .padding(.horizontal, 10) + } + .padding(.horizontal, 20) + .tabViewStyle(.page) + .frame(height: 289) + PopulationView(population: homeManager.mainInfo.gaepo) + .padding(.horizontal, 30) + } + .padding(.bottom, 30) + .padding(.top, 10) + } .coordinateSpace(name: "pullToRefresh") + } + } + } +} + +private func getWeeklyPeriod() -> [String] { var weeklyPeriod: [String] = [] var date = Date() let formatter = DateFormatter() @@ -23,7 +89,7 @@ func getWeeklyPeriod() -> [String] { return weeklyPeriod } -func getMonthlyPeriod() -> [String] { +private func getMonthlyPeriod() -> [String] { var monthlyPeriod: [String] = [] var date = Date() let formatter = DateFormatter() @@ -36,148 +102,9 @@ func getMonthlyPeriod() -> [String] { return monthlyPeriod } -struct PullToRefresh: View { - var coordinateSpaceName: String - var onRefresh: () -> Void - - @State var needRefresh: Bool = false - - var body: some View { - GeometryReader { geo in - if (geo.frame(in: .named(coordinateSpaceName))).midY > 50 { - Spacer() - .onAppear { - needRefresh = true - } - } else if geo.frame(in: .named(coordinateSpaceName)).midY < 10 { - Spacer() - .onAppear { - if needRefresh { - needRefresh = false - onRefresh() - } - } - } - HStack { - Spacer() - if needRefresh { - ProgressView() - } - Spacer() - } - } .padding(.top, -50) - } -} - -struct HomeView: View { - init(fundInfo: Binding, tagLatencyInfo: Binding) { - UIPageControl.appearance().currentPageIndicatorTintColor = UIColor(Color.gradientPurple) - UIPageControl.appearance().pageIndicatorTintColor = UIColor.gray.withAlphaComponent(0.2) - - self._isNoticedFundInfo = fundInfo - self._isNoticedTagLatencyInfo = tagLatencyInfo - } - @State var test: Bool = true - @Environment(\.colorScheme) var colorScheme - @EnvironmentObject var hane: Hane - - @Binding var isNoticedFundInfo: Bool - @Binding var isNoticedTagLatencyInfo: Bool - - var body: some View { - NavigationView { - ZStack { - if hane.isInCluster { - Image("Background") - .resizable() - .edgesIgnoringSafeArea(.top) - .opacity(0.7) - } else { - Theme.backgroundColor(forScheme: colorScheme) - .edgesIgnoringSafeArea(colorScheme == .dark ? .all : .top) - } - VStack(alignment: .center, spacing: 20) { - HStack(alignment: .center) { - if hane.profileImage != "" { - AsyncImage(url: URL(string: hane.profileImage)) { image in - image - .resizable() - .scaledToFill() - .frame(width: 28, height: 28) - .clipShape(Circle()) - .padding(.trailing, 3) - } placeholder: { - Image(systemName: "person.circle") - .resizable() - .frame(width: 28, height: 28) - .padding(.trailing, 3) - .foregroundColor(.iconColor) - } - } else { - Image(systemName: "person.circle") - .resizable() - .frame(width: 28, height: 28) - .padding(.trailing, 3) - .foregroundColor(.iconColor) - } - - Text(hane.loginID) - .font(.system(size: 20, weight: .semibold, design: .rounded)) - .foregroundColor(!hane.isInCluster && colorScheme == .light ? .black : .white) - - if hane.isInCluster { - Circle() - .foregroundColor(.green) - .frame(width: 8, height: 8) - .padding(.bottom, 10) - .padding(.leading, 0) - } - Spacer() - } - .padding(.top, 20) - .frame(height: 30) - .padding(.horizontal, 30) - - ScrollView { - PullToRefresh(coordinateSpaceName: "pullToRefresh") { - Task { - try await hane.refresh() - } - } - - VStack(spacing: 22.5) { - TodayAccTimeCardView(isNoticed: $isNoticedTagLatencyInfo) - .padding(.horizontal, 30) - - ThisMonthAccTimeCardView(isNoticed: $isNoticedFundInfo) - .padding(.horizontal, 30) - - TabView { - ChartView(item: ChartItem(id: "주", title: "최근 주간 그래프", period: getWeeklyPeriod(), data: hane.sixWeekAccumulationTime)) - .padding(.horizontal, 10) - ChartView(item: ChartItem(id: "개월", title: "최근 월간 그래프", period: getMonthlyPeriod(), data: hane.sixMonthAccumulationTime)) - .padding(.horizontal, 10) - } - .padding(.horizontal, 20) - .tabViewStyle(.page) - .frame(height: 289) - - PopulationView() - .padding(.horizontal, 30) - } - .padding(.bottom, 30) - .padding(.top, 10) - } .coordinateSpace(name: "pullToRefresh") - } - } - } - .navigationTitle("알림") - - } -} - -#Preview { - HomeView(fundInfo: .constant(false), tagLatencyInfo: .constant(false)) - .environmentObject(Hane()) -} +// +//#Preview { +// HomeView(fundInfo: .constant(false), tagLatencyInfo: .constant(false)) +// .environmentObject(Hane()) +//} diff --git a/HANE24/View/Home/NoticeView.swift b/HANE24/View/Home/NoticeView.swift index 402e9d7..3f925d1 100644 --- a/HANE24/View/Home/NoticeView.swift +++ b/HANE24/View/Home/NoticeView.swift @@ -7,10 +7,10 @@ import SwiftUI -struct NoticeView: View { +public struct NoticeView: View { @Binding var showNotice: Bool - var notice: Notice - var body: some View { + var notice: Notice + public var body: some View { ZStack { Color.black.opacity(0.4) .ignoresSafeArea(.all) diff --git a/HANE24/View/Home/PopulationView.swift b/HANE24/View/Home/PopulationView.swift index faf7161..b2e077b 100644 --- a/HANE24/View/Home/PopulationView.swift +++ b/HANE24/View/Home/PopulationView.swift @@ -8,7 +8,8 @@ import SwiftUI struct PopulationView: View { - @EnvironmentObject var hane: Hane + var population: Int + var body: some View { VStack(alignment: .leading, spacing: 8) { Text("실시간 현황") @@ -25,7 +26,7 @@ struct PopulationView: View { .padding(.horizontal, 20) .foregroundColor(.black) Spacer() - Text("\(hane.clusterPopulation)") + Text("\(population)") .font(.system(size: 20, weight: .bold)) .foregroundColor(.black) .padding(.leading, 20) @@ -38,8 +39,3 @@ struct PopulationView: View { } } } - -#Preview { - PopulationView() - .environmentObject(Hane()) -} diff --git a/HANE24/View/Home/ThisMonthAccTimeCardView.swift b/HANE24/View/Home/ThisMonthAccTimeCardView.swift index eefd3ea..c80828a 100644 --- a/HANE24/View/Home/ThisMonthAccTimeCardView.swift +++ b/HANE24/View/Home/ThisMonthAccTimeCardView.swift @@ -9,6 +9,7 @@ import SwiftUI struct ThisMonthAccTimeCardView: View { @EnvironmentObject var hane: Hane + @ObservedObject var homeManager: HomeVM @Binding var isNoticed: Bool @@ -37,11 +38,11 @@ struct ThisMonthAccTimeCardView: View { LoadingAnimation() } else { HStack(alignment: .bottom, spacing: 0) { - Text("\(hane.monthlyAccumulationTime / 3600)") + Text("\(homeManager.accumulationTimes.monthAccumulationTime / 3600)") .font(.system(size: 20, weight: .bold)) Text("시간 ") .font(.system(size: 16, weight: .bold)) - Text("\(hane.monthlyAccumulationTime % 3600 / 60)") + Text("\(homeManager.accumulationTimes.monthAccumulationTime % 3600 / 60)") .font(.system(size: 20, weight: .bold)) Text("분") .font(.system(size: 16, weight: .bold)) @@ -89,6 +90,7 @@ struct ThisMonthAccTimeCardView: View { if hane.loading { LoadingAnimation() } else { + // TODO: hane -> homeVM HStack(alignment: .bottom, spacing: 0) { Text("\(hane.thisMonthAcceptedAccumulationTime / 3600)") .font(.system(size: 20, weight: .bold)) @@ -124,6 +126,6 @@ struct ThisMonthAccTimeCardView: View { hane.loading = false hane.monthlyAccumulationTime = 34567 hane.thisMonthAcceptedAccumulationTime = 12345 - return ThisMonthAccTimeCardView(isNoticed: .constant(false)) + return ThisMonthAccTimeCardView(homeManager: HomeVM(), isNoticed: .constant(false)) .environmentObject(hane) } diff --git a/HANE24/View/Home/TodayAccTimeCardView.swift b/HANE24/View/Home/TodayAccTimeCardView.swift index 39f7364..879c7b3 100644 --- a/HANE24/View/Home/TodayAccTimeCardView.swift +++ b/HANE24/View/Home/TodayAccTimeCardView.swift @@ -8,6 +8,7 @@ import SwiftUI struct TodayAccTimeCardView: View { + @ObservedObject var homeManager: HomeVM @EnvironmentObject var hane: Hane @AppStorage("DailySelectionOption") private var dailySelectionOption = UserDefaults.standard.integer(forKey: "DailySelectionOption") @@ -58,11 +59,11 @@ struct TodayAccTimeCardView: View { LoadingAnimation() } else { HStack(alignment: .bottom, spacing: 0) { - Text("\(hane.dailyAccumulationTime / 3600)") + Text("\(homeManager.dailyAccumulationTime / 3600)") .font(.system(size: 20, weight: .bold)) Text("시간 ") .font(.system(size: 16, weight: .bold)) - Text("\(hane.dailyAccumulationTime % 3600 / 60)") + Text("\(homeManager.dailyAccumulationTime % 3600 / 60)") .font(.system(size: 20, weight: .bold)) Text("분") .font(.system(size: 16, weight: .bold)) @@ -118,7 +119,7 @@ struct TodayAccTimeCardView: View { .padding(.leading, 10) .padding(.trailing, 14) /// Progress Circle - progressCircle + CircularProgressBar(drawingStroke: $drawingStroke, objectiveTime: options[dailySelectionOption], progressiveTime: Double(hane.dailyAccumulationTime)) .frame(width: 112, height: 112) .padding(.top, 11) .padding(.bottom, 18) @@ -131,60 +132,6 @@ struct TodayAccTimeCardView: View { } .frame(height: isFold ? 80 : 260, alignment: .top) } - - var progressCircle: some View { - - ZStack { - HStack(spacing: 0) { - Text("\(Int(Double(hane.dailyAccumulationTime) / Double(options[dailySelectionOption] * 3600) * 100))") - .font(.system(size: 32, weight: .medium, design: .default)) - .foregroundColor(.black) - Text("%") - .font(.system(size: 14, weight: .medium, design: .default)) - .foregroundColor(.black) - .padding(.top, 10) - } - Circle() - .stroke( - AngularGradient( - gradient: Gradient( - colors: [ - .gradientBlue.opacity(0.1), - .gradientWhtie.opacity(0.1), - .gradientPurple.opacity(0.1), - .gradientPurple.opacity(0.1), - .gradientWhtie.opacity(0.1), - .gradientBlue.opacity(0.1) - ] - ), - center: .center, - startAngle: .zero, - endAngle: .degrees(360) - ), - style: StrokeStyle(lineWidth: 8, lineCap: .round) - ) - .overlay { - Circle() - .trim(from: 0, to: drawingStroke ? (Double(hane.dailyAccumulationTime) / Double(options[dailySelectionOption] * 3600)) : 0) - .stroke( - AngularGradient( - gradient: Gradient(colors: [ - .gradientBlue.opacity(0.35), - .gradientWhtie, - .gradientPurple, - .gradientPurple, - .gradientWhtie, - .gradientBlue.opacity(0.35) - ]), - center: .center, - startAngle: .degrees(0), - endAngle: .degrees(360) - ), - style: StrokeStyle(lineWidth: 8, lineCap: .round)) - .rotationEffect(.degrees(270)) - } - } - } } extension View { @@ -218,6 +165,6 @@ extension View { let hane = Hane() hane.dailyAccumulationTime = 12280 hane.loading = false - return TodayAccTimeCardView(isNoticed: .constant(false)) + return TodayAccTimeCardView(homeManager: HomeVM(), isNoticed: .constant(false)) .environmentObject(hane) } diff --git a/HANE24/View/MainView.swift b/HANE24/View/MainView.swift index ecea43b..54ba664 100644 --- a/HANE24/View/MainView.swift +++ b/HANE24/View/MainView.swift @@ -9,6 +9,8 @@ import SwiftUI struct MainView: View { @EnvironmentObject var hane: Hane + + @StateObject var homeVM = HomeVM() @State var selection = 1 @Environment(\.colorScheme) var colorScheme @@ -19,7 +21,7 @@ struct MainView: View { var body: some View { ZStack { TabView(selection: $selection) { - HomeView(fundInfo: $isNoticedFundInfo, tagLatencyInfo: $isNoticedTagLatencyInfo) + HomeView(homeManager: homeVM, isNoticedFundInfo: $isNoticedFundInfo, isNoticedTagLatencyInfo: $isNoticedTagLatencyInfo) .tabItem({ Image(selection == 1 ? "selectedHome" : "home").renderingMode(.template) }) .tag(1) @@ -38,6 +40,7 @@ struct MainView: View { .task { do { try await hane.refresh() + try await homeVM.refresh() } catch { print("error on MainView \(error.localizedDescription)") } diff --git a/HANE24/View/PullToRefresh.swift b/HANE24/View/PullToRefresh.swift new file mode 100644 index 0000000..392b374 --- /dev/null +++ b/HANE24/View/PullToRefresh.swift @@ -0,0 +1,41 @@ +// +// PullToRefresh.swift +// 24HANE +// +// Created by Katherine JANG on 3/26/24. +// + +import SwiftUI + +struct PullToRefresh: View { + var coordinateSpaceName: String + var onRefresh: () -> Void + + @State var needRefresh: Bool = false + + var body: some View { + GeometryReader { geo in + if (geo.frame(in: .named(coordinateSpaceName))).midY > 50 { + Spacer() + .onAppear { + needRefresh = true + } + } else if geo.frame(in: .named(coordinateSpaceName)).midY < 10 { + Spacer() + .onAppear { + if needRefresh { + needRefresh = false + onRefresh() + } + } + } + HStack { + Spacer() + if needRefresh { + ProgressView() + } + Spacer() + } + } .padding(.top, -50) + } +} diff --git a/HANE24/ViewModel/HaneVM.swift b/HANE24/ViewModel/HaneVM.swift index 1255ef2..4b57f9d 100644 --- a/HANE24/ViewModel/HaneVM.swift +++ b/HANE24/ViewModel/HaneVM.swift @@ -72,31 +72,13 @@ class Hane: ObservableObject { self.loginID = "" self.clusterPopulation = 0 - self.fundInfoNotice = Notice(title: "", content: "") - self.tagLatencyNotice = Notice(title: "", content: "") + self.fundInfoNotice = Notice() + self.tagLatencyNotice = Notice() self.inOutLog = InOutLog(inTimeStamp: nil, outTimeStamp: nil, durationSecond: nil) self.perMonth = PerMonth(login: "", profileImage: "", inOutLogs: [], totalAccumulationTime: 0, acceptedAccumulationTime: 0) - self.mainInfo = MainInfo( - login: "", - profileImage: "", - isAdmin: false, - inoutState: "", - tagAt: nil, - gaepo: 0, - infoMessages: - InfoMessages( - fundInfoNotice: InfoMessage(title: "", content: ""), - tagLatencyNotice: InfoMessage(title: "", content: "") - ) - ) - self.accumulationTimes = AccumulationTimes( - todayAccumulationTime: 0, - monthAccumulationTime: 0, - sixWeekAccumulationTime: Array(repeating: 0, count: 6), - sixMonthAccumulationTime: Array(repeating: 0, count: 6), - monthlyAcceptedAccumulationTime: 0 - ) + self.mainInfo = MainInfo() + self.accumulationTimes = AccumulationTimes() self.APIroot = "https://" + (Bundle.main.infoDictionary?["API_URL"] as? String ?? "wrong") self.reissueState = .none diff --git a/HANE24/ViewModel/HomeVM.swift b/HANE24/ViewModel/HomeVM.swift new file mode 100644 index 0000000..0dc91df --- /dev/null +++ b/HANE24/ViewModel/HomeVM.swift @@ -0,0 +1,77 @@ +// +// HomeVM.swift +// 24HANE +// +// Created by Katherine JANG on 3/26/24. +// + +import Foundation + +class HomeVM: ObservableObject { + @Published var isInCluster: Bool + + @Published var fundInfoNotice: InfoMessage + @Published var tagLatencyNotice: InfoMessage + + /// 누적시간데이터 + @Published var dailyAccumulationTime: Int64 = 0 + @Published var accumulationTimes: AccumulationTimes + + @Published var isLoading: Bool + + @Published var mainInfo: MainInfo + + var timer: Timer? + var lastTag: Date? + + init() { + self.isInCluster = false + + self.fundInfoNotice = InfoMessage() + self.tagLatencyNotice = InfoMessage( ) + + self.isLoading = false + + self.mainInfo = MainInfo() + + self.accumulationTimes = AccumulationTimes() + + self.lastTag = Date() + + self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in + guard let self = self else { return } + guard self.isInCluster else { return } + self.dailyAccumulationTime = self.accumulationTimes.todayAccumulationTime + if let lastTag = self.lastTag { + self.dailyAccumulationTime += (Date.now.millisecondsSince1970 - lastTag.millisecondsSince1970) / 1000 + } + } + } + + @MainActor + func updateAccumulationTimes() async throws { + guard let accTimes = try await NetworkManager.shared.getRequest("/v3/tag-log/accumulationTimes", type: AccumulationTimes.self) else { + throw MyError.tokenExpired("") + } + self.accumulationTimes = accTimes + self.dailyAccumulationTime = accTimes.todayAccumulationTime + } + + //TODO: 요청한 데이터가 nil일 경우 에러 핸들링 + @MainActor + func updateMainInfo() async throws { + guard let mainInfo = try await NetworkManager.shared.getRequest("/v3/tag-log/maininfo", type: MainInfo.self) else { + throw MyError.tokenExpired("") + } + self.mainInfo = mainInfo + self.isInCluster = mainInfo.inoutState == "IN" + self.fundInfoNotice = mainInfo.infoMessages.fundInfoNotice + self.tagLatencyNotice = mainInfo.infoMessages.tagLatencyNotice + } + + @MainActor + func refresh() async throws { + try await self.updateMainInfo() + try await self.updateAccumulationTimes() + } +} diff --git a/HANE24/ViewModel/NetworkManager.swift b/HANE24/ViewModel/NetworkManager.swift index f7d1b1c..99366e3 100644 --- a/HANE24/ViewModel/NetworkManager.swift +++ b/HANE24/ViewModel/NetworkManager.swift @@ -36,7 +36,7 @@ class NetworkManager: NetworkProtocol { /// FIXME: token invalid 경우에 signIn 상태 변경 throw MyError.tokenExpired("get new token!") } - + var request = URLRequest(url: url) request.httpMethod = "GET" request.allHTTPHeaderFields = [ @@ -86,7 +86,7 @@ class NetworkManager: NetworkProtocol { throw MyError.tokenExpired("request Failed") } } - + func deleteRequest(_ urlPath: String) async throws { guard let url = URL(string: apiRoot + urlPath) else { return diff --git a/HANE24TEST/HANE24Test.swift b/HANE24TEST/HANE24Test.swift index 8097996..f55c834 100644 --- a/HANE24TEST/HANE24Test.swift +++ b/HANE24TEST/HANE24Test.swift @@ -86,5 +86,5 @@ final class CalendarTest: XCTestCase { } // MARK: - Missing Data Test - + } diff --git a/HANE24Widget/HANE24Widget.swift b/HANE24Widget/HANE24Widget.swift index dcc5ee2..25e1780 100644 --- a/HANE24Widget/HANE24Widget.swift +++ b/HANE24Widget/HANE24Widget.swift @@ -95,7 +95,7 @@ struct HANE24WidgetEntryView: View { .foregroundStyle(colorScheme == .dark ? Color(hex: "#EAEAEA") : Color(hex: "#707070")) .allowsTightening(true) } - + HStack { Rectangle() .frame(width: 2, height: 32) @@ -109,7 +109,7 @@ struct HANE24WidgetEntryView: View { } } .padding(.top, 18) - + HStack { Rectangle() .frame(width: 2, height: 32) diff --git a/HANE24Widget/HANE24WidgetConstant.swift b/HANE24Widget/HANE24WidgetConstant.swift index 991afee..471b235 100644 --- a/HANE24Widget/HANE24WidgetConstant.swift +++ b/HANE24Widget/HANE24WidgetConstant.swift @@ -8,7 +8,6 @@ import Foundation import WidgetKit - struct HaneWidgetConstant { static var storageKey = "accessToken" static var appGroupName = "group.24HoursAreNotEnough"