Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
179 lines
5.0 KiB
Swift
179 lines
5.0 KiB
Swift
import AffineResources
|
|
import SwiftUI
|
|
|
|
struct RolePage: View {
|
|
@Binding var selectedRole: OnboardingRole?
|
|
|
|
private let roles = OnboardingRole.allCases
|
|
|
|
var body: some View {
|
|
VStack(spacing: 24) {
|
|
Color.clear
|
|
.frame(height: 18)
|
|
VStack(spacing: 18) {
|
|
Text("Which best describes you?")
|
|
.font(.system(size: 26, weight: .bold, design: .rounded))
|
|
.multilineTextAlignment(.center)
|
|
Text("We'll use this to improve your experience and prioritize relevant features.")
|
|
.font(.system(size: 17, weight: .medium))
|
|
.foregroundStyle(AffineColors.textSecondary.color)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
VStack(spacing: 12) {
|
|
ForEach(roles, id: \.self) { role in
|
|
RoleOption(
|
|
role: role,
|
|
isSelected: selectedRole == role
|
|
) {
|
|
selectedRole = role
|
|
}
|
|
}
|
|
}
|
|
Spacer()
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FeaturePage: View {
|
|
let feature: OnboardingFeature
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
Spacer(minLength: 10)
|
|
VStack(spacing: 12) {
|
|
featureTitle
|
|
Text(LocalizedStringKey(feature.subtitle))
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundStyle(AffineColors.textSecondary.color)
|
|
.multilineTextAlignment(.center)
|
|
.lineSpacing(3)
|
|
.padding(.horizontal, 12)
|
|
}
|
|
|
|
Spacer(minLength: 18)
|
|
|
|
FeatureArtwork(feature: feature)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 360)
|
|
|
|
Spacer(minLength: 10)
|
|
}
|
|
}
|
|
|
|
private var featureTitle: some View {
|
|
feature.titleSegments.reduce(Text("")) { partial, segment in
|
|
partial
|
|
+ Text(LocalizedStringKey(segment.text))
|
|
.foregroundColor(segment.isHighlighted ? AffineColors.buttonPrimary.color : AffineColors.textPrimary.color)
|
|
}
|
|
.font(.system(size: 30, weight: .bold))
|
|
.multilineTextAlignment(.center)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.padding(.horizontal, 8)
|
|
}
|
|
}
|
|
|
|
private struct RoleOption: View {
|
|
let role: OnboardingRole
|
|
let isSelected: Bool
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
HStack(spacing: 16) {
|
|
Image(role.assetName)
|
|
.renderingMode(.template)
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: 38, height: 38)
|
|
.foregroundStyle(AffineColors.textPrimary.color)
|
|
Text(LocalizedStringKey(role.title))
|
|
.font(.system(size: 18, weight: .semibold))
|
|
.foregroundStyle(AffineColors.textPrimary.color)
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 20)
|
|
.frame(height: 84)
|
|
.background(isSelected ? AffineColors.buttonPrimary.color.opacity(0.12) : AffineColors.layerPureWhite.color)
|
|
.clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: 22, style: .continuous)
|
|
.stroke(
|
|
isSelected ? AffineColors.buttonPrimary.color : AffineColors.layerBorder.color.opacity(0.7),
|
|
lineWidth: isSelected ? 2 : 1
|
|
)
|
|
}
|
|
.shadow(color: .black.opacity(isSelected ? 0.05 : 0.04), radius: 12, x: 0, y: 6)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
|
|
private struct FeatureArtwork: View {
|
|
let feature: OnboardingFeature
|
|
|
|
var body: some View {
|
|
Image(feature.assetName)
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.padding(.horizontal, 4)
|
|
.accessibilityHidden(true)
|
|
}
|
|
}
|
|
|
|
struct PrimaryButton: View {
|
|
let title: String
|
|
var isLoading = false
|
|
var isEnabled = true
|
|
var fontSize: CGFloat = 16
|
|
var height: CGFloat = 54
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
HStack(spacing: 10) {
|
|
if isLoading {
|
|
ProgressView()
|
|
.tint(AffineColors.layerPureWhite.color)
|
|
}
|
|
Text(LocalizedStringKey(title))
|
|
.font(.system(size: fontSize, weight: .bold))
|
|
}
|
|
.foregroundStyle(AffineColors.layerPureWhite.color)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: height)
|
|
.background(isEnabled ? AffineColors.buttonPrimary.color : AffineColors.textPlaceholder.color)
|
|
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
|
|
}
|
|
.buttonStyle(.plain)
|
|
.disabled(isLoading || !isEnabled)
|
|
.opacity(isLoading ? 0.8 : (isEnabled ? 1 : 0.72))
|
|
}
|
|
}
|
|
|
|
struct PageDots: View {
|
|
let count: Int
|
|
let selectedIndex: Int
|
|
|
|
private let dotHeight: CGFloat = 6
|
|
private let dotSpacing: CGFloat = 12
|
|
|
|
var body: some View {
|
|
HStack(spacing: dotSpacing) {
|
|
ForEach(0..<count, id: \.self) { index in
|
|
Capsule()
|
|
.fill(index == selectedIndex ? AffineColors.buttonPrimary.color : AffineColors.buttonPrimary.color.opacity(0.18))
|
|
.frame(height: dotHeight)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|
|
.frame(height: dotHeight)
|
|
.animation(.spring(response: 0.35, dampingFraction: 0.8), value: selectedIndex)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
OnboardingRootView(onCompleteOnboarding: {})
|
|
}
|