Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
4fe7fde9b9
commit
8596a699c1
@ -8,10 +8,53 @@
|
||||
import SwiftUI
|
||||
import Bedrock
|
||||
|
||||
// MARK: - Device Size Tier
|
||||
|
||||
/// Categorizes devices by screen size for adaptive layouts.
|
||||
/// Primarily used to adjust card dimensions on smaller iPhones and constrain width on iPad.
|
||||
enum DeviceSizeTier {
|
||||
case compact // iPhone SE, Mini, older 4.7" devices (height < 700pt)
|
||||
case standard // Regular iPhone (height 700-850pt)
|
||||
case large // Pro Max, Plus models (height > 850pt)
|
||||
case tablet // iPad
|
||||
|
||||
/// The current device's size tier based on screen dimensions.
|
||||
static var current: DeviceSizeTier {
|
||||
let bounds = UIScreen.main.bounds
|
||||
let height = max(bounds.width, bounds.height)
|
||||
let width = min(bounds.width, bounds.height)
|
||||
|
||||
// iPad detection: width >= 768pt in portrait
|
||||
if width >= 768 {
|
||||
return .tablet
|
||||
}
|
||||
|
||||
switch height {
|
||||
case ..<700:
|
||||
return .compact
|
||||
case ..<850:
|
||||
return .standard
|
||||
default:
|
||||
return .large
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the current device is an iPad.
|
||||
static var isTablet: Bool {
|
||||
current == .tablet
|
||||
}
|
||||
|
||||
/// Whether the current device is a compact iPhone (SE, Mini).
|
||||
static var isCompact: Bool {
|
||||
current == .compact
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - App-Specific Sizes
|
||||
|
||||
extension Design {
|
||||
/// BusinessCard-specific size constants.
|
||||
/// Some values adapt based on device size tier.
|
||||
enum CardSize {
|
||||
static let cardWidth: CGFloat = 320
|
||||
static let cardHeight: CGFloat = 340
|
||||
@ -20,7 +63,17 @@ extension Design {
|
||||
static let avatarLarge: CGFloat = 80
|
||||
static let avatarOverlap: CGFloat = 40
|
||||
static let logoSize: CGFloat = 64
|
||||
static let bannerHeight: CGFloat = 240
|
||||
|
||||
/// Banner height adapts to device size to prevent dominating small screens.
|
||||
static var bannerHeight: CGFloat {
|
||||
switch DeviceSizeTier.current {
|
||||
case .compact: return 180
|
||||
case .standard: return 210
|
||||
case .large: return 240
|
||||
case .tablet: return 240
|
||||
}
|
||||
}
|
||||
|
||||
static let qrSize: CGFloat = 200
|
||||
static let qrSizeLarge: CGFloat = 260
|
||||
static let colorSwatchSize: CGFloat = 40
|
||||
@ -29,10 +82,14 @@ extension Design {
|
||||
static let widgetPhoneHeight: CGFloat = 120
|
||||
static let widgetWatchSize: CGFloat = 100
|
||||
static let floatingButtonSize: CGFloat = 56
|
||||
/// Bottom offset for floating button above tab bar.
|
||||
static let floatingButtonBottomOffset: CGFloat = 72
|
||||
/// Aspect ratio for logo container (3:2 landscape).
|
||||
static let logoContainerAspectRatio: CGFloat = 3.0 / 2.0
|
||||
|
||||
/// Maximum card width to prevent stretching on iPad.
|
||||
/// On iPhone this returns nil (no constraint needed).
|
||||
static var maxCardWidth: CGFloat? {
|
||||
DeviceSizeTier.isTablet ? 500 : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1082,6 +1082,8 @@ private struct CardPreviewSheet: View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
BusinessCardView(card: card)
|
||||
.frame(maxWidth: Design.CardSize.maxCardWidth)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(Design.Spacing.large)
|
||||
}
|
||||
.background(Color.AppBackground.base)
|
||||
|
||||
@ -98,7 +98,9 @@ private struct CardPageView: View {
|
||||
ScrollView {
|
||||
VStack(spacing: Design.Spacing.large) {
|
||||
BusinessCardView(card: card)
|
||||
.frame(maxWidth: Design.CardSize.maxCardWidth)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.padding(.vertical, Design.Spacing.xLarge)
|
||||
}
|
||||
|
||||
@ -9,26 +9,27 @@ struct RootTabView: View {
|
||||
var body: some View {
|
||||
@Bindable var appState = appState
|
||||
|
||||
ZStack(alignment: .bottom) {
|
||||
TabView(selection: $appState.selectedTab) {
|
||||
Tab(String.localized("My Cards"), systemImage: "rectangle.stack", value: AppTab.cards) {
|
||||
CardsHomeView()
|
||||
}
|
||||
|
||||
Tab(String.localized("Contacts"), systemImage: "person.2", value: AppTab.contacts) {
|
||||
ContactsView()
|
||||
}
|
||||
|
||||
Tab(String.localized("Widgets"), systemImage: "square.grid.2x2", value: AppTab.widgets) {
|
||||
WidgetsView()
|
||||
}
|
||||
TabView(selection: $appState.selectedTab) {
|
||||
Tab(String.localized("My Cards"), systemImage: "rectangle.stack", value: AppTab.cards) {
|
||||
CardsHomeView()
|
||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||
// Floating share button positioned above tab bar
|
||||
if !appState.cardStore.cards.isEmpty {
|
||||
FloatingShareButton {
|
||||
showingShareSheet = true
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.bottom, Design.Spacing.small)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Floating share button - only shown on cards tab when cards exist
|
||||
if appState.selectedTab == .cards && !appState.cardStore.cards.isEmpty {
|
||||
FloatingShareButton {
|
||||
showingShareSheet = true
|
||||
}
|
||||
Tab(String.localized("Contacts"), systemImage: "person.2", value: AppTab.contacts) {
|
||||
ContactsView()
|
||||
}
|
||||
|
||||
Tab(String.localized("Widgets"), systemImage: "square.grid.2x2", value: AppTab.widgets) {
|
||||
WidgetsView()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingShareSheet) {
|
||||
@ -62,7 +63,6 @@ private struct FloatingShareButton: View {
|
||||
}
|
||||
.accessibilityLabel(String.localized("Share"))
|
||||
.accessibilityHint(String.localized("Opens the share sheet to send your card"))
|
||||
.padding(.bottom, Design.CardSize.floatingButtonBottomOffset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user