diff --git a/HongikYeolgong2.xcodeproj/project.pbxproj b/HongikYeolgong2.xcodeproj/project.pbxproj index 895080d..1670eb9 100644 --- a/HongikYeolgong2.xcodeproj/project.pbxproj +++ b/HongikYeolgong2.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 473E8EBA2CCEA702000F102C /* StudySessionResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 473E8EB92CCEA702000F102C /* StudySessionResponseDTO.swift */; }; 473E8EBC2CCEBEF3000F102C /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 473E8EBB2CCEBEF3000F102C /* ModalView.swift */; }; 4752A27D2CB96EB00073B784 /* CancleBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4752A27C2CB96EB00073B784 /* CancleBag.swift */; }; + 47581ED02CF01EB200A2EA31 /* AmplitudeSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 47581ECF2CF01EB200A2EA31 /* AmplitudeSwift */; }; 475B86DD2CA1AEF7000534B2 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 475B86DC2CA1AEF7000534B2 /* HomeView.swift */; }; 475B86DF2CA1AF1E000534B2 /* RecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 475B86DE2CA1AF1E000534B2 /* RecordView.swift */; }; 475B86E12CA1AF31000534B2 /* RankingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 475B86E02CA1AF31000534B2 /* RankingView.swift */; }; @@ -154,7 +155,6 @@ 5E847A742CDBD52E0034C2A7 /* CalendarDataInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E847A732CDBD52E0034C2A7 /* CalendarDataInteractor.swift */; }; 5E847A772CDBD5590034C2A7 /* AllStudyRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E847A762CDBD5590034C2A7 /* AllStudyRecord.swift */; }; 5EAE88722CDF9B4500DCBA31 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EAE88712CDF9B4500DCBA31 /* UserProfile.swift */; }; - 5ED93CD72CE2311D004A7931 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5ED93CD62CE2311D004A7931 /* GoogleService-Info.plist */; }; 5ED93CDB2CE3690D004A7931 /* Secrets-prod.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 5ED93CDA2CE3690D004A7931 /* Secrets-prod.xcconfig */; }; 98B5F00A2CDB362C007CF5FA /* Secrets-dev.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 98B5F0092CDB362C007CF5FA /* Secrets-dev.xcconfig */; }; 98B5F00C2CDB6226007CF5FA /* StudyTimeResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B5F00B2CDB6226007CF5FA /* StudyTimeResponseDTO.swift */; }; @@ -334,7 +334,6 @@ 5E847A732CDBD52E0034C2A7 /* CalendarDataInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDataInteractor.swift; sourceTree = ""; }; 5E847A762CDBD5590034C2A7 /* AllStudyRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllStudyRecord.swift; sourceTree = ""; }; 5EAE88712CDF9B4500DCBA31 /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; - 5ED93CD62CE2311D004A7931 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; 5ED93CDA2CE3690D004A7931 /* Secrets-prod.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Secrets-prod.xcconfig"; sourceTree = ""; }; 98B5F0092CDB362C007CF5FA /* Secrets-dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Secrets-dev.xcconfig"; sourceTree = ""; }; 98B5F00B2CDB6226007CF5FA /* StudyTimeResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyTimeResponseDTO.swift; sourceTree = ""; }; @@ -364,6 +363,7 @@ 47F71B592CDC68BC0044DEC5 /* FirebaseFirestoreCombine-Community in Frameworks */, 47F71B532CDC68BC0044DEC5 /* FirebaseAuthCombine-Community in Frameworks */, 47F71B632CDC813A0044DEC5 /* FirebaseFunctionsCombine-Community in Frameworks */, + 47581ED02CF01EB200A2EA31 /* AmplitudeSwift in Frameworks */, 47C8154B2CE231810017EA24 /* SwiftJWT in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1073,6 +1073,7 @@ 47F71B602CDC813A0044DEC5 /* FirebaseFunctions */, 47F71B622CDC813A0044DEC5 /* FirebaseFunctionsCombine-Community */, 47C8154A2CE231810017EA24 /* SwiftJWT */, + 47581ECF2CF01EB200A2EA31 /* AmplitudeSwift */, ); productName = HongikYeolgong2; productReference = 47B1D4A82C9CB1740071B62B /* HongikYeolgong2.app */; @@ -1149,6 +1150,7 @@ packageReferences = ( 47F71B4F2CDC68BC0044DEC5 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 47C815492CE231810017EA24 /* XCRemoteSwiftPackageReference "Swift-JWT" */, + 47581ECE2CF01EB200A2EA31 /* XCRemoteSwiftPackageReference "Amplitude-Swift" */, ); productRefGroup = 47B1D4A92C9CB1740071B62B /* Products */; projectDirPath = ""; @@ -1731,6 +1733,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 47581ECE2CF01EB200A2EA31 /* XCRemoteSwiftPackageReference "Amplitude-Swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/amplitude/Amplitude-Swift"; + requirement = { + branch = main; + kind = branch; + }; + }; 47C815492CE231810017EA24 /* XCRemoteSwiftPackageReference "Swift-JWT" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Kitura/Swift-JWT.git"; @@ -1750,6 +1760,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 47581ECF2CF01EB200A2EA31 /* AmplitudeSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 47581ECE2CF01EB200A2EA31 /* XCRemoteSwiftPackageReference "Amplitude-Swift" */; + productName = AmplitudeSwift; + }; 47C8154A2CE231810017EA24 /* SwiftJWT */ = { isa = XCSwiftPackageProductDependency; package = 47C815492CE231810017EA24 /* XCRemoteSwiftPackageReference "Swift-JWT" */; diff --git a/HongikYeolgong2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/HongikYeolgong2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3602ef9..5a90277 100644 --- a/HongikYeolgong2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/HongikYeolgong2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "37f87f153a7df0e69f7682478910cd31cbff8c536dc79a044414e38ba896c24f", + "originHash" : "2d278dd33b619da8a9340e2076758bc4da9f152353bae8b810225babaa366f5f", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -10,6 +10,24 @@ "version" : "1.2024011602.0" } }, + { + "identity" : "amplitude-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/amplitude/Amplitude-Swift", + "state" : { + "branch" : "main", + "revision" : "4097b2a7a0b8fc786c6feddc598f9f379de997af" + } + }, + { + "identity" : "analytics-connector-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/amplitude/analytics-connector-ios.git", + "state" : { + "revision" : "decb203b5ce0e06091bbc5040acbf24fa85ebdce", + "version" : "1.3.0" + } + }, { "identity" : "app-check", "kind" : "remoteSourceControl", diff --git a/HongikYeolgong2/Domain/Interactors/StudySessionInteractor.swift b/HongikYeolgong2/Domain/Interactors/StudySessionInteractor.swift index b5241d1..b44c34c 100644 --- a/HongikYeolgong2/Domain/Interactors/StudySessionInteractor.swift +++ b/HongikYeolgong2/Domain/Interactors/StudySessionInteractor.swift @@ -24,7 +24,7 @@ final class StudySessionInteractorImpl: StudySessionInteractor { private let cancleBag = CancelBag() private let studySessionRepository: StudySessionRepository private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect() - private let addedTime: TimeInterval = .init(minutes: 31) + private let addedTime: TimeInterval = .init(hours: 6) private var lastTime: Date? private var subscription: AnyCancellable? diff --git a/HongikYeolgong2/Info.plist b/HongikYeolgong2/Info.plist index 0babe6d..63f06c8 100644 --- a/HongikYeolgong2/Info.plist +++ b/HongikYeolgong2/Info.plist @@ -2,6 +2,8 @@ + AmplitudeKey + $(AMPLITUDE_KEY) AppleIdURL $(APPLEID_API_URL) BaseURL diff --git a/HongikYeolgong2/Presentation/Home/HomeView.swift b/HongikYeolgong2/Presentation/Home/HomeView.swift index c2fd223..c6a567f 100644 --- a/HongikYeolgong2/Presentation/Home/HomeView.swift +++ b/HongikYeolgong2/Presentation/Home/HomeView.swift @@ -5,8 +5,10 @@ // Created by 권석기 on 9/23/24. // -import SwiftUI import Combine +import SwiftUI + +import AmplitudeSwift struct HomeView: View { // MARK: - Properties @@ -49,10 +51,10 @@ struct HomeView: View { ActionButtonControllerView( studySession: $studySession, actions: .init( - endButtonTapped: { shouldShowEndUseModal.toggle() }, - startButtonTapped: { shouldShowTimePicker.toggle() }, - seatButtonTapped: { shouldShowWebView.toggle() }, - addButtonTapped: { shouldShowAddTimeModal.toggle() } + endButtonTapped: endButtonTapped, + startButtonTapped: startButtonTapped, + seatButtonTapped: seatButtonTapped, + addButtonTapped: addButtonTapped ) ) @@ -106,6 +108,28 @@ struct HomeView: View { : studySessionInteractor.pauseStudy() } } +} + +// MARK: - Helpers +extension HomeView { + func endButtonTapped() { + shouldShowEndUseModal.toggle() + Amplitude.instance.track(eventType: "StudyEndButton") + } + + func startButtonTapped() { + shouldShowTimePicker.toggle() + Amplitude.instance.track(eventType: "StudyStartButton") + } + + func seatButtonTapped() { + shouldShowWebView.toggle() + } + + func addButtonTapped() { + shouldShowAddTimeModal.toggle() + Amplitude.instance.track(eventType: "StudyExtendButton") + } func retryAction() { weeklyStudyInteractor.getWeekyStudy(studyRecords: $studyRecords) diff --git a/HongikYeolgong2/Presentation/Root/HongikYeolgong2App.swift b/HongikYeolgong2/Presentation/Root/HongikYeolgong2App.swift index 423eeef..cf4784d 100644 --- a/HongikYeolgong2/Presentation/Root/HongikYeolgong2App.swift +++ b/HongikYeolgong2/Presentation/Root/HongikYeolgong2App.swift @@ -6,9 +6,10 @@ // import SwiftUI +import AmplitudeSwift @main -struct HongikYeolgong2App: App { +struct HongikYeolgong2App: App { let enviroment = AppEnviroment.bootstrap() var body: some Scene { @@ -20,3 +21,12 @@ struct HongikYeolgong2App: App { } } } + +extension Amplitude { + static var instance = Amplitude( + configuration: Configuration( + apiKey: SecretKeys.ampliKey, + autocapture: [.sessions, .appLifecycles, .screenViews] + ) + ) +} diff --git a/HongikYeolgong2/Presentation/Root/MainTabView.swift b/HongikYeolgong2/Presentation/Root/MainTabView.swift index 35b5d3a..360e47b 100644 --- a/HongikYeolgong2/Presentation/Root/MainTabView.swift +++ b/HongikYeolgong2/Presentation/Root/MainTabView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import AmplitudeSwift enum Tab: CaseIterable { case home @@ -49,16 +50,28 @@ struct MainTabView: View { TabView(selection: $currentTab, content: { HomeView() - .tag(Tab.home) + .tag(Tab.home) + .onAppear { + Amplitude.instance.track(eventType: "Home") + } RecordView() .tag(Tab.record) + .onAppear { + Amplitude.instance.track(eventType: "Record") + } RankingView() .tag(Tab.ranking) + .onAppear { + Amplitude.instance.track(eventType: "Ranking") + } SettingView() .tag(Tab.setting) + .onAppear { + Amplitude.instance.track(eventType: "Setting") + } }) .overlay(alignment: .bottom) { TabBarView(currentTab: $currentTab) diff --git a/HongikYeolgong2/Presentation/Setting/SettingView.swift b/HongikYeolgong2/Presentation/Setting/SettingView.swift index a231b23..777d782 100644 --- a/HongikYeolgong2/Presentation/Setting/SettingView.swift +++ b/HongikYeolgong2/Presentation/Setting/SettingView.swift @@ -8,6 +8,8 @@ import SwiftUI import Combine +import AmplitudeSwift + struct SettingView: View { @Environment(\.injected.appState) var appState @Environment(\.injected.interactors.userDataInteractor) var userDataInteractor @@ -23,7 +25,7 @@ struct SettingView: View { var body: some View { NetworkStateView( loadables: [AnyLoadable($isLoading)], - retryAction: withDraw + retryAction: retryAction ) { content } @@ -31,53 +33,44 @@ struct SettingView: View { var content: some View { NavigationStack(path: $settingPath) { - VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 19.adjustToScreenWidth) { + Spacer().frame(height: 32.adjustToScreenHeight) + + HStack(spacing: 0) { ProfileImage() - - HStack(spacing: 8.adjustToScreenWidth) { + Spacer().frame(width: 20.adjustToScreenWidth) + HStack(spacing: 0) { ProfileText(text: userProfile.nickname) - - + Spacer().frame(width: 8.adjustToScreenWidth) ProfileText(text: "|", textColor: Color.gray400) - - + Spacer().frame(width: 8.adjustToScreenWidth) ProfileText(text: userProfile.department) } } - VStack(spacing: 20.adjustToScreenHeight) { - MenuItem(title: "공지사항") { - settingPath.append(.webView(title: "공지사항", url: SecretKeys.noticeUrl)) - } content: { - Image(.arrowRight) - } - - MenuItem(title: "문의사항") { - settingPath.append(.webView(title: "문의사항", url: SecretKeys.qnaUrl)) - } content: { - Image(.arrowRight) - } - - MenuItem(title: "열람실 종료 시간 알림") {} - content: { - Toggle("", isOn: Binding( - get: { isOnAlarm }, - set: { _ in userPermissionsInteractor.handleNotificationPermissions() } - )) - .toggleStyle(SwitchToggleStyle(tint: Color.blue100)) - .fixedSize() - .padding(.trailing, 3) - } - } - .padding(.top, 20.adjustToScreenHeight) + Spacer().frame(height: 20.adjustToScreenHeight) + MenuItem(title: "공지사항", + onTap: navigateToNotice, + content: { Image(.arrowRight) }) + + Spacer().frame(height: 20.adjustToScreenHeight) + MenuItem(title: "문의사항", + onTap: navigateToInquiry, + content: { Image(.arrowRight) }) + + Spacer().frame(height: 20.adjustToScreenHeight) + MenuItem(title: "열람실 종료 시간 알림", + content: { Toggle("", isOn: Binding( + get: { isOnAlarm }, + set: { _ in userPermissionsInteractor.handleNotificationPermissions() } + )) + .toggleStyle(SwitchToggleStyle(tint: Color.blue100)) + .fixedSize() + .padding(.trailing, 3) }) + Spacer().frame(height: 10.adjustToScreenHeight) InfomationView() - .padding(.top, 10.adjustToScreenHeight) - } - .padding(.top, 32.adjustToScreenHeight) - + Spacer() HStack(spacing: 0) { @@ -87,10 +80,9 @@ struct SettingView: View { } label: { ProfileText(text: "로그아웃", textColor: Color.gray300) } - + Spacer().frame(width: 24.adjustToScreenWidth) ProfileText(text: "|", textColor: Color.gray400) - .padding(.horizontal, 24.adjustToScreenWidth) - + Spacer().frame(width: 24.adjustToScreenWidth) Button { shouldShowWithdrawModal.toggle() } label: { @@ -98,7 +90,8 @@ struct SettingView: View { } Spacer() } - .padding(.bottom, 36.adjustToScreenHeight) + + Spacer().frame(height: 36.adjustToScreenHeight) } .padding(.horizontal, 32.adjustToScreenWidth) .modifier(IOSBackground()) @@ -108,7 +101,7 @@ struct SettingView: View { confirmButtonText: "돌아가기", cancleButtonText: "탈퇴하기", confirmAction: {}, - cancleAction: { userDataInteractor.withdraw(isLoading: $isLoading) }) + cancleAction: withdrawButtonTapped) } .systemOverlay(isPresented: $shouldShowLogoutModal) { ModalView(isPresented: $shouldShowLogoutModal, @@ -116,12 +109,12 @@ struct SettingView: View { confirmButtonText: "돌아가기", cancleButtonText: "로그아웃하기", confirmAction: {}, - cancleAction: { userDataInteractor.logout() }) + cancleAction: logoutButtonTapped) } .onReceive(isOnAlarmUpdated) { isOnAlarm = $0 } - .onReceive(isSceneActive) { + .onReceive(isSceneActive) { userPermissionsInteractor.resolveStatus(for: .localNotifications) } .onReceive(userProfileUpdated) { @@ -137,9 +130,30 @@ struct SettingView: View { } } } +} + +// MARK: - Helpers +extension SettingView { + func retryAction() { + userDataInteractor.withdraw(isLoading: $isLoading) + } - func withDraw() { + func withdrawButtonTapped() { userDataInteractor.withdraw(isLoading: $isLoading) + Amplitude.instance.track(eventType: "WithdrawButton") + } + + func logoutButtonTapped() { + userDataInteractor.logout() + Amplitude.instance.track(eventType: "LogoutButton") + } + + func navigateToNotice() { + settingPath.append(.webView(title: "공지사항", url: SecretKeys.noticeUrl)) + } + + func navigateToInquiry() { + settingPath.append(.webView(title: "문의사항", url: SecretKeys.qnaUrl)) } } diff --git a/HongikYeolgong2/Util/Constants.swift b/HongikYeolgong2/Util/Constants.swift index de34446..a18b706 100644 --- a/HongikYeolgong2/Util/Constants.swift +++ b/HongikYeolgong2/Util/Constants.swift @@ -16,6 +16,7 @@ struct SecretKeys { static let bundleName = Bundle.main.bundleIdentifier ?? "" static let teamID = Bundle.main.infoDictionary?["TeamID"] as? String ?? "" static let serviceID = Bundle.main.infoDictionary?["ServiceID"] as? String ?? "" + static let ampliKey = Bundle.main.infoDictionary?["AmplitudeKey"] as? String ?? "" }