diff --git a/.github/workflows/ios.yaml b/.github/workflows/ios.yaml index 7953ab1..1bc76d7 100644 --- a/.github/workflows/ios.yaml +++ b/.github/workflows/ios.yaml @@ -41,8 +41,13 @@ jobs: env: APPCONFIG: ${{ secrets.XCCONFIG }} run: | - mkdir XCConfig + mkdir -p XCConfig echo $APPCONFIG | base64 --decode > ./XCConfig/Shared.xcconfig + - name: Create GoogleService-Info.plist + env: + GOOGLE_SERVICE_INFO_BASE64: ${{ secrets.GOOGLE_INFO_PLIST }} + run: | + echo $GOOGLE_SERVICE_INFO_BASE64 | base64 --decode > ./PyeonHaeng-iOS/Resources/GoogleService-Info.plist - name: Set Default Scheme run: | scheme_list=$(xcodebuild -list -json | tr -d "\n") diff --git a/.gitignore b/.gitignore index e04264a..fb5179d 100644 --- a/.gitignore +++ b/.gitignore @@ -137,5 +137,10 @@ xcuserdata /*.gcno **/xcshareddata/WorkspaceSettings.xcsettings -XCConfig/* + # End of https://www.toptal.com/developers/gitignore/api/macos,xcode,swift,swiftpackagemanager + +### Custom ### +XCConfig/* +GoogleService-Info.plist + diff --git a/PyeonHaeng-iOS.xcodeproj/project.pbxproj b/PyeonHaeng-iOS.xcodeproj/project.pbxproj index 2dbd36e..e32c4d6 100644 --- a/PyeonHaeng-iOS.xcodeproj/project.pbxproj +++ b/PyeonHaeng-iOS.xcodeproj/project.pbxproj @@ -35,6 +35,9 @@ BA4EA3602B6A37E10003DCE7 /* Entity in Frameworks */ = {isa = PBXBuildFile; productRef = BA4EA35F2B6A37E10003DCE7 /* Entity */; }; BA4FB9C52BA1BC5B00ED03C4 /* NoticeAPI in Frameworks */ = {isa = PBXBuildFile; productRef = BA4FB9C42BA1BC5B00ED03C4 /* NoticeAPI */; }; BA4FB9C72BA1BC5B00ED03C4 /* NoticeAPISupport in Frameworks */ = {isa = PBXBuildFile; productRef = BA4FB9C62BA1BC5B00ED03C4 /* NoticeAPISupport */; }; + BA73DDC42BC17912009EE718 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = BA73DDC32BC17912009EE718 /* GoogleService-Info.plist */; }; + BA73DDC72BC179F6009EE718 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = BA73DDC62BC179F6009EE718 /* FirebaseAnalytics */; }; + BA73DDCB2BC179F6009EE718 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = BA73DDCA2BC179F6009EE718 /* FirebaseCrashlytics */; }; BA849A342B8F4F36004495BF /* ProductConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8E83232B8EF83B00FE968C /* ProductConfiguration.swift */; }; BA849A372B8F4FC0004495BF /* MockPaginatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA849A362B8F4FC0004495BF /* MockPaginatable.swift */; }; BA8E83242B8EF83B00FE968C /* ProductConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8E83232B8EF83B00FE968C /* ProductConfiguration.swift */; }; @@ -124,6 +127,7 @@ BA28F1A72B6157E90052855E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BA402F7D2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvenienceSelectBottomSheetView.swift; sourceTree = ""; }; BA4EA35A2B6A00F70003DCE7 /* Entity */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Entity; sourceTree = ""; }; + BA73DDC32BC17912009EE718 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; BA849A362B8F4FC0004495BF /* MockPaginatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPaginatable.swift; sourceTree = ""; }; BA8E83232B8EF83B00FE968C /* ProductConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductConfiguration.swift; sourceTree = ""; }; BA9903552BBBD9F500DC3ED3 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; @@ -185,9 +189,11 @@ BAB6212E2BB13CD100877FC3 /* NetworkAPIKit in Frameworks */, BA0623D82B68E51400A0A3B2 /* Log in Frameworks */, BA0564092B6248EA003D6DC7 /* HomeAPI in Frameworks */, + BA73DDC72BC179F6009EE718 /* FirebaseAnalytics in Frameworks */, BAE5ADB02BAD61D000176ADE /* CreditsAPI in Frameworks */, BAB569612B639F3000D1E0F9 /* DesignSystem in Frameworks */, BAE5EDB42BB1450100FC4B78 /* NetworkMonitor in Frameworks */, + BA73DDCB2BC179F6009EE718 /* FirebaseCrashlytics in Frameworks */, E55DD5182B9370D100AA63C0 /* SearchAPI in Frameworks */, E55DD51A2B9370D100AA63C0 /* SearchAPISupport in Frameworks */, ); @@ -254,6 +260,7 @@ BA28F1702B6152E90052855E /* Resources */ = { isa = PBXGroup; children = ( + BA73DDC32BC17912009EE718 /* GoogleService-Info.plist */, BA28F1A72B6157E90052855E /* Info.plist */, BAA4D9B02B5A1796005999F8 /* Assets.xcassets */, BA9903552BBBD9F500DC3ED3 /* InfoPlist.xcstrings */, @@ -560,6 +567,8 @@ BAE5ADB12BAD61D000176ADE /* CreditsAPISupport */, BAB6212D2BB13CD100877FC3 /* NetworkAPIKit */, BAE5EDB32BB1450100FC4B78 /* NetworkMonitor */, + BA73DDC62BC179F6009EE718 /* FirebaseAnalytics */, + BA73DDCA2BC179F6009EE718 /* FirebaseCrashlytics */, ); productName = "PyeonHaeng-iOS"; productReference = BAA4D9A92B5A1795005999F8 /* PyeonHaeng-iOS.app */; @@ -618,6 +627,9 @@ ja, ); mainGroup = BAA4D9A02B5A1795005999F8; + packageReferences = ( + BA73DDC52BC179F6009EE718 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + ); productRefGroup = BAA4D9AA2B5A1795005999F8 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -650,6 +662,7 @@ BA28F1A12B61572A0052855E /* Pretendard-Thin.otf in Resources */, BA28F1A62B61572A0052855E /* Pretendard-Black.otf in Resources */, BA28F1A52B61572A0052855E /* Pretendard-Light.otf in Resources */, + BA73DDC42BC17912009EE718 /* GoogleService-Info.plist in Resources */, BA28F1A42B61572A0052855E /* Pretendard-ExtraBold.otf in Resources */, BA28F1A02B61572A0052855E /* Pretendard-Regular.otf in Resources */, BAA4D9B12B5A1796005999F8 /* Assets.xcassets in Resources */, @@ -1267,6 +1280,17 @@ }; /* End XCConfigurationList section */ +/* Begin XCRemoteSwiftPackageReference section */ + BA73DDC52BC179F6009EE718 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 10.23.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ BA0564082B6248EA003D6DC7 /* HomeAPI */ = { isa = XCSwiftPackageProductDependency; @@ -1292,6 +1316,16 @@ isa = XCSwiftPackageProductDependency; productName = NoticeAPISupport; }; + BA73DDC62BC179F6009EE718 /* FirebaseAnalytics */ = { + isa = XCSwiftPackageProductDependency; + package = BA73DDC52BC179F6009EE718 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalytics; + }; + BA73DDCA2BC179F6009EE718 /* FirebaseCrashlytics */ = { + isa = XCSwiftPackageProductDependency; + package = BA73DDC52BC179F6009EE718 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCrashlytics; + }; BAB569602B639F3000D1E0F9 /* DesignSystem */ = { isa = XCSwiftPackageProductDependency; productName = DesignSystem; diff --git a/PyeonHaeng-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PyeonHaeng-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..8bee099 --- /dev/null +++ b/PyeonHaeng-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,122 @@ +{ + "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "748c7837511d0e6a507737353af268484e1745e2", + "version" : "1.2024011601.1" + } + }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "c218c2054299b15ae577e818bbba16084d3eabe6", + "version" : "10.18.2" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "revision" : "888f0b6026e2441a69e3ee2ad5293c7a92031e62", + "version" : "10.23.1" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "c7a5917ebe48d69f421aadf154ef3969c8b7f12d", + "version" : "10.23.1" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", + "version" : "9.4.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "26c898aed8bed13b8a63057ee26500abbbcb8d55", + "version" : "7.13.1" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359", + "version" : "1.62.2" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "9534039303015a84837090d20fa21cae6e5eadb6", + "version" : "3.3.2" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", + "version" : "100.0.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "43aaef65e0c665daadf848761d560e446d350d3d", + "version" : "1.22.4" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", + "version" : "2.30910.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", + "version" : "2.4.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb", + "version" : "1.26.0" + } + } + ], + "version" : 2 +} diff --git a/PyeonHaeng-iOS.xcodeproj/xcshareddata/xcschemes/PyeonHaeng-iOS.xcscheme b/PyeonHaeng-iOS.xcodeproj/xcshareddata/xcschemes/PyeonHaeng-iOS.xcscheme index 54d05ed..ce61faf 100644 --- a/PyeonHaeng-iOS.xcodeproj/xcshareddata/xcschemes/PyeonHaeng-iOS.xcscheme +++ b/PyeonHaeng-iOS.xcodeproj/xcshareddata/xcschemes/PyeonHaeng-iOS.xcscheme @@ -62,6 +62,12 @@ ReferencedContainer = "container:PyeonHaeng-iOS.xcodeproj"> + + + + diff --git a/PyeonHaeng-iOS/Sources/PyeonHaengApp.swift b/PyeonHaeng-iOS/Sources/PyeonHaengApp.swift index 4b30574..fe63b57 100644 --- a/PyeonHaeng-iOS/Sources/PyeonHaengApp.swift +++ b/PyeonHaeng-iOS/Sources/PyeonHaengApp.swift @@ -6,6 +6,7 @@ // import DesignSystem +import FirebaseCore import NetworkMonitor import SwiftUI @@ -17,6 +18,7 @@ struct PyeonHaengApp: App { init() { FontRegistrar.registerFonts() // 앱을 실행하기 전에 폰트를 로드합니다. setupNavigationBarAppearance() + FirebaseApp.configure() } var body: some Scene { diff --git a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/ConvenienceSelectBottomSheetView.swift b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/ConvenienceSelectBottomSheetView.swift index a86c3b2..4f72708 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/ConvenienceSelectBottomSheetView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/ConvenienceSelectBottomSheetView.swift @@ -7,6 +7,7 @@ import DesignSystem import Entity +import FirebaseAnalytics import SwiftUI // MARK: - ConvenienceSelectBottomSheetView @@ -24,6 +25,7 @@ struct ConvenienceSelectBottomSheetView: View where ViewModel: HomeVi ForEach(ConvenienceStore.allCases, id: \.self) { store in Button { + Analytics.logEvent("change_conveniencestore", parameters: ["promotion": store.rawValue]) viewModel.trigger(.changeConvenienceStore(store)) dismiss() } label: { diff --git a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/PromotionSelectBottomSheetView.swift b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/PromotionSelectBottomSheetView.swift index 5e7785b..84d58d1 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/PromotionSelectBottomSheetView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/BottomSheet/PromotionSelectBottomSheetView.swift @@ -7,6 +7,7 @@ import DesignSystem import Entity +import FirebaseAnalytics import SwiftUI // MARK: - PromotionSelectBottomSheetView @@ -41,6 +42,7 @@ struct PromotionSelectBottomSheetView: View where ViewModel: HomeView private var promotionButtons: some View { ForEach(Promotion.allCases, id: \.self) { promotion in Button { + Analytics.logEvent("change_promotion", parameters: ["promotion": promotion.rawValue]) viewModel.changePromotion(to: promotion) dismiss() } label: { diff --git a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeProductDetailSelectionView.swift b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeProductDetailSelectionView.swift index 2216c72..6a376ee 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeProductDetailSelectionView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeProductDetailSelectionView.swift @@ -12,8 +12,8 @@ import SwiftUI struct HomeProductDetailSelectionView: View where ViewModel: HomeViewModelRepresentable { @EnvironmentObject private var viewModel: ViewModel - @State private var convenienceStoreModalPresented: Bool = false - @State private var promotionModalPresented: Bool = false + @State private var convenienceStoreModalPresented = false + @State private var promotionModalPresented = false var body: some View { HStack { diff --git a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeView.swift b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeView.swift index 9745069..6cec671 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/HomeScene/View/HomeView.swift @@ -6,6 +6,7 @@ // import DesignSystem +import FirebaseAnalytics import SwiftUI // MARK: - HomeView @@ -13,7 +14,7 @@ import SwiftUI struct HomeView: View where ViewModel: HomeViewModelRepresentable { @StateObject private var viewModel: ViewModel @State private var isOnboardingSheetOpen = false - @AppStorage("isFirstLaunch") private var isFirstLaunch: Bool = false + @AppStorage("isFirstLaunch") private var isFirstLaunch = false @Environment(\.injected) private var container init(viewModel: @autoclosure @escaping () -> ViewModel) { @@ -76,6 +77,7 @@ struct HomeView: View where ViewModel: HomeViewModelRepresentable { } } } + .analyticsScreen(name: "main_countent", class: "\(Self.self)") .tint(.accent) .fullScreenCover(isPresented: $isOnboardingSheetOpen) { OnboardingView() diff --git a/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoLineGraphView.swift b/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoLineGraphView.swift index f39e2f4..cee5765 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoLineGraphView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoLineGraphView.swift @@ -16,7 +16,7 @@ struct ProductInfoLineGraphView: View where ViewModel: ProductInfoVie @EnvironmentObject private var viewModel: ViewModel @State private var offset: CGSize = .zero - @State private var index: Int = 0 + @State private var index = 0 @State private var frameSize: CGSize = .zero @State private var symbolLocations: [CGPoint] = [] diff --git a/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoView.swift b/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoView.swift index 5800fdb..b3acd10 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/ProductInfoScene/ProductInfoView.swift @@ -6,6 +6,7 @@ // import DesignSystem +import FirebaseAnalytics import SwiftUI // MARK: - ProductInfoView @@ -37,5 +38,10 @@ struct ProductInfoView: View where ViewModel: ProductInfoViewModelRep .onAppear { viewModel.trigger(.fetchProduct) } + .analyticsScreen( + name: "product_info_content", + class: "\(Self.self)", + extraParameters: ["product": viewModel.state.product.name] + ) } } diff --git a/PyeonHaeng-iOS/Sources/Scenes/ProductSearchScene/SearchView.swift b/PyeonHaeng-iOS/Sources/Scenes/ProductSearchScene/SearchView.swift index f44e1dc..4747166 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/ProductSearchScene/SearchView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/ProductSearchScene/SearchView.swift @@ -7,13 +7,14 @@ import DesignSystem import Entity +import FirebaseAnalytics import SwiftUI // MARK: - SearchView struct SearchView: View where ViewModel: SearchViewModelRepresentable { @StateObject private var viewModel: ViewModel - @State private var text: String = "" + @State private var text = "" @Environment(\.dismiss) private var dismiss @Environment(\.injected) private var container @@ -79,6 +80,7 @@ struct SearchView: View where ViewModel: SearchViewModelRepresentable } } } + .analyticsScreen(name: "search_content", class: "\(Self.self)") .toolbar { ToolbarItem(placement: .principal) { SearchTextField(text: $text) @@ -111,7 +113,10 @@ private struct SearchTextField: View where ViewModel: SearchViewModel lineWidth: Metrics.textFieldBorderWidth ) } - .onSubmit { viewModel.trigger(.textChanged(text)) } + .onSubmit { + Analytics.logEvent("search_product", parameters: ["product": text]) + viewModel.trigger(.textChanged(text)) + } if !text.isEmpty { Button { text = "" diff --git a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift index 7f2d17a..3acbac6 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift @@ -6,6 +6,7 @@ // import DesignSystem +import FirebaseAnalytics import SwiftUI // MARK: - SettingsItem @@ -35,6 +36,7 @@ struct SettingsView: View { .scrollDisabled(true) .navigationTitle("설정") .navigationBarTitleDisplayMode(.inline) + .analyticsScreen(name: "settings_content", class: "\(Self.self)") } }