Polish app theming and toolbar badge behavior
This commit is contained in:
@@ -282,9 +282,23 @@ private final class QRScannerViewModel: NSObject, ObservableObject, AVCaptureMet
|
||||
continuation.resume()
|
||||
}
|
||||
|
||||
guard let device = AVCaptureDevice.default(for: .video),
|
||||
let input = try? AVCaptureDeviceInput(device: device),
|
||||
self.captureSession.canAddInput(input) else {
|
||||
guard let device = AVCaptureDevice.default(for: .video) else {
|
||||
DispatchQueue.main.async {
|
||||
self.isPreviewAvailable = false
|
||||
self.statusMessage = "No compatible camera was found. Use the fallback payload below."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let input = try? AVCaptureDeviceInput(device: device) else {
|
||||
DispatchQueue.main.async {
|
||||
self.isPreviewAvailable = false
|
||||
self.statusMessage = "No compatible camera was found. Use the fallback payload below."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard self.captureSession.canAddInput(input) else {
|
||||
DispatchQueue.main.async {
|
||||
self.isPreviewAvailable = false
|
||||
self.statusMessage = "No compatible camera was found. Use the fallback payload below."
|
||||
@@ -296,6 +310,7 @@ private final class QRScannerViewModel: NSObject, ObservableObject, AVCaptureMet
|
||||
|
||||
let output = AVCaptureMetadataOutput()
|
||||
guard self.captureSession.canAddOutput(output) else {
|
||||
self.captureSession.removeInput(input)
|
||||
DispatchQueue.main.async {
|
||||
self.isPreviewAvailable = false
|
||||
self.statusMessage = "Unable to configure QR metadata scanning on this device."
|
||||
@@ -305,6 +320,18 @@ private final class QRScannerViewModel: NSObject, ObservableObject, AVCaptureMet
|
||||
|
||||
self.captureSession.addOutput(output)
|
||||
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
||||
|
||||
let supportedTypes = output.availableMetadataObjectTypes
|
||||
guard supportedTypes.contains(.qr) else {
|
||||
self.captureSession.removeOutput(output)
|
||||
self.captureSession.removeInput(input)
|
||||
DispatchQueue.main.async {
|
||||
self.isPreviewAvailable = false
|
||||
self.statusMessage = "This camera does not support QR metadata scanning. Use the fallback payload below."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
output.metadataObjectTypes = [.qr]
|
||||
self.isConfigured = true
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ private extension View {
|
||||
func cleanTabBarOnIOS() -> some View {
|
||||
#if os(iOS)
|
||||
toolbarBackground(.visible, for: .tabBar)
|
||||
.toolbarBackground(Color.white.opacity(0.98), for: .tabBar)
|
||||
.toolbarBackground(AppTheme.chromeFill, for: .tabBar)
|
||||
#else
|
||||
self
|
||||
#endif
|
||||
@@ -28,6 +28,7 @@ private extension View {
|
||||
|
||||
struct HomeRootView: View {
|
||||
@ObservedObject var model: AppViewModel
|
||||
@State private var notificationBellFrame: CGRect?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -37,6 +38,16 @@ struct HomeRootView: View {
|
||||
RegularHomeContainer(model: model)
|
||||
}
|
||||
}
|
||||
.onPreferenceChange(NotificationBellFrameKey.self) { notificationBellFrame = $0 }
|
||||
.overlay(alignment: .topLeading) {
|
||||
if usesCompactNavigation {
|
||||
NotificationBellBadgeOverlay(
|
||||
unreadCount: model.unreadNotificationCount,
|
||||
bellFrame: notificationBellFrame
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $model.isNotificationCenterPresented) {
|
||||
NotificationCenterSheet(model: model)
|
||||
}
|
||||
@@ -90,6 +101,7 @@ private struct RegularHomeContainer: View {
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
Sidebar(model: model)
|
||||
.navigationSplitViewColumnWidth(min: 240, ideal: 280, max: 320)
|
||||
} detail: {
|
||||
HomeSectionScreen(model: model, section: model.selectedSection, compactLayout: false)
|
||||
.navigationTitle(model.selectedSection.title)
|
||||
@@ -111,6 +123,39 @@ private struct DashboardToolbar: ToolbarContent {
|
||||
}
|
||||
}
|
||||
|
||||
private struct NotificationBellFrameKey: PreferenceKey {
|
||||
static var defaultValue: CGRect? = nil
|
||||
|
||||
static func reduce(value: inout CGRect?, nextValue: () -> CGRect?) {
|
||||
value = nextValue() ?? value
|
||||
}
|
||||
}
|
||||
|
||||
private struct NotificationBellBadgeOverlay: View {
|
||||
let unreadCount: Int
|
||||
let bellFrame: CGRect?
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
if unreadCount > 0, let bellFrame {
|
||||
let rootFrame = proxy.frame(in: .global)
|
||||
|
||||
Text("\(min(unreadCount, 9))")
|
||||
.font(.caption2.weight(.bold))
|
||||
.foregroundStyle(.white)
|
||||
.frame(minWidth: 18, minHeight: 18)
|
||||
.padding(.horizontal, 3)
|
||||
.background(Color.orange, in: Capsule())
|
||||
.position(
|
||||
x: bellFrame.maxX - rootFrame.minX - 2,
|
||||
y: bellFrame.minY - rootFrame.minY + 2
|
||||
)
|
||||
}
|
||||
}
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
private struct HomeSectionScreen: View {
|
||||
@ObservedObject var model: AppViewModel
|
||||
let section: AppSection
|
||||
@@ -876,25 +921,33 @@ private struct NotificationBellButton: View {
|
||||
Button {
|
||||
model.isNotificationCenterPresented = true
|
||||
} label: {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Image(systemName: model.unreadNotificationCount == 0 ? "bell" : "bell.badge.fill")
|
||||
.font(.headline)
|
||||
.foregroundStyle(model.unreadNotificationCount == 0 ? .primary : dashboardAccent)
|
||||
|
||||
if model.unreadNotificationCount > 0 {
|
||||
Text("\(min(model.unreadNotificationCount, 9))")
|
||||
.font(.caption2.weight(.bold))
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.orange, in: Capsule())
|
||||
.foregroundStyle(.white)
|
||||
.offset(x: 10, y: -10)
|
||||
Image(systemName: imageName)
|
||||
.font(.headline)
|
||||
.foregroundStyle(iconTone)
|
||||
.frame(width: 28, height: 28, alignment: .center)
|
||||
.background(alignment: .center) {
|
||||
#if os(iOS)
|
||||
GeometryReader { proxy in
|
||||
Color.clear
|
||||
.preference(key: NotificationBellFrameKey.self, value: proxy.frame(in: .global))
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.frame(width: 28, height: 28)
|
||||
}
|
||||
.accessibilityLabel("Notifications")
|
||||
}
|
||||
|
||||
private var imageName: String {
|
||||
#if os(iOS)
|
||||
model.unreadNotificationCount == 0 ? "bell" : "bell.fill"
|
||||
#else
|
||||
model.unreadNotificationCount == 0 ? "bell" : "bell.badge.fill"
|
||||
#endif
|
||||
}
|
||||
|
||||
private var iconTone: some ShapeStyle {
|
||||
model.unreadNotificationCount == 0 ? Color.primary : dashboardAccent
|
||||
}
|
||||
}
|
||||
|
||||
private struct NotificationCenterSheet: View {
|
||||
|
||||
Reference in New Issue
Block a user