feat: Update text color to improve readability across multiple authentication views
This commit is contained in:
81
Rosetta/DesignSystem/Components/GlassModifier.swift
Normal file
81
Rosetta/DesignSystem/Components/GlassModifier.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Glass Modifier (5-layer glass that works on black)
|
||||
//
|
||||
// Layer stack:
|
||||
// 1. .ultraThinMaterial — system blur
|
||||
// 2. black.opacity(0.22) — dark tint (depth on dark mode)
|
||||
// 3. white→clear gradient — highlight / light refraction, blendMode(.screen)
|
||||
// 4. double stroke — outer weak + inner stronger = glass edge
|
||||
// 5. shadow — depth
|
||||
|
||||
struct GlassModifier: ViewModifier {
|
||||
let cornerRadius: CGFloat
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
||||
|
||||
if #available(iOS 26, *) {
|
||||
content
|
||||
.background {
|
||||
shape.fill(.clear)
|
||||
.glassEffect(.regular, in: .rect(cornerRadius: cornerRadius))
|
||||
}
|
||||
} else {
|
||||
content
|
||||
.background {
|
||||
ZStack {
|
||||
shape.fill(.ultraThinMaterial)
|
||||
shape.fill(Color.black.opacity(0.22))
|
||||
shape.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.14), .clear],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
).blendMode(.screen)
|
||||
shape.stroke(Color.white.opacity(0.10), lineWidth: 1)
|
||||
shape.stroke(Color.white.opacity(0.18), lineWidth: 1).padding(1.5)
|
||||
}
|
||||
.shadow(color: Color.black.opacity(0.45), radius: 22, y: 14)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - View Extension
|
||||
|
||||
extension View {
|
||||
/// 5-layer frosted glass background.
|
||||
func glass(cornerRadius: CGFloat = 24) -> some View {
|
||||
modifier(GlassModifier(cornerRadius: cornerRadius))
|
||||
}
|
||||
|
||||
/// Glass capsule — convenience for pill-shaped elements.
|
||||
@ViewBuilder
|
||||
func glassCapsule() -> some View {
|
||||
if #available(iOS 26, *) {
|
||||
background {
|
||||
Capsule().fill(.clear)
|
||||
.glassEffect(.regular, in: .capsule)
|
||||
}
|
||||
} else {
|
||||
background {
|
||||
ZStack {
|
||||
Capsule().fill(.ultraThinMaterial)
|
||||
Capsule().fill(Color.black.opacity(0.22))
|
||||
Capsule().fill(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.14), .clear],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
).blendMode(.screen)
|
||||
Capsule().stroke(Color.white.opacity(0.10), lineWidth: 1)
|
||||
Capsule().stroke(Color.white.opacity(0.18), lineWidth: 1).padding(1.5)
|
||||
}
|
||||
.shadow(color: Color.black.opacity(0.45), radius: 22, y: 14)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,24 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
// MARK: - Glass Effect ID Modifier (iOS 26+)
|
||||
|
||||
private struct GlassEffectIDModifier: ViewModifier {
|
||||
let id: String
|
||||
let namespace: Namespace.ID?
|
||||
|
||||
nonisolated func body(content: Content) -> some View {
|
||||
if #available(iOS 26, *), let namespace {
|
||||
content.glassEffectID(id, in: namespace)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tab
|
||||
|
||||
enum RosettaTab: CaseIterable {
|
||||
enum RosettaTab: CaseIterable, Sendable {
|
||||
case chats
|
||||
case settings
|
||||
case search
|
||||
@@ -41,26 +56,33 @@ struct TabBadge {
|
||||
}
|
||||
|
||||
// MARK: - RosettaTabBar
|
||||
/// Figma spec:
|
||||
/// Container: padding(25h, 16t, 25b), gap=8
|
||||
/// Main pill: 282x62, r=296, padding(4h, 3v), glass+shadow
|
||||
/// Each tab: 99x56, icon 30pt, label 10pt
|
||||
/// Selected: #EDEDED rect r=100, icon+label #008BFF, label bold
|
||||
/// Unselected: icon+label #404040
|
||||
/// Search pill: 62x62, glass+shadow, icon 17pt #404040
|
||||
|
||||
struct RosettaTabBar: View {
|
||||
let selectedTab: RosettaTab
|
||||
var onTabSelected: ((RosettaTab) -> Void)?
|
||||
var badges: [TabBadge] = []
|
||||
|
||||
@Namespace private var glassNS
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 26, *) {
|
||||
GlassEffectContainer(spacing: 8) {
|
||||
tabBarContent
|
||||
}
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.top, 4)
|
||||
} else {
|
||||
tabBarContent
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
||||
private var tabBarContent: some View {
|
||||
HStack(spacing: 8) {
|
||||
mainTabsPill
|
||||
searchPill
|
||||
}
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,145 +90,314 @@ struct RosettaTabBar: View {
|
||||
|
||||
private extension RosettaTabBar {
|
||||
var mainTabsPill: some View {
|
||||
// Content on top — NOT clipped (lens can pop out)
|
||||
HStack(spacing: 0) {
|
||||
ForEach(RosettaTab.allCases.filter { $0 != .search }, id: \.self) { tab in
|
||||
tabItem(tab)
|
||||
TabItemButton(
|
||||
tab: tab,
|
||||
isSelected: tab == selectedTab,
|
||||
badgeText: badges.first(where: { $0.tab == tab })?.text,
|
||||
onTap: { onTabSelected?(tab) },
|
||||
glassNamespace: glassNS
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.top, 3)
|
||||
.padding(.bottom, 3)
|
||||
.frame(height: 62)
|
||||
.applyGlassPill()
|
||||
// Background clipped separately — content stays unclipped
|
||||
.background {
|
||||
mainPillGlass
|
||||
}
|
||||
}
|
||||
|
||||
func tabItem(_ tab: RosettaTab) -> some View {
|
||||
let isSelected = tab == selectedTab
|
||||
let badgeText = badges.first(where: { $0.tab == tab })?.text
|
||||
|
||||
return Button {
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
||||
onTabSelected?(tab)
|
||||
@ViewBuilder
|
||||
var mainPillGlass: some View {
|
||||
if #available(iOS 26, *) {
|
||||
Capsule().fill(.clear).glassEffect(.regular, in: .capsule)
|
||||
} else {
|
||||
ZStack {
|
||||
// 1. Material
|
||||
Capsule().fill(.ultraThinMaterial)
|
||||
// 2. Dark tint
|
||||
Capsule().fill(Color.black.opacity(0.22))
|
||||
// 3. Highlight
|
||||
Capsule().fill(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.14), .clear],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
).blendMode(.screen)
|
||||
// 4a. Outer stroke
|
||||
Capsule().stroke(Color.white.opacity(0.10), lineWidth: 1)
|
||||
// 4b. Inner stroke
|
||||
Capsule().stroke(Color.white.opacity(0.18), lineWidth: 1).padding(1.5)
|
||||
}
|
||||
} label: {
|
||||
// 5. Shadows
|
||||
.shadow(color: Color.black.opacity(0.45), radius: 22, y: 14)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tab Item Button
|
||||
|
||||
private struct TabItemButton: View {
|
||||
let tab: RosettaTab
|
||||
let isSelected: Bool
|
||||
let badgeText: String?
|
||||
let onTap: () -> Void
|
||||
var glassNamespace: Namespace.ID?
|
||||
|
||||
@State private var pressed = false
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
VStack(spacing: 1) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Image(systemName: isSelected ? tab.selectedIcon : tab.icon)
|
||||
.font(.system(size: 22))
|
||||
.foregroundStyle(isSelected ? RosettaColors.primaryBlue : RosettaColors.adaptive(light: Color(hex: 0x404040), dark: Color(hex: 0x8E8E93)))
|
||||
.foregroundStyle(tabColor)
|
||||
.frame(height: 30)
|
||||
|
||||
if let badgeText {
|
||||
badgeView(badgeText)
|
||||
Text(badgeText)
|
||||
.font(.system(size: 10, weight: .medium))
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, badgeText.count > 2 ? 4 : 0)
|
||||
.frame(minWidth: 18, minHeight: 18)
|
||||
.background(Capsule().fill(RosettaColors.error))
|
||||
.offset(x: 10, y: -4)
|
||||
}
|
||||
}
|
||||
|
||||
Text(tab.label)
|
||||
.font(.system(size: 10, weight: isSelected ? .bold : .medium))
|
||||
.foregroundStyle(isSelected ? RosettaColors.primaryBlue : RosettaColors.adaptive(light: Color(hex: 0x404040), dark: Color(hex: 0x8E8E93)))
|
||||
.foregroundStyle(tabColor)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.top, 6)
|
||||
.padding(.bottom, 7)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background {
|
||||
if isSelected {
|
||||
if isSelected && !pressed {
|
||||
RoundedRectangle(cornerRadius: 100)
|
||||
.fill(RosettaColors.adaptive(
|
||||
light: Color(hex: 0xEDEDED),
|
||||
dark: Color.white.opacity(0.12)
|
||||
))
|
||||
.padding(.horizontal, -8)
|
||||
.padding(.vertical, -6)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
// Lens: padding → glass bubble → scale → lift
|
||||
.padding(14)
|
||||
.background {
|
||||
if pressed {
|
||||
lensBubble
|
||||
.transition(.scale(scale: 0.8).combined(with: .opacity))
|
||||
}
|
||||
}
|
||||
.scaleEffect(pressed ? 1.12 : 1)
|
||||
.offset(y: pressed ? -28 : 0)
|
||||
.shadow(color: .black.opacity(pressed ? 0.45 : 0), radius: 22, y: 14)
|
||||
.shadow(color: Color.cyan.opacity(pressed ? 0.12 : 0), radius: 20, y: 0)
|
||||
.animation(.spring(response: 0.34, dampingFraction: 0.65), value: pressed)
|
||||
.simultaneousGesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { _ in
|
||||
if !pressed {
|
||||
pressed = true
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
}
|
||||
}
|
||||
.onEnded { _ in pressed = false }
|
||||
)
|
||||
.modifier(GlassEffectIDModifier(id: "\(tab)", namespace: glassNamespace))
|
||||
.accessibilityLabel(tab.label)
|
||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||
}
|
||||
|
||||
private var tabColor: Color {
|
||||
isSelected
|
||||
? RosettaColors.primaryBlue
|
||||
: RosettaColors.adaptive(
|
||||
light: Color(hex: 0x404040),
|
||||
dark: Color(hex: 0x8E8E93)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Lens Bubble
|
||||
|
||||
@ViewBuilder
|
||||
private var lensBubble: some View {
|
||||
if #available(iOS 26, *) {
|
||||
Circle()
|
||||
.fill(.clear)
|
||||
.glassEffect(.regular.interactive(), in: .circle)
|
||||
} else {
|
||||
ZStack {
|
||||
// 1. Material
|
||||
Circle().fill(.ultraThinMaterial)
|
||||
// 2. Dark tint
|
||||
Circle().fill(Color.black.opacity(0.22))
|
||||
// 3. Highlight (top→bottom, screen blend)
|
||||
Circle().fill(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.14), .clear],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
).blendMode(.screen)
|
||||
// 4a. Outer stroke
|
||||
Circle().stroke(Color.white.opacity(0.10), lineWidth: 1)
|
||||
// 4b. Inner stroke
|
||||
Circle().stroke(Color.white.opacity(0.18), lineWidth: 1).padding(1.5)
|
||||
// 6. Rainbow (thin, subtle, screen blend)
|
||||
Circle().stroke(
|
||||
AngularGradient(
|
||||
colors: [
|
||||
Color.cyan.opacity(0.55),
|
||||
Color.blue.opacity(0.55),
|
||||
Color.purple.opacity(0.55),
|
||||
Color.pink.opacity(0.55),
|
||||
Color.orange.opacity(0.55),
|
||||
Color.yellow.opacity(0.45),
|
||||
Color.cyan.opacity(0.55),
|
||||
],
|
||||
center: .center
|
||||
),
|
||||
lineWidth: 1.4
|
||||
).blendMode(.screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Search Pill
|
||||
|
||||
private extension RosettaTabBar {
|
||||
var searchPill: some View {
|
||||
let isSelected = selectedTab == .search
|
||||
SearchPillButton(
|
||||
isSelected: selectedTab == .search,
|
||||
onTap: { onTabSelected?(.search) },
|
||||
glassNamespace: glassNS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return Button {
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
||||
onTabSelected?(.search)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: isSelected ? "magnifyingglass" : "magnifyingglass")
|
||||
private struct SearchPillButton: View {
|
||||
let isSelected: Bool
|
||||
let onTap: () -> Void
|
||||
var glassNamespace: Namespace.ID?
|
||||
|
||||
@State private var pressed = false
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.foregroundStyle(isSelected ? RosettaColors.primaryBlue : RosettaColors.adaptive(light: Color(hex: 0x404040), dark: Color(hex: 0x8E8E93)))
|
||||
.frame(width: 54, height: 54)
|
||||
.background {
|
||||
if isSelected {
|
||||
Circle()
|
||||
.fill(RosettaColors.adaptive(
|
||||
light: Color(hex: 0xEDEDED),
|
||||
dark: Color.white.opacity(0.12)
|
||||
))
|
||||
}
|
||||
}
|
||||
.foregroundStyle(
|
||||
isSelected
|
||||
? RosettaColors.primaryBlue
|
||||
: RosettaColors.adaptive(
|
||||
light: Color(hex: 0x404040),
|
||||
dark: Color(hex: 0x8E8E93)
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(4)
|
||||
// Lens
|
||||
.padding(14)
|
||||
.background {
|
||||
if pressed {
|
||||
searchLensBubble
|
||||
.transition(.scale(scale: 0.8).combined(with: .opacity))
|
||||
}
|
||||
}
|
||||
.scaleEffect(pressed ? 1.15 : 1)
|
||||
.offset(y: pressed ? -28 : 0)
|
||||
.shadow(color: .black.opacity(pressed ? 0.45 : 0), radius: 22, y: 14)
|
||||
.shadow(color: Color.cyan.opacity(pressed ? 0.12 : 0), radius: 20, y: 0)
|
||||
.animation(.spring(response: 0.34, dampingFraction: 0.65), value: pressed)
|
||||
.simultaneousGesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { _ in
|
||||
if !pressed {
|
||||
pressed = true
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
}
|
||||
}
|
||||
.onEnded { _ in pressed = false }
|
||||
)
|
||||
.frame(width: 62, height: 62)
|
||||
.applyGlassPill()
|
||||
// Background clipped separately
|
||||
.background { searchPillGlass }
|
||||
.modifier(GlassEffectIDModifier(id: "search", namespace: glassNamespace))
|
||||
.accessibilityLabel("Search")
|
||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Glass Pill
|
||||
// MARK: Lens for search
|
||||
|
||||
private struct GlassPillModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
@ViewBuilder
|
||||
private var searchLensBubble: some View {
|
||||
if #available(iOS 26, *) {
|
||||
content
|
||||
.glassEffect(.regular, in: .capsule)
|
||||
Circle()
|
||||
.fill(.clear)
|
||||
.glassEffect(.regular.interactive(), in: .circle)
|
||||
} else {
|
||||
content
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(RosettaColors.adaptive(
|
||||
light: Color.white.opacity(0.65),
|
||||
dark: Color(hex: 0x2A2A2A).opacity(0.8)
|
||||
))
|
||||
.shadow(color: RosettaColors.adaptive(
|
||||
light: Color(hex: 0xDDDDDD).opacity(0.5),
|
||||
dark: Color.black.opacity(0.3)
|
||||
), radius: 16, y: 4)
|
||||
)
|
||||
ZStack {
|
||||
Circle().fill(.ultraThinMaterial)
|
||||
Circle().fill(Color.black.opacity(0.22))
|
||||
Circle().fill(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.14), .clear],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
).blendMode(.screen)
|
||||
Circle().stroke(Color.white.opacity(0.10), lineWidth: 1)
|
||||
Circle().stroke(Color.white.opacity(0.18), lineWidth: 1).padding(1.5)
|
||||
Circle().stroke(
|
||||
AngularGradient(
|
||||
colors: [
|
||||
Color.cyan.opacity(0.55),
|
||||
Color.blue.opacity(0.55),
|
||||
Color.purple.opacity(0.55),
|
||||
Color.pink.opacity(0.55),
|
||||
Color.orange.opacity(0.55),
|
||||
Color.yellow.opacity(0.45),
|
||||
Color.cyan.opacity(0.55),
|
||||
],
|
||||
center: .center
|
||||
),
|
||||
lineWidth: 1.4
|
||||
).blendMode(.screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension View {
|
||||
func applyGlassPill() -> some View {
|
||||
modifier(GlassPillModifier())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private extension RosettaTabBar {
|
||||
func badgeView(_ text: String) -> some View {
|
||||
Text(text)
|
||||
.font(.system(size: 10, weight: .medium))
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, text.count > 2 ? 4 : 0)
|
||||
.frame(minWidth: 18, minHeight: 18)
|
||||
.background(Capsule().fill(RosettaColors.error))
|
||||
.offset(x: 10, y: -4)
|
||||
}
|
||||
|
||||
var safeAreaBottom: CGFloat {
|
||||
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = scene.windows.first(where: \.isKeyWindow) else { return 0 }
|
||||
return window.safeAreaInsets.bottom
|
||||
@ViewBuilder
|
||||
private var searchPillGlass: some View {
|
||||
if #available(iOS 26, *) {
|
||||
Circle().fill(.clear).glassEffect(.regular, in: .circle)
|
||||
} else {
|
||||
ZStack {
|
||||
Circle().fill(.ultraThinMaterial)
|
||||
Circle().fill(Color.black.opacity(0.22))
|
||||
Circle().fill(
|
||||
LinearGradient(
|
||||
colors: [Color.white.opacity(0.14), .clear],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
).blendMode(.screen)
|
||||
Circle().stroke(Color.white.opacity(0.10), lineWidth: 1)
|
||||
Circle().stroke(Color.white.opacity(0.18), lineWidth: 1).padding(1.5)
|
||||
}
|
||||
.shadow(color: Color.black.opacity(0.45), radius: 22, y: 14)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,12 +405,12 @@ private extension RosettaTabBar {
|
||||
|
||||
#Preview {
|
||||
ZStack(alignment: .bottom) {
|
||||
RosettaColors.Adaptive.background.ignoresSafeArea()
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
Text("Content here")
|
||||
.foregroundStyle(RosettaColors.Adaptive.text)
|
||||
Text("Hold a tab to see the lens")
|
||||
.foregroundStyle(.white.opacity(0.5))
|
||||
Spacer()
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ private extension ConfirmSeedPhraseView {
|
||||
|
||||
Text("Enter words #2, #5, #9, #12 to confirm\nyou've backed up your phrase.")
|
||||
.font(.system(size: 15))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(3)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ private extension ImportSeedPhraseView {
|
||||
|
||||
Text("Enter your 12-word recovery phrase\nto restore your account.")
|
||||
.font(.system(size: 15))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(3)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ struct WeakPasswordWarning: View {
|
||||
|
||||
Text("Your password is too weak. Consider using at least 6 characters for better security.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.lineSpacing(2)
|
||||
}
|
||||
.padding(14)
|
||||
|
||||
@@ -54,7 +54,7 @@ private extension SeedPhraseView {
|
||||
|
||||
Text("Write down these 12 words in order.\nYou'll need them to restore your account.")
|
||||
.font(.system(size: 15))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(3)
|
||||
.opacity(isContentVisible ? 1.0 : 0.0)
|
||||
|
||||
@@ -103,7 +103,7 @@ private extension SetPasswordView {
|
||||
? "Set a password to protect your recovered account.\nYou'll need it to unlock Rosetta."
|
||||
: "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.")
|
||||
.font(.system(size: 14))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(2)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ private extension SetPasswordView {
|
||||
|
||||
Image(systemName: isRevealed ? "eye.slash" : "eye")
|
||||
.font(.system(size: 16))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.5))
|
||||
.frame(width: 30, height: 30)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
@@ -218,7 +218,7 @@ private extension SetPasswordView {
|
||||
|
||||
Text("Your password is never stored or sent anywhere. It's only used to encrypt your keys locally.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.lineSpacing(2)
|
||||
}
|
||||
.padding(16)
|
||||
|
||||
@@ -79,7 +79,7 @@ struct UnlockView: View {
|
||||
// Subtitle — matching Android
|
||||
Text("For unlock account enter password")
|
||||
.font(.system(size: 15))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.opacity(showSubtitle ? 1 : 0)
|
||||
.offset(y: showSubtitle ? 0 : 8)
|
||||
|
||||
@@ -136,7 +136,7 @@ private extension UnlockView {
|
||||
|
||||
Image(systemName: showPassword ? "eye.slash" : "eye")
|
||||
.font(.system(size: 18))
|
||||
.foregroundStyle(Color(white: 0.45))
|
||||
.foregroundStyle(Color.white.opacity(0.5))
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { showPassword.toggle() }
|
||||
}
|
||||
@@ -195,7 +195,7 @@ private extension UnlockView {
|
||||
VStack(spacing: 4) {
|
||||
HStack(spacing: 0) {
|
||||
Text("You can also ")
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
|
||||
Button {
|
||||
onCreateNewAccount?()
|
||||
@@ -207,13 +207,13 @@ private extension UnlockView {
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Text(" or")
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
}
|
||||
.font(.system(size: 15))
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("create a ")
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
|
||||
Button {
|
||||
onCreateNewAccount?()
|
||||
|
||||
@@ -90,7 +90,7 @@ private extension WelcomeView {
|
||||
var subtitleSection: some View {
|
||||
Text("Secure messaging with\ncryptographic keys")
|
||||
.font(.system(size: 16))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.opacity(isVisible ? 1.0 : 0.0)
|
||||
.offset(y: isVisible ? 0 : 12)
|
||||
@@ -122,7 +122,7 @@ private extension WelcomeView {
|
||||
|
||||
Text(label)
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.foregroundStyle(RosettaColors.secondaryText)
|
||||
.foregroundStyle(Color.white.opacity(0.7))
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel(label)
|
||||
|
||||
Reference in New Issue
Block a user