mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
WIP Very Good PayWall
This commit is contained in:
parent
9be7f38b5e
commit
c47777aadc
@ -32,6 +32,7 @@ swift_library(
|
||||
"//Swiftgram/SGSwiftUI:SGSwiftUI",
|
||||
"//Swiftgram/SGIAP:SGIAP",
|
||||
"//Swiftgram/SGPayWall:SGPayWall",
|
||||
"//Swiftgram/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/LegacyUI:LegacyUI",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/Postbox:Postbox",
|
||||
|
@ -10,6 +10,7 @@ import ItemListUI
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import PresentationDataUtils
|
||||
import TelegramUIPreferences
|
||||
|
||||
// Optional
|
||||
import SGSimpleSettings
|
||||
@ -648,8 +649,8 @@ public func sgSessionBackupManagerController(context: AccountContext, presentati
|
||||
legacyController.title = "Session Backup" //i18n("BackupManager.Title", strings.baseLanguageCode)
|
||||
|
||||
let swiftUIView = SGSwiftUIView<SessionBackupManagerView>(
|
||||
navigationBarHeight: legacyController.navigationBarHeightModel,
|
||||
containerViewLayout: legacyController.containerViewLayoutModel,
|
||||
legacyController: legacyController,
|
||||
manageSafeArea: true,
|
||||
content: {
|
||||
SessionBackupManagerView(wrapperController: legacyController, context: context)
|
||||
}
|
||||
@ -984,7 +985,7 @@ public func sgDebugController(context: AccountContext) -> ViewController {
|
||||
#if DEBUG
|
||||
if #available(iOS 13.0, *) {
|
||||
if let sgIAPManager = context.sharedContext.SGIAP {
|
||||
presentControllerImpl?(sgPayWallController(presentationData: presentationData, SGIAPManager: sgIAPManager), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
presentControllerImpl?(sgPayWallController(accountManager: context.sharedContext.accountManager, presentationData: presentationData, SGIAPManager: sgIAPManager), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
} else {
|
||||
presentControllerImpl?(UndoOverlayController(
|
||||
|
@ -100,6 +100,7 @@ public final class SGIAPManager: NSObject {
|
||||
|
||||
public private(set) var availableProducts: [SGProduct] = []
|
||||
private var finishedSuccessfulTransactions = Set<String>()
|
||||
private var onRestoreCompletion: (() -> Void)?
|
||||
|
||||
public final class SGProduct: Equatable {
|
||||
private lazy var numberFormatter: NumberFormatter = {
|
||||
@ -206,14 +207,21 @@ public final class SGIAPManager: NSObject {
|
||||
super.init()
|
||||
|
||||
SKPaymentQueue.default().add(self)
|
||||
|
||||
#if DEBUG
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
|
||||
self.requestProducts()
|
||||
}
|
||||
#else
|
||||
self.requestProducts()
|
||||
#endif
|
||||
}
|
||||
|
||||
deinit {
|
||||
SKPaymentQueue.default().remove(self)
|
||||
}
|
||||
|
||||
var canMakePayments: Bool {
|
||||
public var canMakePayments: Bool {
|
||||
return SKPaymentQueue.canMakePayments()
|
||||
}
|
||||
|
||||
@ -233,17 +241,14 @@ public final class SGIAPManager: NSObject {
|
||||
self.productRequest = productRequest
|
||||
}
|
||||
|
||||
public func restorePurchases(completion: @escaping (RestoreState) -> Void) {
|
||||
public func restorePurchases(completion: @escaping () -> Void) {
|
||||
SGLogger.shared.log("SGIAP", "Restoring purchases...")
|
||||
self.onRestoreCompletion = completion
|
||||
|
||||
let paymentQueue = SKPaymentQueue.default()
|
||||
paymentQueue.restoreCompletedTransactions()
|
||||
}
|
||||
|
||||
public enum RestoreState {
|
||||
case succeed(Bool)
|
||||
case failed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SGIAPManager: SKProductsRequestDelegate {
|
||||
@ -292,6 +297,15 @@ extension SGIAPManager: SKPaymentTransactionObserver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
||||
SGLogger.shared.log("SGIAP", "Transactions restored")
|
||||
|
||||
if let onRestoreCompletion = self.onRestoreCompletion {
|
||||
self.onRestoreCompletion = nil
|
||||
onRestoreCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
filegroup(
|
||||
name = "SGPayWallAssets",
|
||||
srcs = glob(["Images.xcassets/**"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "SGPayWall",
|
||||
module_name = "SGPayWall",
|
||||
|
6
Swiftgram/SGPayWall/Images.xcassets/Contents.json
Normal file
6
Swiftgram/SGPayWall/Images.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
23
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/Contents.json
vendored
Normal file
23
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "pro.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "pro@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "pro@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/pro.png
vendored
Normal file
BIN
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/pro.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
BIN
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/pro@2x.png
vendored
Normal file
BIN
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/pro@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/pro@3x.png
vendored
Normal file
BIN
Swiftgram/SGPayWall/Images.xcassets/pro.imageset/pro@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
@ -6,130 +6,13 @@ import TelegramPresentationData
|
||||
import LegacyUI
|
||||
import Display
|
||||
import SGConfig
|
||||
// import SGStrings
|
||||
|
||||
|
||||
struct SGPerk: Identifiable {
|
||||
let id = UUID()
|
||||
let title: String
|
||||
let description: String
|
||||
let icon: String
|
||||
}
|
||||
|
||||
import SGStrings
|
||||
import SwiftSignalKit
|
||||
import TelegramUIPreferences
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct SGPayWallView: View {
|
||||
|
||||
weak var wrapperController: LegacyController?
|
||||
let SGIAP: SGIAPManager
|
||||
|
||||
let perks = [
|
||||
SGPerk(title: "Premium Features", description: "Access all premium features and tools", icon: "star.fill"),
|
||||
SGPerk(title: "No Ads", description: "Enjoy an ad-free experience", icon: "banner.slash"),
|
||||
SGPerk(title: "Cloud Sync", description: "Sync your data across all devices", icon: "cloud.fill"),
|
||||
SGPerk(title: "Priority Support", description: "Get priority customer support", icon: "questionmark.circle.fill"),
|
||||
SGPerk(title: "Advanced Stats", description: "Access detailed analytics and insights", icon: "chart.bar.fill"),
|
||||
SGPerk(title: "Premium Features", description: "Access all premium features and tools", icon: "star.fill"),
|
||||
SGPerk(title: "No Ads", description: "Enjoy an ad-free experience", icon: "banner.slash"),
|
||||
SGPerk(title: "Cloud Sync", description: "Sync your data across all devices", icon: "cloud.fill"),
|
||||
SGPerk(title: "Priority Support", description: "Get priority customer support", icon: "questionmark.circle.fill"),
|
||||
SGPerk(title: "Advanced Stats", description: "Access detailed analytics and insights", icon: "chart.bar.fill"),
|
||||
SGPerk(title: "Advanced Stats", description: "Access detailed analytics and insights", icon: "chart.bar.fill"),
|
||||
SGPerk(title: "Premium Features", description: "Access all premium features and tools", icon: "star.fill"),
|
||||
SGPerk(title: "No Ads", description: "Enjoy an ad-free experience", icon: "banner.slash"),
|
||||
SGPerk(title: "Cloud Sync", description: "Sync your data across all devices", icon: "cloud.fill"),
|
||||
SGPerk(title: "Priority Support", description: "Get priority customer support", icon: "questionmark.circle.fill"),
|
||||
SGPerk(title: "Advanced Stats", description: "Access detailed analytics and insights", icon: "chart.bar.fill"),
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 0) {
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
ForEach(perks) { perk in
|
||||
HStack(spacing: 16) {
|
||||
Image(systemName: perk.icon)
|
||||
.font(.title)
|
||||
.foregroundColor(.blue)
|
||||
.frame(width: 32)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(perk.title)
|
||||
.font(.headline)
|
||||
Text(perk.description)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
}
|
||||
|
||||
VStack(spacing: 12) {
|
||||
Button(action: {
|
||||
for availableProduct in SGIAP.availableProducts {
|
||||
if SG_CONFIG.iaps.contains(availableProduct.skProduct.productIdentifier ) {
|
||||
SGIAP.purchaseProduct(availableProduct, completion: { _ in })
|
||||
}
|
||||
}
|
||||
SGIAP.buyProduct(product: product)
|
||||
}) {
|
||||
Text("Unlock Premium - $9.99")
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.cornerRadius(16)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
SGIAP.restorePurchases(completion: { _ in })
|
||||
}) {
|
||||
Text("Restore Purchases")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
.padding(24)
|
||||
.background(
|
||||
Rectangle()
|
||||
.fill(Color(UIColor.systemBackground))
|
||||
.shadow(radius: 8, y: -4)
|
||||
)
|
||||
}.navigationBarItems(trailing: closeButtonView)
|
||||
|
||||
}
|
||||
.colorScheme(.dark)
|
||||
}
|
||||
|
||||
private var closeButtonView: some View {
|
||||
Button(action: {
|
||||
wrapperController?.dismiss(animated: true)
|
||||
}) {
|
||||
if #available(iOS 15.0, *) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.headline)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public func sgPayWallController(presentationData: PresentationData? = nil, SGIAPManager: SGIAPManager) -> ViewController {
|
||||
public func sgPayWallController(accountManager: AccountMana presentationData: PresentationData? = nil, SGIAPManager: SGIAPManager) -> ViewController {
|
||||
// let theme = presentationData?.theme ?? (UITraitCollection.current.userInterfaceStyle == .dark ? defaultDarkColorPresentationTheme : defaultPresentationTheme)
|
||||
let theme = defaultDarkColorPresentationTheme
|
||||
let strings = presentationData?.strings ?? defaultPresentationStrings
|
||||
@ -142,296 +25,352 @@ public func sgPayWallController(presentationData: PresentationData? = nil, SGIAP
|
||||
// legacyController.displayNavigationBar = false
|
||||
legacyController.statusBar.statusBarStyle = .White
|
||||
legacyController.attemptNavigation = { _ in return false }
|
||||
let swiftUIView = SGPayWallView(wrapperController: legacyController, SGIAP: SGIAPManager)
|
||||
|
||||
let statusSignal = accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.sgStatus])
|
||||
|> take(1)
|
||||
|> map { sharedData -> Int64 in
|
||||
if let sgStatus = sharedData.entries[ApplicationSpecificSharedDataKeys.sgStatus] as? SGStatus {
|
||||
return sgStatus.status
|
||||
} else {
|
||||
return SGStatus.default
|
||||
}
|
||||
}
|
||||
|
||||
let swiftUIView = SGSwiftUIView<SGPayWallView>(
|
||||
legacyController: legacyController,
|
||||
content: {
|
||||
SGPayWallView(statusSignal: statusSignal, wrapperController: legacyController, SGIAP: SGIAPManager, lang: strings.baseLanguageCode)
|
||||
}
|
||||
)
|
||||
let controller = UIHostingController(rootView: swiftUIView, ignoreSafeArea: true)
|
||||
legacyController.bind(controller: controller)
|
||||
|
||||
return legacyController
|
||||
}
|
||||
|
||||
//
|
||||
//import SwiftUI
|
||||
//
|
||||
//
|
||||
//struct ModalView: View {
|
||||
// @State private var isShowingModal = true
|
||||
//
|
||||
// var body: some View {
|
||||
// Button("Show Modal") {
|
||||
// isShowingModal.toggle()
|
||||
// }
|
||||
// .sheet(isPresented: $isShowingModal) {
|
||||
// ContentView()
|
||||
// .presentationDetents([.large]) // iOS 16+
|
||||
// .presentationDragIndicator(.hidden)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//let innerShadowWidth: CGFloat = 15.0
|
||||
//
|
||||
//struct BackgroundView: View {
|
||||
// var body: some View {
|
||||
// ZStack {
|
||||
// LinearGradient(
|
||||
// gradient: Gradient(stops: [
|
||||
// .init(color: Color(hex: "A053F8").opacity(0.8), location: 0.0),
|
||||
// .init(color: Color.clear, location: 0.20),
|
||||
//
|
||||
// ]),
|
||||
// startPoint: .topLeading,
|
||||
// endPoint: .bottomTrailing
|
||||
// )
|
||||
// .edgesIgnoringSafeArea(.all)
|
||||
// LinearGradient(
|
||||
// gradient: Gradient(stops: [
|
||||
// .init(color: Color(hex: "CC4303").opacity(0.6), location: 0.0),
|
||||
// .init(color: Color.clear, location: 0.15),
|
||||
// ]),
|
||||
// startPoint: .topTrailing,
|
||||
// endPoint: .bottomLeading
|
||||
// )
|
||||
// .blendMode(.lighten)
|
||||
//
|
||||
// .edgesIgnoringSafeArea(.all)
|
||||
// .overlay(
|
||||
// RoundedRectangle(cornerRadius: 0)
|
||||
// .stroke(Color.clear, lineWidth: 0)
|
||||
// .background(
|
||||
// ZStack {
|
||||
// innerShadow(x: -2, y: -2, blur: 4, color: Color(hex: "FF8C56")) // orange shadow
|
||||
// innerShadow(x: 2, y: 2, blur: 4, color: Color(hex: "A053F8")) // purple shadow
|
||||
// // innerShadow(x: 0, y: 0, blur: 4, color: Color.white.opacity(0.3))
|
||||
// }
|
||||
// )
|
||||
// ).ignoresSafeArea(.all)
|
||||
// }
|
||||
// .background(Color.black)
|
||||
// }
|
||||
//
|
||||
// func innerShadow(x: CGFloat, y: CGFloat, blur: CGFloat, color: Color) -> some View {
|
||||
// return RoundedRectangle(cornerRadius: 0)
|
||||
// .stroke(color, lineWidth: innerShadowWidth)
|
||||
// .blur(radius: blur)
|
||||
// .offset(x: x, y: y)
|
||||
// .mask(RoundedRectangle(cornerRadius: 0).fill(LinearGradient(gradient: Gradient(colors: [Color.black, Color.clear]), startPoint: .top, endPoint: .bottom)))
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension Color {
|
||||
// init(hex: String) {
|
||||
// let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
// var int: UInt64 = 0
|
||||
// Scanner(string: hex).scanHexInt64(&int)
|
||||
// let a, r, g, b: UInt64
|
||||
// switch hex.count {
|
||||
// case 6: // RGB (No alpha)
|
||||
// (a, r, g, b) = (255, (int >> 16) & 0xff, (int >> 8) & 0xff, int & 0xff)
|
||||
// case 8: // ARGB
|
||||
// (a, r, g, b) = ((int >> 24) & 0xff, (int >> 16) & 0xff, (int >> 8) & 0xff, int & 0xff)
|
||||
// default:
|
||||
// (a, r, g, b) = (255, 0, 0, 0)
|
||||
// }
|
||||
// self.init(.sRGB, red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255, opacity: Double(a) / 255)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//
|
||||
//struct ContentView: View {
|
||||
// var body: some View {
|
||||
// bodyContent
|
||||
// .overlay(closeButtonView
|
||||
// .padding([.top, .trailing], 16) // Add padding from top and right edges
|
||||
// .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// var bodyContent: some View {
|
||||
// ZStack {
|
||||
// BackgroundView()
|
||||
// ZStack(alignment: .bottom) {
|
||||
// ScrollView(showsIndicators: false) {
|
||||
// VStack(spacing: 24) {
|
||||
// // Premium Icon
|
||||
// PremiumIconView()
|
||||
// .frame(width: 100, height: 100)
|
||||
//
|
||||
// // Title and Subtitle
|
||||
// VStack(spacing: 8) {
|
||||
// Text("Swiftgram Pro")
|
||||
// .font(.largeTitle)
|
||||
// .fontWeight(.bold)
|
||||
//
|
||||
// Text("Supercharged with Pro features")
|
||||
// .font(.callout)
|
||||
// // .foregroundColor(.secondary)
|
||||
// .multilineTextAlignment(.center)
|
||||
// .padding(.horizontal)
|
||||
// }
|
||||
//
|
||||
// // Features List
|
||||
// VStack(spacing: 8) {
|
||||
// FeatureRow(
|
||||
// icon: FeatureIcon(icon: "lock.fill", backgroundColor: .blue),
|
||||
// title: "Session Backup",
|
||||
// subtitle: "Restore sessions from encrypted local Apple Keychain backup."
|
||||
// )
|
||||
//
|
||||
// FeatureRow(
|
||||
// icon: FeatureIcon(icon: "nosign", backgroundColor: .gray, fontWeight: .bold),
|
||||
// title: "Message Filter",
|
||||
// subtitle: "Reduce visibility of spam, promotions and annoying messages."
|
||||
// )
|
||||
//
|
||||
// FeatureRow(
|
||||
// icon: FeatureIcon(icon: "bell.badge.slash.fill", backgroundColor: .red),
|
||||
// title: "Disable @mentions and replies",
|
||||
// subtitle: "Hide or silence non-important notifications."
|
||||
// )
|
||||
//
|
||||
// FeatureRow(
|
||||
// icon: FeatureIcon(icon: "bold.underline", backgroundColor: .blue, iconSize: 16),
|
||||
// title: "Quick Formatting panel",
|
||||
// subtitle: "Save time preparing your posts with a panel right above your keyboard."
|
||||
// )
|
||||
//
|
||||
//
|
||||
// }
|
||||
// .padding(.horizontal, innerShadowWidth + 8.0)
|
||||
//
|
||||
// Text("Privacy Policy")
|
||||
// .font(.footnote)
|
||||
//
|
||||
// Color.clear.frame(height: 40)
|
||||
// }
|
||||
// .padding(.vertical, 40)
|
||||
// }
|
||||
// // Fixed purchase button at bottom
|
||||
// VStack(spacing: 0) {
|
||||
// Divider()
|
||||
//
|
||||
// Button(action: {
|
||||
// // Your purchase action here
|
||||
// }) {
|
||||
// Text("Subscribe for $9.99 / month")
|
||||
// .fontWeight(.semibold)
|
||||
// .frame(maxWidth: .infinity)
|
||||
// .padding()
|
||||
// .background(Color(hex: "F1552E"))
|
||||
// .foregroundColor(.white)
|
||||
// .cornerRadius(12)
|
||||
// }
|
||||
// .padding()
|
||||
// }
|
||||
// .foregroundColor(Color.black)
|
||||
// .background(.ultraThinMaterial)
|
||||
// .shadow(radius: 8, y: -4)
|
||||
// }
|
||||
// }
|
||||
// .colorScheme(.dark)
|
||||
// }
|
||||
//
|
||||
// private var closeButtonView: some View {
|
||||
// Button(action: {
|
||||
//
|
||||
// }) {
|
||||
// Image(systemName: "xmark")
|
||||
// .font(.headline)
|
||||
// .foregroundColor(.secondary.opacity(0.6))
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// Premium Icon View using SVG
|
||||
//struct PremiumIconView: View {
|
||||
// var body: some View {
|
||||
// Image("PRO") // You'll need to add the SVG to your assets
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct FeatureIcon: View {
|
||||
// let icon: String
|
||||
// let iconColor: Color
|
||||
// let backgroundColor: Color
|
||||
// let iconSize: CGFloat
|
||||
// let frameSize: CGFloat
|
||||
// let fontWeight: Font.Weight
|
||||
//
|
||||
// init(
|
||||
// icon: String,
|
||||
// iconColor: Color = .white,
|
||||
// backgroundColor: Color = .blue,
|
||||
// iconSize: CGFloat = 18,
|
||||
// frameSize: CGFloat = 32,
|
||||
// fontWeight: Font.Weight = .regular
|
||||
// ) {
|
||||
// self.icon = icon
|
||||
// self.iconColor = iconColor
|
||||
// self.backgroundColor = backgroundColor
|
||||
// self.iconSize = iconSize
|
||||
// self.frameSize = frameSize
|
||||
// self.fontWeight = fontWeight
|
||||
// }
|
||||
//
|
||||
// var body: some View {
|
||||
// Image(systemName: icon)
|
||||
// .font(.system(size: iconSize))
|
||||
// .fontWeight(fontWeight)
|
||||
// .foregroundColor(iconColor)
|
||||
// .frame(width: frameSize, height: frameSize)
|
||||
// .background(backgroundColor)
|
||||
// .clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//// Feature Row Component
|
||||
//struct FeatureRow: View {
|
||||
// let icon: FeatureIcon
|
||||
// let title: String
|
||||
// let subtitle: String
|
||||
//
|
||||
// var body: some View {
|
||||
// Button(action: {
|
||||
// // Add your tap action here
|
||||
// }) {
|
||||
// HStack(spacing: 16) {
|
||||
//
|
||||
// HStack(alignment: .top, spacing: 12) {
|
||||
// icon
|
||||
//
|
||||
// VStack(alignment: .leading, spacing: 4) {
|
||||
// Text(title)
|
||||
// .font(.headline)
|
||||
// .fontWeight(.medium)
|
||||
//
|
||||
// Text(subtitle)
|
||||
// .font(.subheadline)
|
||||
// .foregroundColor(.secondary)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Spacer()
|
||||
//
|
||||
// Image(systemName: "chevron.right")
|
||||
// .font(.system(size: 12, weight: .semibold))
|
||||
// .foregroundColor(.secondary)
|
||||
// }
|
||||
// .padding()
|
||||
// .background(
|
||||
// RoundedRectangle(cornerRadius: 12)
|
||||
// .fill(Color(.systemGray6))
|
||||
// .shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 4)
|
||||
// )
|
||||
// }
|
||||
// .buttonStyle(PlainButtonStyle())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//struct ContentView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// ModalView()
|
||||
// }
|
||||
//}
|
||||
private let innerShadowWidth: CGFloat = 15.0
|
||||
private let accentColorHex: String = "F1552E"
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct BackgroundView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
LinearGradient(
|
||||
gradient: Gradient(stops: [
|
||||
.init(color: Color(hex: "A053F8").opacity(0.8), location: 0.0), // purple gradient
|
||||
.init(color: Color.clear, location: 0.20),
|
||||
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
LinearGradient(
|
||||
gradient: Gradient(stops: [
|
||||
.init(color: Color(hex: "CC4303").opacity(0.6), location: 0.0), // orange gradient
|
||||
.init(color: Color.clear, location: 0.15),
|
||||
]),
|
||||
startPoint: .topTrailing,
|
||||
endPoint: .bottomLeading
|
||||
)
|
||||
.blendMode(.lighten)
|
||||
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 0)
|
||||
.stroke(Color.clear, lineWidth: 0)
|
||||
.background(
|
||||
ZStack {
|
||||
innerShadow(x: -2, y: -2, blur: 4, color: Color(hex: "FF8C56")) // orange shadow
|
||||
innerShadow(x: 2, y: 2, blur: 4, color: Color(hex: "A053F8")) // purple shadow
|
||||
// innerShadow(x: 0, y: 0, blur: 4, color: Color.white.opacity(0.3))
|
||||
}
|
||||
)
|
||||
)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
.background(Color.black)
|
||||
}
|
||||
|
||||
func innerShadow(x: CGFloat, y: CGFloat, blur: CGFloat, color: Color) -> some View {
|
||||
return RoundedRectangle(cornerRadius: 0)
|
||||
.stroke(color, lineWidth: innerShadowWidth)
|
||||
.blur(radius: blur)
|
||||
.offset(x: x, y: y)
|
||||
.mask(RoundedRectangle(cornerRadius: 0).fill(LinearGradient(gradient: Gradient(colors: [Color.black, Color.clear]), startPoint: .top, endPoint: .bottom)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct SGPayWallView: View {
|
||||
@Environment(\.navigationBarHeight) var navigationBarHeight: CGFloat
|
||||
@Environment(\.containerViewLayout) var containerViewLayout: ContainerViewLayout?
|
||||
|
||||
weak var wrapperController: LegacyController?
|
||||
let SGIAP: SGIAPManager
|
||||
let lang: String
|
||||
|
||||
// State management
|
||||
@State private var product: SGIAPManager.SGProduct?
|
||||
@State private var isRestoringPurchases = false
|
||||
|
||||
private let productsPub = NotificationCenter.default.publisher(for: .SGIAPHelperProductsUpdatedNotification, object: nil)
|
||||
|
||||
// Loading state enum
|
||||
private enum LoadingState {
|
||||
case loading
|
||||
case loaded
|
||||
case error(String)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
BackgroundView()
|
||||
|
||||
ZStack(alignment: .bottom) {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 24) {
|
||||
// Icon
|
||||
Image("pro")
|
||||
.frame(width: 100, height: 100)
|
||||
|
||||
// Title and Subtitle
|
||||
VStack(spacing: 8) {
|
||||
Text("Swiftgram Pro")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text("Supercharged with Pro features")
|
||||
.font(.callout)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
// Features
|
||||
VStack(spacing: 8) {
|
||||
featuresSection
|
||||
restorePurchasesButton
|
||||
}
|
||||
|
||||
// Spacer for purchase buttons
|
||||
Color.clear.frame(height: 50)
|
||||
}
|
||||
.padding(.vertical, 50)
|
||||
}
|
||||
|
||||
// Fixed purchase button at bottom
|
||||
purchaseSection
|
||||
}
|
||||
}
|
||||
.overlay(closeButtonView)
|
||||
.colorScheme(.dark)
|
||||
.onReceive(productsPub) { _ in
|
||||
updateSelectedProduct()
|
||||
}
|
||||
.onAppear {
|
||||
updateSelectedProduct()
|
||||
}
|
||||
}
|
||||
|
||||
private var featuresSection: some View {
|
||||
VStack(spacing: 8) {
|
||||
FeatureRow(
|
||||
icon: FeatureIcon(icon: "lock.fill", backgroundColor: .blue),
|
||||
title: "Session Backup",
|
||||
subtitle: "Restore sessions from encrypted local Apple Keychain backup."
|
||||
)
|
||||
|
||||
FeatureRow(
|
||||
icon: FeatureIcon(icon: "nosign", backgroundColor: .gray, fontWeight: .bold),
|
||||
title: "Message Filter",
|
||||
subtitle: "Reduce visibility of spam, promotions and annoying messages."
|
||||
)
|
||||
|
||||
FeatureRow(
|
||||
icon: FeatureIcon(icon: "bell.badge.slash.fill", backgroundColor: .red),
|
||||
title: "Disable @mentions and replies",
|
||||
subtitle: "Hide or silence non-important notifications."
|
||||
)
|
||||
|
||||
FeatureRow(
|
||||
icon: FeatureIcon(icon: "bold.underline", backgroundColor: .blue, iconSize: 16),
|
||||
title: "Quick Formatting panel",
|
||||
subtitle: "Save time preparing your posts with a panel right above your keyboard."
|
||||
)
|
||||
}
|
||||
.padding(.leading, max(innerShadowWidth + 8.0, sgLeftSafeAreaInset(containerViewLayout)))
|
||||
.padding(.trailing, max(innerShadowWidth + 8.0, sgRightSafeAreaInset(containerViewLayout)))
|
||||
}
|
||||
|
||||
private var restorePurchasesButton: some View {
|
||||
Button(action: handleRestorePurchases) {
|
||||
Text("Restore Purchases")
|
||||
.font(.footnote)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color(hex: accentColorHex))
|
||||
}
|
||||
.disabled(isRestoringPurchases)
|
||||
.opacity(isRestoringPurchases ? 1.0 : 0.5)
|
||||
}
|
||||
|
||||
private var purchaseSection: some View {
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
|
||||
Button(action: handlePurchase) {
|
||||
Text(buttonTitle)
|
||||
.fontWeight(.semibold)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color(hex: accentColorHex))
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.disabled(!canPurchase)
|
||||
.opacity(canPurchase ? 1.0 : 0.5)
|
||||
.padding([.horizontal, .top])
|
||||
.padding(.bottom, sgBottomSafeAreaInset(containerViewLayout))
|
||||
}
|
||||
.foregroundColor(Color.black)
|
||||
.backgroundIfAvailable(material: .ultraThinMaterial)
|
||||
.shadow(radius: 8, y: -4)
|
||||
}
|
||||
|
||||
private var closeButtonView: some View {
|
||||
Button(action: dismiss) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary.opacity(0.6))
|
||||
}
|
||||
.padding([.top, .trailing], 16)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
}
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
private var buttonTitle: String {
|
||||
if let product = product {
|
||||
if !SGIAP.canMakePayments {
|
||||
return "Payments unavailable"
|
||||
} else {
|
||||
return "Subscribe for \(product.price) / month"
|
||||
}
|
||||
} else {
|
||||
return "Contacting App Store..."
|
||||
}
|
||||
}
|
||||
|
||||
private var canPurchase: Bool {
|
||||
if !SGIAP.canMakePayments {
|
||||
return false
|
||||
} else {
|
||||
return product != nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
|
||||
private func updateSelectedProduct() {
|
||||
product = SGIAP.availableProducts.first { $0.id == SG_CONFIG.iaps.first ?? "" }
|
||||
}
|
||||
|
||||
private func handlePurchase() {
|
||||
guard let product = product else { return }
|
||||
SGIAP.buyProduct(product.skProduct)
|
||||
}
|
||||
|
||||
private func handleRestorePurchases() {
|
||||
isRestoringPurchases = true
|
||||
SGIAP.restorePurchases {
|
||||
isRestoringPurchases = false
|
||||
}
|
||||
}
|
||||
|
||||
private func dismiss() {
|
||||
wrapperController?.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct FeatureIcon: View {
|
||||
let icon: String
|
||||
let iconColor: Color
|
||||
let backgroundColor: Color
|
||||
let iconSize: CGFloat
|
||||
let frameSize: CGFloat
|
||||
let fontWeight: SwiftUI.Font.Weight
|
||||
|
||||
init(
|
||||
icon: String,
|
||||
iconColor: Color = .white,
|
||||
backgroundColor: Color = .blue,
|
||||
iconSize: CGFloat = 18,
|
||||
frameSize: CGFloat = 32,
|
||||
fontWeight: SwiftUI.Font.Weight = .regular
|
||||
) {
|
||||
self.icon = icon
|
||||
self.iconColor = iconColor
|
||||
self.backgroundColor = backgroundColor
|
||||
self.iconSize = iconSize
|
||||
self.frameSize = frameSize
|
||||
self.fontWeight = fontWeight
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: iconSize))
|
||||
.fontWeightIfAvailable(fontWeight)
|
||||
.foregroundColor(iconColor)
|
||||
.frame(width: frameSize, height: frameSize)
|
||||
.background(backgroundColor)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct FeatureRow: View {
|
||||
let icon: FeatureIcon
|
||||
let title: String
|
||||
let subtitle: String
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
// TODO(swiftgram): Feature row clarification
|
||||
}) {
|
||||
HStack(spacing: 16) {
|
||||
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
icon
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.fontWeight(.medium)
|
||||
|
||||
Text(subtitle)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(.systemGray6))
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 4)
|
||||
)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
|
@ -7,75 +7,170 @@ import TelegramPresentationData
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public class ObservedValue<T>: ObservableObject {
|
||||
@Published var value: T
|
||||
@Published public var value: T
|
||||
|
||||
init(_ value: T) {
|
||||
public init(_ value: T) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public struct NavigationBarHeightKey: EnvironmentKey {
|
||||
public static let defaultValue: CGFloat = 0
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public struct ContainerViewLayoutKey: EnvironmentKey {
|
||||
public static let defaultValue: ContainerViewLayout? = nil
|
||||
}
|
||||
|
||||
// Perhaps, affects Performance a lot
|
||||
//@available(iOS 13.0, *)
|
||||
//public struct ContainerViewLayoutUpdateCountKey: EnvironmentKey {
|
||||
// public static let defaultValue: ObservedValue<Int64> = ObservedValue(0)
|
||||
//}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public extension EnvironmentValues {
|
||||
var navigationBarHeight: CGFloat {
|
||||
get { self[NavigationBarHeightKey.self] }
|
||||
set { self[NavigationBarHeightKey.self] = newValue }
|
||||
}
|
||||
|
||||
var containerViewLayout: ContainerViewLayout? {
|
||||
get { self[ContainerViewLayoutKey.self] }
|
||||
set { self[ContainerViewLayoutKey.self] = newValue }
|
||||
}
|
||||
|
||||
// var containerViewLayoutUpdateCount: ObservedValue<Int64> {
|
||||
// get { self[ContainerViewLayoutUpdateCountKey.self] }
|
||||
// set { self[ContainerViewLayoutUpdateCountKey.self] = newValue }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public struct SGSwiftUIView<Content: View>: View {
|
||||
public let content: Content
|
||||
public let manageSafeArea: Bool
|
||||
|
||||
@ObservedObject var navigationBarHeight: ObservedValue<CGFloat>
|
||||
@ObservedObject var containerViewLayout: ObservedValue<ContainerViewLayout?>
|
||||
// @ObservedObject var containerViewLayoutUpdateCount: ObservedValue<Int64>
|
||||
|
||||
public init(
|
||||
navigationBarHeight: ObservedValue<CGFloat>,
|
||||
containerViewLayout: ObservedValue<ContainerViewLayout?>,
|
||||
legacyController: LegacySwiftUIController,
|
||||
manageSafeArea: Bool = false,
|
||||
@ViewBuilder content: () -> Content
|
||||
) {
|
||||
self.navigationBarHeight = navigationBarHeight
|
||||
self.containerViewLayout = containerViewLayout
|
||||
#if DEBUG
|
||||
if manageSafeArea {
|
||||
print("WARNING SGSwiftUIView: manageSafeArea is deprecated, use @Environment(\\.navigationBarHeight) and @Environment(\\.containerViewLayout)")
|
||||
}
|
||||
#endif
|
||||
self.navigationBarHeight = legacyController.navigationBarHeightModel
|
||||
self.containerViewLayout = legacyController.containerViewLayoutModel
|
||||
// self.containerViewLayoutUpdateCount = legacyController.containerViewLayoutUpdateCountModel
|
||||
self.manageSafeArea = manageSafeArea
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
content
|
||||
.modifier(CustomSafeAreaPadding(navigationBarHeight: navigationBarHeight, containerViewLayout: containerViewLayout))
|
||||
.if(manageSafeArea) { $0.modifier(CustomSafeArea()) }
|
||||
.environment(\.navigationBarHeight, navigationBarHeight.value)
|
||||
.environment(\.containerViewLayout, containerViewLayout.value)
|
||||
// .environment(\.containerViewLayoutUpdateCount, containerViewLayoutUpdateCount)
|
||||
// .onReceive(containerViewLayoutUpdateCount.$value) { _ in
|
||||
// // Make sure View is updated when containerViewLayoutUpdateCount changes,
|
||||
// // in case it does not depend on containerViewLayout
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public struct CustomSafeAreaPadding: ViewModifier {
|
||||
@ObservedObject var navigationBarHeight: ObservedValue<CGFloat>
|
||||
@ObservedObject var containerViewLayout: ObservedValue<ContainerViewLayout?>
|
||||
public struct CustomSafeArea: ViewModifier {
|
||||
@Environment(\.navigationBarHeight) var navigationBarHeight: CGFloat
|
||||
@Environment(\.containerViewLayout) var containerViewLayout: ContainerViewLayout?
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
// .padding(.top, /*totalTopSafeArea > navigationBarHeight.value ? totalTopSafeArea :*/ navigationBarHeight.value)
|
||||
.padding(.top, totalTopSafeArea > navigationBarHeight.value ? totalTopSafeArea : navigationBarHeight.value)
|
||||
.padding(.bottom, (containerViewLayout.value?.safeInsets.bottom ?? 0) /*+ (containerViewLayout.value?.intrinsicInsets.bottom ?? 0)*/)
|
||||
.padding(.leading, containerViewLayout.value?.safeInsets.left ?? 0)
|
||||
.padding(.trailing, containerViewLayout.value?.safeInsets.right ?? 0)
|
||||
.padding(.top, topInset)
|
||||
.padding(.bottom, bottomInset)
|
||||
.padding(.leading, leftInset)
|
||||
.padding(.trailing, rightInset)
|
||||
}
|
||||
|
||||
var totalTopSafeArea: CGFloat {
|
||||
(containerViewLayout.value?.safeInsets.top ?? 0) +
|
||||
(containerViewLayout.value?.intrinsicInsets.top ?? 0)
|
||||
private var topInset: CGFloat {
|
||||
max(
|
||||
(containerViewLayout?.safeInsets.top ?? 0) + (containerViewLayout?.intrinsicInsets.top ?? 0),
|
||||
navigationBarHeight
|
||||
)
|
||||
}
|
||||
|
||||
private var bottomInset: CGFloat {
|
||||
(containerViewLayout?.safeInsets.bottom ?? 0)
|
||||
// DEPRECATED, do not change
|
||||
// + (containerViewLayout.value?.intrinsicInsets.bottom ?? 0)
|
||||
}
|
||||
|
||||
private var leftInset: CGFloat {
|
||||
containerViewLayout?.safeInsets.left ?? 0
|
||||
}
|
||||
|
||||
private var rightInset: CGFloat {
|
||||
containerViewLayout?.safeInsets.right ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public extension View {
|
||||
func sgTopSafeAreaInset(_ containerViewLayout: ContainerViewLayout?, _ navigationBarHeight: CGFloat) -> CGFloat {
|
||||
return max(
|
||||
(containerViewLayout?.safeInsets.top ?? 0) + (containerViewLayout?.intrinsicInsets.top ?? 0),
|
||||
navigationBarHeight
|
||||
)
|
||||
}
|
||||
|
||||
func sgBottomSafeAreaInset(_ containerViewLayout: ContainerViewLayout?) -> CGFloat {
|
||||
return (containerViewLayout?.safeInsets.bottom ?? 0) + (containerViewLayout?.intrinsicInsets.bottom ?? 0)
|
||||
}
|
||||
|
||||
func sgLeftSafeAreaInset(_ containerViewLayout: ContainerViewLayout?) -> CGFloat {
|
||||
return containerViewLayout?.safeInsets.left ?? 0
|
||||
}
|
||||
|
||||
func sgRightSafeAreaInset(_ containerViewLayout: ContainerViewLayout?) -> CGFloat {
|
||||
return containerViewLayout?.safeInsets.right ?? 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public final class LegacySwiftUIController: LegacyController {
|
||||
public var navigationBarHeightModel: ObservedValue<CGFloat>
|
||||
public var containerViewLayoutModel: ObservedValue<ContainerViewLayout?>
|
||||
public var inputHeightModel: ObservedValue<CGFloat?>
|
||||
// public var containerViewLayoutUpdateCountModel: ObservedValue<Int64>
|
||||
|
||||
override public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
navigationBarHeightModel = ObservedValue<CGFloat>(0.0)
|
||||
containerViewLayoutModel = ObservedValue<ContainerViewLayout?>(initialLayout)
|
||||
inputHeightModel = ObservedValue<CGFloat?>(nil)
|
||||
// containerViewLayoutUpdateCountModel = ObservedValue<Int64>(0)
|
||||
super.init(presentation: presentation, theme: theme, strings: strings, initialLayout: initialLayout)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
// containerViewLayoutUpdateCountModel.value += 1
|
||||
|
||||
var newNavigationBarHeight = navigationLayout(layout: layout).navigationFrame.maxY
|
||||
if !self.displayNavigationBar {
|
||||
if !self.displayNavigationBar || self.navigationPresentation == .modal {
|
||||
newNavigationBarHeight = 0.0
|
||||
}
|
||||
if navigationBarHeightModel.value != newNavigationBarHeight {
|
||||
@ -237,7 +332,7 @@ public extension View {
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension Color {
|
||||
public extension Color {
|
||||
|
||||
func uiColor() -> UIColor {
|
||||
|
||||
@ -264,4 +359,65 @@ extension Color {
|
||||
}
|
||||
return (r, g, b, a)
|
||||
}
|
||||
|
||||
init(hex: String) {
|
||||
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
var int: UInt64 = 0
|
||||
Scanner(string: hex).scanHexInt64(&int)
|
||||
let a, r, g, b: UInt64
|
||||
switch hex.count {
|
||||
case 6: // RGB (No alpha)
|
||||
(a, r, g, b) = (255, (int >> 16) & 0xff, (int >> 8) & 0xff, int & 0xff)
|
||||
case 8: // ARGB
|
||||
(a, r, g, b) = ((int >> 24) & 0xff, (int >> 16) & 0xff, (int >> 8) & 0xff, int & 0xff)
|
||||
default:
|
||||
(a, r, g, b) = (255, 0, 0, 0)
|
||||
}
|
||||
self.init(.sRGB, red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255, opacity: Double(a) / 255)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum BackgroundMaterial {
|
||||
case ultraThinMaterial
|
||||
case thinMaterial
|
||||
case regularMaterial
|
||||
case thickMaterial
|
||||
case ultraThickMaterial
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
var material: Material {
|
||||
switch self {
|
||||
case .ultraThinMaterial: return .ultraThinMaterial
|
||||
case .thinMaterial: return .thinMaterial
|
||||
case .regularMaterial: return .regularMaterial
|
||||
case .thickMaterial: return .thickMaterial
|
||||
case .ultraThickMaterial: return .ultraThickMaterial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public extension View {
|
||||
func fontWeightIfAvailable(_ weight: SwiftUI.Font.Weight) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
return self.fontWeight(weight)
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
func backgroundIfAvailable(material: BackgroundMaterial) -> some View {
|
||||
if #available(iOS 15.0, *) {
|
||||
return self.background(material.material)
|
||||
} else {
|
||||
return self.background(
|
||||
Color(.systemBackground)
|
||||
.opacity(0.75)
|
||||
.blur(radius: 3)
|
||||
.overlay(Color.white.opacity(0.1))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -365,7 +365,7 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
SGRESOURCES = ["//Swiftgram/SGSettingsUI:SGUIAssets"]
|
||||
SGRESOURCES = ["//Swiftgram/SGSettingsUI:SGUIAssets", "//Swiftgram/SGPayWall:SGPayWallAssets"]
|
||||
|
||||
swift_library(
|
||||
name = "Lib",
|
||||
|
Loading…
x
Reference in New Issue
Block a user