diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..af40b77 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: Build + +on: + pull_request: + branches: [ main, develop ] + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_ios_app: + name: Build iOS App + runs-on: macos-latest + env: + TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Select Xcode Version + run: sudo xcode-select -switch /Applications/Xcode.app + + - name: Clean Build Folder + run: | + xcodebuild clean \ + -workspace TorqueNet.xcodeproj/project.xcworkspace \ + -scheme TorqueNet \ + -configuration Debug + + - name: Build iOS App (Debug) + run: | + xcodebuild \ + -workspace TorqueNet.xcodeproj/project.xcworkspace \ + -scheme TorqueNet \ + -sdk iphonesimulator \ + -configuration Debug \ + -destination "platform=iOS Simulator,name=iPhone 15" \ + build \ + | xcpretty diff --git a/TorqueNet.xcodeproj/project.pbxproj b/TorqueNet.xcodeproj/project.pbxproj index 0857d2f..eb26b42 100644 --- a/TorqueNet.xcodeproj/project.pbxproj +++ b/TorqueNet.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 0477CF9C2E40BCFC00A9DB5F /* Exo2-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0477CF8A2E40BCFC00A9DB5F /* Exo2-ExtraLight.ttf */; }; 0477D0832E40EBCB00A9DB5F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0477D0822E40EBCB00A9DB5F /* GoogleService-Info.plist */; }; 0477D0912E40EDB400A9DB5F /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 0477D0902E40EDB400A9DB5F /* Lottie */; }; + 04C07A602ED5AF6400969093 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 04C07A5F2ED5AF6400969093 /* Kingfisher */; }; 04C7F2C42EABBABF007C148C /* FirebaseAI in Frameworks */ = {isa = PBXBuildFile; productRef = 04C7F2C32EABBABF007C148C /* FirebaseAI */; }; 04C7F2C62EABBABF007C148C /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 04C7F2C52EABBABF007C148C /* FirebaseAnalytics */; }; 04C7F2C82EABBABF007C148C /* FirebaseAnalyticsCore in Frameworks */ = {isa = PBXBuildFile; productRef = 04C7F2C72EABBABF007C148C /* FirebaseAnalyticsCore */; }; @@ -125,6 +126,7 @@ 04C7F2EC2EABBABF007C148C /* FirebaseStorage in Frameworks */, 04C7F2C82EABBABF007C148C /* FirebaseAnalyticsCore in Frameworks */, 04C7F2C42EABBABF007C148C /* FirebaseAI in Frameworks */, + 04C07A602ED5AF6400969093 /* Kingfisher in Frameworks */, 04C7F2CA2EABBABF007C148C /* FirebaseAnalyticsIdentitySupport in Frameworks */, 04C7F2D22EABBABF007C148C /* FirebaseAuthCombine-Community in Frameworks */, 323D74D82E958A8200EC7543 /* GoogleSignInSwift in Frameworks */, @@ -239,6 +241,7 @@ 04C7F2E92EABBABF007C148C /* FirebaseRemoteConfig */, 04C7F2EB2EABBABF007C148C /* FirebaseStorage */, 04C7F2ED2EABBABF007C148C /* FirebaseStorageCombine-Community */, + 04C07A5F2ED5AF6400969093 /* Kingfisher */, ); productName = TorqueNet; productReference = 04356D5F2E40B85B00D1C0A6 /* TorqueNet.app */; @@ -326,6 +329,7 @@ 0477D08F2E40EDB400A9DB5F /* XCRemoteSwiftPackageReference "lottie-spm" */, 323D74D42E958A8200EC7543 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, 04C7F2C22EABBABF007C148C /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + 04C07A5E2ED5AF6400969093 /* XCRemoteSwiftPackageReference "Kingfisher" */, ); preferredProjectObjectVersion = 77; productRefGroup = 04356D602E40B85B00D1C0A6 /* Products */; @@ -720,6 +724,14 @@ minimumVersion = 4.5.2; }; }; + 04C07A5E2ED5AF6400969093 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.6.2; + }; + }; 04C7F2C22EABBABF007C148C /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; @@ -744,6 +756,11 @@ package = 0477D08F2E40EDB400A9DB5F /* XCRemoteSwiftPackageReference "lottie-spm" */; productName = Lottie; }; + 04C07A5F2ED5AF6400969093 /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 04C07A5E2ED5AF6400969093 /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; 04C7F2C32EABBABF007C148C /* FirebaseAI */ = { isa = XCSwiftPackageProductDependency; package = 04C7F2C22EABBABF007C148C /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; diff --git a/TorqueNet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TorqueNet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 66f4ca9..095f369 100644 --- a/TorqueNet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/TorqueNet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "b098d4a527cba476e21434d4024ce329ced8572a87fa24fe01e88d7f685e6d36", + "originHash" : "b0d262d96714b2a8143ffd555d0362f1e8fb53a7cca92b142c5c373d6132d9b2", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -118,6 +118,15 @@ "version" : "101.0.0" } }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher", + "state" : { + "revision" : "d30a5fad881137e2267f96a8e3fc35c58999bb94", + "version" : "8.6.2" + } + }, { "identity" : "leveldb", "kind" : "remoteSourceControl", diff --git a/TorqueNet.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate b/TorqueNet.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate index a3b3dfa..4745f19 100644 Binary files a/TorqueNet.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate and b/TorqueNet.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TorqueNet/Assets.xcassets/audi.imageset/Contents.json b/TorqueNet/Assets.xcassets/audi.imageset/Contents.json new file mode 100644 index 0000000..49e43ae --- /dev/null +++ b/TorqueNet/Assets.xcassets/audi.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "audi.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TorqueNet/Assets.xcassets/audi.imageset/audi.png b/TorqueNet/Assets.xcassets/audi.imageset/audi.png new file mode 100644 index 0000000..dffc290 Binary files /dev/null and b/TorqueNet/Assets.xcassets/audi.imageset/audi.png differ diff --git a/TorqueNet/Assets.xcassets/bmw.imageset/Contents.json b/TorqueNet/Assets.xcassets/bmw.imageset/Contents.json new file mode 100644 index 0000000..150d6b9 --- /dev/null +++ b/TorqueNet/Assets.xcassets/bmw.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bmw.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TorqueNet/Assets.xcassets/bmw.imageset/bmw.png b/TorqueNet/Assets.xcassets/bmw.imageset/bmw.png new file mode 100644 index 0000000..0c75a72 Binary files /dev/null and b/TorqueNet/Assets.xcassets/bmw.imageset/bmw.png differ diff --git a/TorqueNet/Assets.xcassets/porsche.imageset/Contents.json b/TorqueNet/Assets.xcassets/porsche.imageset/Contents.json new file mode 100644 index 0000000..e66dbc8 --- /dev/null +++ b/TorqueNet/Assets.xcassets/porsche.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "porsche.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TorqueNet/Assets.xcassets/porsche.imageset/porsche.png b/TorqueNet/Assets.xcassets/porsche.imageset/porsche.png new file mode 100644 index 0000000..056b9f4 Binary files /dev/null and b/TorqueNet/Assets.xcassets/porsche.imageset/porsche.png differ diff --git a/TorqueNet/Assets.xcassets/tesla.imageset/Contents.json b/TorqueNet/Assets.xcassets/tesla.imageset/Contents.json new file mode 100644 index 0000000..54a6925 --- /dev/null +++ b/TorqueNet/Assets.xcassets/tesla.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "tesla.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TorqueNet/Assets.xcassets/tesla.imageset/tesla.png b/TorqueNet/Assets.xcassets/tesla.imageset/tesla.png new file mode 100644 index 0000000..90d2831 Binary files /dev/null and b/TorqueNet/Assets.xcassets/tesla.imageset/tesla.png differ diff --git a/TorqueNet/Assets.xcassets/toyota.imageset/Contents.json b/TorqueNet/Assets.xcassets/toyota.imageset/Contents.json new file mode 100644 index 0000000..c53b219 --- /dev/null +++ b/TorqueNet/Assets.xcassets/toyota.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "toyota.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TorqueNet/Assets.xcassets/toyota.imageset/toyota.png b/TorqueNet/Assets.xcassets/toyota.imageset/toyota.png new file mode 100644 index 0000000..4de258b Binary files /dev/null and b/TorqueNet/Assets.xcassets/toyota.imageset/toyota.png differ diff --git a/TorqueNet/Core/Component/Browser/SafariView.swift b/TorqueNet/Core/Component/Browser/SafariView.swift new file mode 100644 index 0000000..4f0ef82 --- /dev/null +++ b/TorqueNet/Core/Component/Browser/SafariView.swift @@ -0,0 +1,22 @@ +// +// SafariView.swift +// TorqueNet +// +// Created by MAC on 25/11/2025. +// + +import Foundation +import SwiftUI +import SafariServices + +struct SafariView: UIViewControllerRepresentable { + let url: URL + + func makeUIViewController(context: Context) -> SFSafariViewController { + return SFSafariViewController(url: url) + } + + func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) { + // Update the view controller if needed + } +} diff --git a/TorqueNet/Core/Component/ImageView/CustomImageView.swift b/TorqueNet/Core/Component/ImageView/CustomImageView.swift index 237db76..87d1e10 100644 --- a/TorqueNet/Core/Component/ImageView/CustomImageView.swift +++ b/TorqueNet/Core/Component/ImageView/CustomImageView.swift @@ -7,6 +7,7 @@ import SwiftUI +import Kingfisher struct CustomImageView: View { let url: String @@ -14,29 +15,41 @@ struct CustomImageView: View { var height: CGFloat = 256 var body: some View { - AsyncImage(url: URL(string: url)) { phase in - switch phase { - case .failure: - Image(systemName: "photo") - //.font(.largeTitle) - .resizable() - .scaledToFill() - .foregroundColor(Color.theme.surfaceColor) + KFImage(URL(string: url)) + .placeholder { + ProgressView() + } + .onFailure { _ in + print("Image failed to load: \(url)") + } + .resizable() + .scaledToFill() + .frame(maxWidth: maxWidth) + .frame(height: height) + .background(Color.theme.surfaceColor) + .clipShape(RoundedRectangle(cornerRadius: 25)) + .overlay( + KFOverlay(url: url) + ) + } +} - case .success(let image): - image - .resizable() - .scaledToFill() - default: - ProgressView() - } - } - .frame(maxWidth: maxWidth) - .frame( height: height) - //.clipShape(.rect(cornerRadius: 25)) - } +struct KFOverlay: View { + let url: String + var body: some View { + KFImage(URL(string: url)) + .resizable() + .scaledToFit() + .opacity(0) + .overlay( + Image(systemName: "profile") + .foregroundColor(.gray) + .opacity(0.7) + ) + } } + #Preview { CustomImageView(url: "https://hws.dev/paul3.jpg") } diff --git a/TorqueNet/Core/Local/TorqueNetCoreDataModel.xcdatamodeld/TorqueNetCoreDataModel.xcdatamodel/contents b/TorqueNet/Core/Local/TorqueNetCoreDataModel.xcdatamodeld/TorqueNetCoreDataModel.xcdatamodel/contents index 101bbcf..8f465e4 100644 --- a/TorqueNet/Core/Local/TorqueNetCoreDataModel.xcdatamodeld/TorqueNetCoreDataModel.xcdatamodel/contents +++ b/TorqueNet/Core/Local/TorqueNetCoreDataModel.xcdatamodeld/TorqueNetCoreDataModel.xcdatamodel/contents @@ -1,12 +1,14 @@ + + \ No newline at end of file diff --git a/TorqueNet/Features/Home/Presentation/HomeView.swift b/TorqueNet/Features/Home/Presentation/HomeView.swift index d6d2423..d6d0791 100644 --- a/TorqueNet/Features/Home/Presentation/HomeView.swift +++ b/TorqueNet/Features/Home/Presentation/HomeView.swift @@ -20,11 +20,11 @@ struct HomeView: View { var brands: [Brand] = [ Brand(image: "benz", title: "Mercedes-Benz"), - Brand(image: "benz", title: "Tesla"), - Brand(image: "benz", title: "Audi"), - Brand(image: "benz", title: "BMW"), - Brand(image: "benz", title: "Porsche"), - Brand(image: "benz", title: "Toyota") + Brand(image: "tesla", title: "Tesla"), + Brand(image: "audi", title: "Audi"), + Brand(image: "bmw", title: "BMW"), + Brand(image: "porsche", title: "Porsche"), + Brand(image: "toyota", title: "Toyota") ] var featuredCars: [FeaturedCar] = [ @@ -484,7 +484,7 @@ struct EnhancedBrandView: View { VStack(spacing: 8) { ZStack { RoundedRectangle(cornerRadius: 16) - .fill(isSelected ? Color.theme.primaryColor.opacity(0.1) : Color.theme.surfaceColor) + .fill(Color.theme.surfaceColor) .frame(width: 80, height: 80) .shadow(color: Color.theme.onSurfaceColor.opacity(0.08), radius: 12, x: 0, y: 4) .overlay( @@ -494,8 +494,9 @@ struct EnhancedBrandView: View { Image(brand.image) .resizable() - .scaledToFit() + .scaledToFill() .frame(width: 40, height: 40) + //.clipShape(RoundedRectangle(cornerRadius: 16)) } Text(brand.title) diff --git a/TorqueNet/Features/Settings/Data/Mapper/UserEntityExtension.swift b/TorqueNet/Features/Settings/Data/Mapper/UserEntityExtension.swift index 18d66d9..f1d7053 100644 --- a/TorqueNet/Features/Settings/Data/Mapper/UserEntityExtension.swift +++ b/TorqueNet/Features/Settings/Data/Mapper/UserEntityExtension.swift @@ -12,7 +12,9 @@ extension UserEntity { email: self.email ?? "", phoneNumber: self.phoneNumber ?? "", profileImageUrl: self.profileImageUrl, - isSeller: self.isSeller + isSeller: self.isSeller, + cars: Int(self.cars ?? "0"), + rides: Int(self.rides ?? "0") ) } @@ -23,5 +25,7 @@ extension UserEntity { self.phoneNumber = user.phoneNumber self.profileImageUrl = user.profileImageUrl self.isSeller = user.isSeller ?? false + self.cars = user.cars?.description ?? "0" + self.rides = user.rides?.description ?? "0" } } diff --git a/TorqueNet/Features/Settings/Domain/Model/User.swift b/TorqueNet/Features/Settings/Domain/Model/User.swift index 312bf95..64eca02 100644 --- a/TorqueNet/Features/Settings/Domain/Model/User.swift +++ b/TorqueNet/Features/Settings/Domain/Model/User.swift @@ -16,8 +16,8 @@ struct User: Codable, Identifiable, Hashable { var phoneNumber: String? var profileImageUrl: String? var isSeller: Bool? - var cars:Int - var rides:Int + var cars:Int? + var rides:Int? @ServerTimestamp var createdAt: Date? var initials: String { diff --git a/TorqueNet/Features/Settings/Presentation/ProfileView.swift b/TorqueNet/Features/Settings/Presentation/ProfileView.swift index 909787b..f96d523 100644 --- a/TorqueNet/Features/Settings/Presentation/ProfileView.swift +++ b/TorqueNet/Features/Settings/Presentation/ProfileView.swift @@ -155,9 +155,9 @@ struct ProfileView: View { .padding(.horizontal, 20) HStack(spacing: 12) { - StatCard(title: "Total Cars", value: currentUser?.cars.description ?? "0", icon: "car.fill") - StatCard(title: "Rides", value: currentUser?.rides.description ?? "0", icon: "road.lanes") - StatCard(title: "Seller", value: currentUser?.isSeller == true ? "Yes" : "No", icon: "star.fill") + StatCard(title: "Total Cars", value: settingsViewModel.currentUser?.cars?.description ?? "0", icon: "car.fill") + StatCard(title: "Rides", value: settingsViewModel.currentUser?.rides?.description ?? "0", icon: "road.lanes") + StatCard(title: "Seller", value: settingsViewModel.currentUser?.isSeller == true ? "Yes" : "No", icon: "star.fill") } .padding(.horizontal, 16) }