From 8b7f4dd469a9ff14796b9a5ed9854e7924985598 Mon Sep 17 00:00:00 2001 From: Hidde van der Ploeg Date: Sun, 29 Oct 2023 11:20:42 +0000 Subject: [PATCH 1/2] Added Logging --- Sources/Billboard/BillboardViewModel.swift | 62 ++++++++++++++++---- Sources/Billboard/Utilities/Logger+Ext.swift | 16 +++++ 2 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 Sources/Billboard/Utilities/Logger+Ext.swift diff --git a/Sources/Billboard/BillboardViewModel.swift b/Sources/Billboard/BillboardViewModel.swift index 38ef1f9..2c8bdb4 100644 --- a/Sources/Billboard/BillboardViewModel.swift +++ b/Sources/Billboard/BillboardViewModel.swift @@ -6,6 +6,7 @@ // import Foundation +import OSLog public final class BillboardViewModel : ObservableObject { @@ -35,6 +36,42 @@ public final class BillboardViewModel : ObservableObject { } } + public static func fetchRandomAd(excludedIDs: [String] = []) async throws -> BillboardAd? { + guard let url = BillboardConfiguration().adsJSONURL else { + return nil + } + + let session = URLSession(configuration: BillboardViewModel.networkConfiguration) + session.sessionDescription = "Fetching Billboard Ad" + + do { + let (data, _) = try await session.data(from: url) + let decoder = JSONDecoder() + let response = try decoder.decode(BillboardAdResponse.self, from: data) + let filteredAds = response.ads.filter({ !excludedIDs.contains($0.appStoreID) }) + let adToShow = filteredAds.randomElement() + + if let adToShow { + Logger.billboard.debug("✨ Billboard Ad presented: \(adToShow.name)") + } + + return adToShow + + } catch DecodingError.keyNotFound(let key, let context) { + Logger.billboard.error("❌ Failed to decode Billboard Ad due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") + } catch DecodingError.typeMismatch(_, let context) { + Logger.billboard.error("❌ Failed to decode Billboard Ad due to type mismatch – \(context.debugDescription)") + } catch DecodingError.valueNotFound(let type, let context) { + Logger.billboard.error("❌ Failed to decode Billboard Ad due to missing \(type) value – \(context.debugDescription)") + } catch DecodingError.dataCorrupted(_) { + Logger.billboard.error("❌ Failed to decode Billboard Ad because it appears to be invalid JSON") + } catch { + Logger.billboard.error("❌ Failed to decode Billboard Ad: \(error.localizedDescription)") + } + + return nil + } + public static func fetchRandomAd(from url: URL, excludedIDs: [String] = []) async throws -> BillboardAd? { let session = URLSession(configuration: BillboardViewModel.networkConfiguration) session.sessionDescription = "Fetching Billboard Ad" @@ -45,18 +82,23 @@ public final class BillboardViewModel : ObservableObject { let response = try decoder.decode(BillboardAdResponse.self, from: data) let filteredAds = response.ads.filter({ !excludedIDs.contains($0.appStoreID) }) let adToShow = filteredAds.randomElement() + + if let adToShow { + Logger.billboard.debug("✨ Billboard Ad presented: \(adToShow.name)") + } + return adToShow } catch DecodingError.keyNotFound(let key, let context) { - print("❌ Failed to decode due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") } catch DecodingError.typeMismatch(_, let context) { - print("❌ Failed to decode from bundle due to type mismatch – \(context.debugDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad due to type mismatch – \(context.debugDescription)") } catch DecodingError.valueNotFound(let type, let context) { - print("❌ Failed to decode from bundle due to missing \(type) value – \(context.debugDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad due to missing \(type) value – \(context.debugDescription)") } catch DecodingError.dataCorrupted(_) { - print("❌ Failed to decode from bundle because it appears to be invalid JSON") + Logger.billboard.error("❌ Failed to decode Billboard Ad because it appears to be invalid JSON") } catch { - print("❌ Failed to decode from bundle: \(error.localizedDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad: \(error.localizedDescription)") } return nil @@ -73,15 +115,15 @@ public final class BillboardViewModel : ObservableObject { return response.ads } catch DecodingError.keyNotFound(let key, let context) { - print("❌ Failed to decode due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") } catch DecodingError.typeMismatch(_, let context) { - print("❌ Failed to decode from bundle due to type mismatch – \(context.debugDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad due to type mismatch – \(context.debugDescription)") } catch DecodingError.valueNotFound(let type, let context) { - print("❌ Failed to decode from bundle due to missing \(type) value – \(context.debugDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad due to missing \(type) value – \(context.debugDescription)") } catch DecodingError.dataCorrupted(_) { - print("❌ Failed to decode from bundle because it appears to be invalid JSON") + Logger.billboard.error("❌ Failed to decode Billboard Ad because it appears to be invalid JSON") } catch { - print("❌ Failed to decode from bundle: \(error.localizedDescription)") + Logger.billboard.error("❌ Failed to decode Billboard Ad: \(error.localizedDescription)") } return [] diff --git a/Sources/Billboard/Utilities/Logger+Ext.swift b/Sources/Billboard/Utilities/Logger+Ext.swift new file mode 100644 index 0000000..ee65929 --- /dev/null +++ b/Sources/Billboard/Utilities/Logger+Ext.swift @@ -0,0 +1,16 @@ +// +// Logger+Ext.swift +// +// +// Created by Hidde van der Ploeg on 29/10/2023. +// + +import OSLog + +extension Logger { + /// Using your bundle identifier is a great way to ensure a unique identifier. + private static var subsystem = Bundle.main.bundleIdentifier! + + /// Logs coming from the Billboard SPM + static let billboard = Logger(subsystem: subsystem, category: "Billboard") +} From 1c85b37def8110d405c1d54e646df94005defed3 Mon Sep 17 00:00:00 2001 From: Hidde van der Ploeg Date: Sun, 29 Oct 2023 11:33:46 +0000 Subject: [PATCH 2/2] Tweaked the design and updated examples project --- Example/BillboardExample/ContentView.swift | 2 +- .../Billboard/Views/BillboardBannerView.swift | 17 +++++---- .../Views/BillboardCountdownView.swift | 36 +++++++++++++------ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Example/BillboardExample/ContentView.swift b/Example/BillboardExample/ContentView.swift index faf92e4..6003642 100644 --- a/Example/BillboardExample/ContentView.swift +++ b/Example/BillboardExample/ContentView.swift @@ -23,7 +23,7 @@ struct ContentView: View { List { if let advert = allAds.randomElement() { Section { - BillboardBannerView(advert: advert) + BillboardBannerView(advert: advert, hideDismissButtonAndTimer: true) .listRowBackground(Color.clear) .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0)) } diff --git a/Sources/Billboard/Views/BillboardBannerView.swift b/Sources/Billboard/Views/BillboardBannerView.swift index 5609279..116dc99 100644 --- a/Sources/Billboard/Views/BillboardBannerView.swift +++ b/Sources/Billboard/Views/BillboardBannerView.swift @@ -32,8 +32,8 @@ public struct BillboardBannerView : View { Button { if let url = advert.appStoreLink { openURL(url) + canDismiss = true } - canDismiss = true } label: { HStack(spacing: 10) { if let appIcon { @@ -120,12 +120,17 @@ public struct BillboardBannerView : View { } } + @ViewBuilder var backgroundView : some View { - RoundedRectangle(cornerRadius: 16, style: .continuous) - .fill(advert.background) - .shadow(color: includeShadow ? advert.background.opacity(0.5) : Color.clear, - radius: 6, - x: 0, y: 2) + if #available(iOS 16.0, *) { + RoundedRectangle(cornerRadius: 16, style: .continuous) + .fill(advert.background.gradient) + .shadow(color: includeShadow ? advert.background.opacity(0.5) : Color.clear, radius: 6, x: 0, y: 2) + } else { + RoundedRectangle(cornerRadius: 16, style: .continuous) + .fill(advert.background) + .shadow(color: includeShadow ? advert.background.opacity(0.5) : Color.clear, radius: 6, x: 0, y: 2) + } } } diff --git a/Sources/Billboard/Views/BillboardCountdownView.swift b/Sources/Billboard/Views/BillboardCountdownView.swift index b57b44e..11acf31 100644 --- a/Sources/Billboard/Views/BillboardCountdownView.swift +++ b/Sources/Billboard/Views/BillboardCountdownView.swift @@ -14,7 +14,7 @@ struct BillboardCountdownView : View { @Binding var canDismiss : Bool - @State private var seconds : Int = 15 + @State private var seconds : Double = 15 @State private var timerProgress : CGFloat = 0.0 private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() @@ -26,22 +26,36 @@ struct BillboardCountdownView : View { Circle() .trim(from: 0, to: timerProgress) .stroke(advert.tint, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round)) - - Text("\(seconds)") - .font(.compatibleSystem(.caption, design: .rounded, weight: .bold)).monospacedDigit() - .rotationEffect(.degrees(90)) - .minimumScaleFactor(0.5) - .onReceive(timer) { _ in - if seconds > 0 { - seconds -= 1 + if #available(iOS 17.0, *) { + Text("\(seconds, specifier: "%.0f")") + .font(.compatibleSystem(.caption, design: .rounded, weight: .heavy)).monospacedDigit() + .rotationEffect(.degrees(90)) + .minimumScaleFactor(0.5) + .animation(.default, value: seconds) + .transition(.identity) + .contentTransition(.numericText(value: seconds)) + .onReceive(timer) { _ in + if seconds > 0 { + seconds -= 1 + } + } + } else { + Text("\(seconds, specifier: "%.0f")") + .font(.compatibleSystem(.caption, design: .rounded, weight: .bold)).monospacedDigit() + .rotationEffect(.degrees(90)) + .minimumScaleFactor(0.5) + .onReceive(timer) { _ in + if seconds > 0 { + seconds -= 1 + } } - } + } } .foregroundColor(advert.tint) .rotationEffect(.degrees(-90)) .frame(width: 32, height: 32) .onAppear { - seconds = Int(totalDuration) + seconds = totalDuration withAnimation(.linear(duration: totalDuration)) { timerProgress = 1.0 }