Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update design and example #29

Merged
merged 2 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Example/BillboardExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
62 changes: 52 additions & 10 deletions Sources/Billboard/BillboardViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import OSLog

public final class BillboardViewModel : ObservableObject {

Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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 []
Expand Down
16 changes: 16 additions & 0 deletions Sources/Billboard/Utilities/Logger+Ext.swift
Original file line number Diff line number Diff line change
@@ -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")
}
17 changes: 11 additions & 6 deletions Sources/Billboard/Views/BillboardBannerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
36 changes: 25 additions & 11 deletions Sources/Billboard/Views/BillboardCountdownView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
}
Expand Down
Loading