Extend glass background and deepen 3D cards

This commit is contained in:
Rafal Kolarz 2026-10-23 01:28:42 +01:00
parent 79ce995af2
commit 6b19d78f53
7 changed files with 119 additions and 50 deletions

View File

@ -292,7 +292,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 12; CURRENT_PROJECT_VERSION = 13;
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Kasztelan/Info.plist; INFOPLIST_FILE = Kasztelan/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink";
@ -316,7 +316,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 12; CURRENT_PROJECT_VERSION = 13;
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Kasztelan/Info.plist; INFOPLIST_FILE = Kasztelan/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.food-and-drink";

View File

@ -89,7 +89,19 @@ struct CardView: View {
} }
private func loyaltyCard(compact: Bool) -> some View { private func loyaltyCard(compact: Bool) -> some View {
return VStack(spacing: compact ? 10 : 20) { GlassDepthDeck {
loyaltyCardFace(compact: compact)
}
.rotation3DEffect(
.degrees(-2.8),
axis: (x: 1, y: 0, z: 0),
anchor: .center,
perspective: 0.62
)
}
private func loyaltyCardFace(compact: Bool) -> some View {
VStack(spacing: compact ? 10 : 20) {
Image("Logo") Image("Logo")
.resizable() .resizable()
.scaledToFit() .scaledToFit()

View File

@ -4,17 +4,25 @@ struct MainTabView: View {
@EnvironmentObject private var model: AppModel @EnvironmentObject private var model: AppModel
var body: some View { var body: some View {
TabView(selection: $model.selectedTab) { ZStack {
NavigationStack { WalletView() } AmbientBackground()
.tabItem { Label(model.text("Karta", "Card"), systemImage: "creditcard.fill") }
.tag(0) TabView(selection: $model.selectedTab) {
NavigationStack { MessagesView() } NavigationStack { WalletView() }
.tabItem { Label(model.text("Wiadomości", "Messages"), systemImage: "envelope.fill") } .toolbarBackground(.hidden, for: .navigationBar)
.badge(model.messages.count) .tabItem { Label(model.text("Karta", "Card"), systemImage: "creditcard.fill") }
.tag(1) .tag(0)
NavigationStack { ProfileView() } NavigationStack { MessagesView() }
.tabItem { Label(model.text("Profil", "Profile"), systemImage: "person.crop.circle.fill") } .toolbarBackground(.hidden, for: .navigationBar)
.tag(2) .tabItem { Label(model.text("Wiadomości", "Messages"), systemImage: "envelope.fill") }
.badge(model.messages.count)
.tag(1)
NavigationStack { ProfileView() }
.toolbarBackground(.hidden, for: .navigationBar)
.tabItem { Label(model.text("Profil", "Profile"), systemImage: "person.crop.circle.fill") }
.tag(2)
}
.background(.clear)
} }
.tint(KTheme.orange) .tint(KTheme.orange)
.toolbarBackground(.ultraThinMaterial, for: .tabBar) .toolbarBackground(.ultraThinMaterial, for: .tabBar)

View File

@ -29,6 +29,7 @@ struct MessagesView: View {
} }
.background(AmbientBackground()) .background(AmbientBackground())
.toolbar { AppToolbar() } .toolbar { AppToolbar() }
.toolbarBackground(.hidden, for: .navigationBar)
.refreshable { await model.refresh() } .refreshable { await model.refresh() }
.task { await model.refresh() } .task { await model.refresh() }
} }

View File

@ -53,6 +53,7 @@ struct ProfileView: View {
} }
.background(AmbientBackground()) .background(AmbientBackground())
.toolbar { AppToolbar() } .toolbar { AppToolbar() }
.toolbarBackground(.hidden, for: .navigationBar)
.fullScreenCover(isPresented: $editingProfile) { EditProfileView() } .fullScreenCover(isPresented: $editingProfile) { EditProfileView() }
.sheet(isPresented: $changingPassword) { ChangePasswordView() } .sheet(isPresented: $changingPassword) { ChangePasswordView() }
.sheet(isPresented: $deletingAccount) { .sheet(isPresented: $deletingAccount) {

View File

@ -59,6 +59,46 @@ struct AmbientBackground: View {
} }
} }
struct GlassDepthDeck<Content: View>: View {
private let cornerRadius: CGFloat
private let content: Content
init(cornerRadius: CGFloat = 26, @ViewBuilder content: () -> Content) {
self.cornerRadius = cornerRadius
self.content = content()
}
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.fill(.ultraThinMaterial)
.overlay {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.stroke(KTheme.purple.opacity(0.34), lineWidth: 1)
}
.scaleEffect(x: 0.88, y: 0.95)
.offset(y: 24)
.blur(radius: 1.2)
.opacity(0.62)
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.fill(.thinMaterial)
.overlay {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.stroke(KTheme.pink.opacity(0.32), lineWidth: 1)
}
.scaleEffect(x: 0.95, y: 0.98)
.offset(y: 13)
.opacity(0.80)
content
.shadow(color: .black.opacity(0.48), radius: 34, y: 22)
.shadow(color: KTheme.pink.opacity(0.26), radius: 22, y: 8)
}
.padding(.bottom, 24)
}
}
struct PanelModifier: ViewModifier { struct PanelModifier: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content

View File

@ -15,8 +15,9 @@ struct WalletView: View {
var body: some View { var body: some View {
walletCarousel walletCarousel
.background(KTheme.background) .background(.clear)
.toolbar { walletToolbar } .toolbar { walletToolbar }
.toolbarBackground(.hidden, for: .navigationBar)
.onAppear { selectedPage = 0 } .onAppear { selectedPage = 0 }
.task(id: model.user?.id) { await store.sync(using: model) } .task(id: model.user?.id) { await store.sync(using: model) }
.fullScreenCover(isPresented: $scannerPresented) { scannerView } .fullScreenCover(isPresented: $scannerPresented) { scannerView }
@ -166,7 +167,11 @@ struct WalletView: View {
} }
.padding(.horizontal, 12) .padding(.horizontal, 12)
.padding(.vertical, 8) .padding(.vertical, 8)
.background(.black.opacity(0.35), in: Capsule()) .background(.ultraThinMaterial, in: Capsule())
.overlay {
Capsule()
.stroke(.white.opacity(0.18), lineWidth: 1)
}
.padding(.bottom, 2) .padding(.bottom, 2)
.allowsHitTesting(false) .allowsHitTesting(false)
} }
@ -285,42 +290,44 @@ private struct DiscountCardView: View {
.accessibilityLabel(model.text("Usuń kartę", "Delete card")) .accessibilityLabel(model.text("Usuń kartę", "Delete card"))
} }
VStack(spacing: 18) { GlassDepthDeck {
StoredBarcodeView(value: card.payload, symbology: card.symbology) VStack(spacing: 18) {
.frame(maxWidth: .infinity) StoredBarcodeView(value: card.payload, symbology: card.symbology)
.frame(height: card.isTwoDimensional ? 220 : 132) .frame(maxWidth: .infinity)
.padding(14) .frame(height: card.isTwoDimensional ? 220 : 132)
.background(.white) .padding(14)
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) .background(.white)
Text(card.payload) .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
.font(.system(.headline, design: .monospaced).weight(.bold)) Text(card.payload)
.multilineTextAlignment(.center) .font(.system(.headline, design: .monospaced).weight(.bold))
.textSelection(.enabled) .multilineTextAlignment(.center)
.lineLimit(3) .textSelection(.enabled)
.minimumScaleFactor(0.65) .lineLimit(3)
} .minimumScaleFactor(0.65)
.frame(maxWidth: .infinity) }
.padding(22) .frame(maxWidth: .infinity)
.background( .padding(22)
ZStack { .background(
LinearGradient( ZStack {
colors: [KTheme.purple.opacity(0.92), KTheme.pink.opacity(0.88), KTheme.orange.opacity(0.86)], LinearGradient(
startPoint: .topLeading, colors: [KTheme.purple.opacity(0.92), KTheme.pink.opacity(0.88), KTheme.orange.opacity(0.86)],
endPoint: .bottomTrailing startPoint: .topLeading,
) endPoint: .bottomTrailing
Circle() )
.fill(.white.opacity(0.15)) Circle()
.frame(width: 170, height: 170) .fill(.white.opacity(0.15))
.blur(radius: 8) .frame(width: 170, height: 170)
.offset(x: 145, y: -100) .blur(radius: 8)
.offset(x: 145, y: -100)
}
)
.clipShape(RoundedRectangle(cornerRadius: 26, style: .continuous))
.overlay {
RoundedRectangle(cornerRadius: 26, style: .continuous)
.stroke(.white.opacity(0.28), lineWidth: 1)
} }
)
.clipShape(RoundedRectangle(cornerRadius: 26, style: .continuous))
.overlay {
RoundedRectangle(cornerRadius: 26, style: .continuous)
.stroke(.white.opacity(0.28), lineWidth: 1)
} }
.shadow(color: KTheme.pink.opacity(0.24), radius: 28, y: 16) .rotation3DEffect(.degrees(-2.8), axis: (x: 1, y: 0, z: 0), perspective: 0.62)
Text(model.text("Dostępna offline • zaszyfrowana synchronizacja", "Available offline • encrypted sync")) Text(model.text("Dostępna offline • zaszyfrowana synchronizacja", "Available offline • encrypted sync"))
.font(.footnote) .font(.footnote)