|
|
@@ -3,17 +3,32 @@ import SwiftUI
|
|
|
struct PunctuationCheckerView: View {
|
|
|
@StateObject private var viewModel = PunctuationCheckerViewModel()
|
|
|
|
|
|
- private var showsOutputLayout: Bool {
|
|
|
- !viewModel.correctedText.isEmpty || viewModel.isChecking
|
|
|
- }
|
|
|
-
|
|
|
var body: some View {
|
|
|
GeometryReader { proxy in
|
|
|
let dynamicHorizontalPadding = min(max(proxy.size.width * 0.045, AppTheme.contentPadding), 52)
|
|
|
let dynamicBottomPadding = min(max(proxy.size.height * 0.04, 20), 40)
|
|
|
|
|
|
Group {
|
|
|
- if !showsOutputLayout {
|
|
|
+ if viewModel.isChecking {
|
|
|
+ loadingLayout(
|
|
|
+ horizontalPadding: dynamicHorizontalPadding,
|
|
|
+ topPadding: AppTheme.brandLabelTopInset,
|
|
|
+ bottomPadding: dynamicBottomPadding
|
|
|
+ )
|
|
|
+ } else if viewModel.hasResults {
|
|
|
+ VStack(alignment: .leading, spacing: 0) {
|
|
|
+ header
|
|
|
+ .padding(.bottom, 20)
|
|
|
+
|
|
|
+ resultsLayout
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
+ }
|
|
|
+ .frame(maxWidth: 1200, maxHeight: .infinity, alignment: .topLeading)
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
+ .padding(.horizontal, dynamicHorizontalPadding)
|
|
|
+ .padding(.top, AppTheme.brandLabelTopInset)
|
|
|
+ .padding(.bottom, dynamicBottomPadding)
|
|
|
+ } else {
|
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
|
header
|
|
|
.padding(.bottom, 24)
|
|
|
@@ -39,40 +54,6 @@ struct PunctuationCheckerView: View {
|
|
|
.padding(.horizontal, dynamicHorizontalPadding)
|
|
|
.padding(.top, AppTheme.brandLabelTopInset)
|
|
|
.padding(.bottom, dynamicBottomPadding)
|
|
|
- } else {
|
|
|
- VStack(alignment: .leading, spacing: 0) {
|
|
|
- header
|
|
|
- .padding(.bottom, 24)
|
|
|
-
|
|
|
- ScrollView(.vertical, showsIndicators: false) {
|
|
|
- VStack(alignment: .leading, spacing: 0) {
|
|
|
- contentCard
|
|
|
- .padding(.bottom, 24)
|
|
|
-
|
|
|
- outputCard
|
|
|
- .padding(.bottom, 24)
|
|
|
-
|
|
|
- if let errorMessage = viewModel.errorMessage {
|
|
|
- Text(errorMessage)
|
|
|
- .font(.system(size: 14))
|
|
|
- .foregroundStyle(.red)
|
|
|
- .padding(.bottom, 12)
|
|
|
- }
|
|
|
-
|
|
|
- GradientButton(title: viewModel.isChecking ? "Checking..." : "Check Punctuation") {
|
|
|
- viewModel.checkPunctuation()
|
|
|
- }
|
|
|
- .disabled(!viewModel.canCheck)
|
|
|
- }
|
|
|
- .padding(.bottom, dynamicBottomPadding)
|
|
|
- }
|
|
|
- .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
- }
|
|
|
- .frame(maxWidth: 1200, maxHeight: .infinity, alignment: .topLeading)
|
|
|
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
- .padding(.horizontal, dynamicHorizontalPadding)
|
|
|
- .padding(.top, AppTheme.brandLabelTopInset)
|
|
|
- .padding(.bottom, dynamicBottomPadding)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -81,43 +62,74 @@ struct PunctuationCheckerView: View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private var header: some View {
|
|
|
- HStack(alignment: .top) {
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
- Text("Punctuation Checker")
|
|
|
- .font(.system(size: 30, weight: .bold))
|
|
|
- .foregroundStyle(AppTheme.textPrimary)
|
|
|
+ private func loadingLayout(
|
|
|
+ horizontalPadding: CGFloat,
|
|
|
+ topPadding: CGFloat,
|
|
|
+ bottomPadding: CGFloat
|
|
|
+ ) -> some View {
|
|
|
+ VStack(spacing: 14) {
|
|
|
+ ProgressView()
|
|
|
+ .controlSize(.regular)
|
|
|
+
|
|
|
+ Text("Loading...")
|
|
|
+ .font(.system(size: 16, weight: .semibold))
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
+
|
|
|
+ Text("Checking punctuation and applying corrections")
|
|
|
+ .font(.system(size: 14))
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
+ }
|
|
|
+ .frame(maxWidth: 1200, maxHeight: .infinity, alignment: .center)
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
|
|
+ .padding(.horizontal, horizontalPadding)
|
|
|
+ .padding(.top, topPadding)
|
|
|
+ .padding(.bottom, bottomPadding)
|
|
|
+ }
|
|
|
|
|
|
- HStack(spacing: 0) {
|
|
|
- Text("Perfect your ")
|
|
|
- .foregroundStyle(AppTheme.textSecondary)
|
|
|
- Text("punctuation")
|
|
|
- .fontWeight(.semibold)
|
|
|
- .foregroundStyle(AppTheme.teal)
|
|
|
- Text(". Write with ")
|
|
|
- .foregroundStyle(AppTheme.textSecondary)
|
|
|
- Text("clarity")
|
|
|
- .fontWeight(.semibold)
|
|
|
- .foregroundStyle(AppTheme.teal)
|
|
|
- Text(".")
|
|
|
- .foregroundStyle(AppTheme.textSecondary)
|
|
|
+ private var header: some View {
|
|
|
+ VStack(alignment: .leading, spacing: 16) {
|
|
|
+ if viewModel.hasResults {
|
|
|
+ IconButton(iconName: "arrow.left") {
|
|
|
+ viewModel.resetResults()
|
|
|
}
|
|
|
- .font(.system(size: 15))
|
|
|
+ .accessibilityLabel("Go back")
|
|
|
}
|
|
|
|
|
|
- Spacer()
|
|
|
+ HStack(alignment: .top) {
|
|
|
+ VStack(alignment: .leading, spacing: 8) {
|
|
|
+ Text("Punctuation Checker")
|
|
|
+ .font(.system(size: 30, weight: .bold))
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
+
|
|
|
+ HStack(spacing: 0) {
|
|
|
+ Text("Perfect your ")
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
+ Text("punctuation")
|
|
|
+ .fontWeight(.semibold)
|
|
|
+ .foregroundStyle(AppTheme.teal)
|
|
|
+ Text(". Write with ")
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
+ Text("clarity")
|
|
|
+ .fontWeight(.semibold)
|
|
|
+ .foregroundStyle(AppTheme.teal)
|
|
|
+ Text(".")
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
+ }
|
|
|
+ .font(.system(size: 15))
|
|
|
+ }
|
|
|
|
|
|
- IconButton {
|
|
|
- SettingsGearIcon()
|
|
|
- } action: {}
|
|
|
- .accessibilityLabel("Settings")
|
|
|
+ Spacer()
|
|
|
+
|
|
|
+ IconButton {
|
|
|
+ SettingsGearIcon()
|
|
|
+ } action: {}
|
|
|
+ .accessibilityLabel("Settings")
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private var contentCard: some View {
|
|
|
- let compactEditor = showsOutputLayout
|
|
|
-
|
|
|
- return VStack(alignment: .leading, spacing: AppTheme.contentCardSpacing) {
|
|
|
+ VStack(alignment: .leading, spacing: AppTheme.contentCardSpacing) {
|
|
|
YourContentHeader()
|
|
|
|
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
|
@@ -134,7 +146,7 @@ struct PunctuationCheckerView: View {
|
|
|
.foregroundStyle(AppTheme.textMuted)
|
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
|
}
|
|
|
- .scrollSafeEditorSectionFrame(compact: compactEditor)
|
|
|
+ .scrollSafeEditorSectionFrame(compact: false)
|
|
|
|
|
|
HStack(spacing: 8) {
|
|
|
ToolbarButton(title: "Paste Text", iconName: "doc.on.clipboard") {
|
|
|
@@ -153,7 +165,7 @@ struct PunctuationCheckerView: View {
|
|
|
}
|
|
|
}
|
|
|
.inputContentCardPadding()
|
|
|
- .scrollSafeContentCardFrame(compact: compactEditor)
|
|
|
+ .scrollSafeContentCardFrame(compact: false)
|
|
|
.background(AppTheme.cardBackground)
|
|
|
.clipShape(RoundedRectangle(cornerRadius: AppTheme.cardCornerRadius))
|
|
|
.overlay(
|
|
|
@@ -172,10 +184,14 @@ struct PunctuationCheckerView: View {
|
|
|
)
|
|
|
.font(.system(size: 14))
|
|
|
.foregroundStyle(AppTheme.textPrimary)
|
|
|
- .scrollSafeTextEditorFrame(compact: showsOutputLayout)
|
|
|
+ .scrollSafeTextEditorFrame(compact: false)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var resultsLayout: some View {
|
|
|
+ correctedTextCard
|
|
|
}
|
|
|
|
|
|
- private var outputCard: some View {
|
|
|
+ private var correctedTextCard: some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
HStack {
|
|
|
Text("Corrected Text")
|
|
|
@@ -191,25 +207,21 @@ struct PunctuationCheckerView: View {
|
|
|
.opacity(viewModel.correctedText.isEmpty ? 0.55 : 1)
|
|
|
}
|
|
|
|
|
|
- Group {
|
|
|
- if viewModel.isChecking && viewModel.correctedText.isEmpty {
|
|
|
- Text("Checking punctuation...")
|
|
|
- .font(.system(size: 14))
|
|
|
- .foregroundStyle(AppTheme.textMuted)
|
|
|
- } else {
|
|
|
- Text(viewModel.correctedText)
|
|
|
- .font(.system(size: 14))
|
|
|
- .foregroundStyle(AppTheme.textPrimary)
|
|
|
- }
|
|
|
+ ScrollView(.vertical, showsIndicators: true) {
|
|
|
+ Text(viewModel.correctedText)
|
|
|
+ .font(.system(size: 14))
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
+ .frame(maxWidth: .infinity, alignment: .leading)
|
|
|
+ .textSelection(.enabled)
|
|
|
+ .padding(16)
|
|
|
}
|
|
|
- .frame(maxWidth: .infinity, alignment: .leading)
|
|
|
- .padding(16)
|
|
|
- .background(AppTheme.tealLight.opacity(0.35))
|
|
|
- .clipShape(RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius))
|
|
|
- .overlay(
|
|
|
- RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius)
|
|
|
- .stroke(AppTheme.teal.opacity(0.2), lineWidth: 1)
|
|
|
- )
|
|
|
+ .frame(maxHeight: .infinity, alignment: .top)
|
|
|
+ .background(AppTheme.tealLight.opacity(0.35))
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius))
|
|
|
+ .overlay(
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius)
|
|
|
+ .stroke(AppTheme.teal.opacity(0.2), lineWidth: 1)
|
|
|
+ )
|
|
|
}
|
|
|
.padding(AppTheme.contentCardPadding)
|
|
|
.background(AppTheme.cardBackground)
|
|
|
@@ -219,5 +231,6 @@ struct PunctuationCheckerView: View {
|
|
|
.stroke(AppTheme.border, lineWidth: 1)
|
|
|
)
|
|
|
.shadow(color: AppTheme.cardShadow, radius: 16, y: 6)
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
}
|
|
|
}
|