|
@@ -3,13 +3,6 @@ import SwiftUI
|
|
|
struct WordsCountView: View {
|
|
struct WordsCountView: View {
|
|
|
@StateObject private var viewModel = WordsCountViewModel()
|
|
@StateObject private var viewModel = WordsCountViewModel()
|
|
|
@Environment(\.onSettingsTapped) private var onSettingsTapped
|
|
@Environment(\.onSettingsTapped) private var onSettingsTapped
|
|
|
- @Environment(\.requirePremiumAccess) private var requirePremiumAccess
|
|
|
|
|
- @EnvironmentObject private var subscriptions: SubscriptionManager
|
|
|
|
|
- @State private var didPromptWordCountPaywall = false
|
|
|
|
|
-
|
|
|
|
|
- private var hasPremium: Bool {
|
|
|
|
|
- subscriptions.hasPremiumAccess
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
var body: some View {
|
|
|
GeometryReader { proxy in
|
|
GeometryReader { proxy in
|
|
@@ -37,10 +30,8 @@ struct WordsCountView: View {
|
|
|
.padding(.bottom, dynamicBottomPadding)
|
|
.padding(.bottom, dynamicBottomPadding)
|
|
|
}
|
|
}
|
|
|
.clearHostingBackground()
|
|
.clearHostingBackground()
|
|
|
- .onChange(of: subscriptions.hasPremiumAccess) { isPremium in
|
|
|
|
|
- if isPremium {
|
|
|
|
|
- viewModel.handleTextChange()
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .onChange(of: viewModel.text) { _ in
|
|
|
|
|
+ viewModel.handleTextChange()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -76,43 +67,31 @@ struct WordsCountView: View {
|
|
|
VStack(alignment: .leading, spacing: AppTheme.contentCardSpacing) {
|
|
VStack(alignment: .leading, spacing: AppTheme.contentCardSpacing) {
|
|
|
YourContentHeader()
|
|
YourContentHeader()
|
|
|
|
|
|
|
|
- Text(hasPremium ? viewModel.wordCountLabel : "Words Count")
|
|
|
|
|
|
|
+ Text(viewModel.wordCountLabel)
|
|
|
.font(.system(size: 18, weight: .semibold))
|
|
.font(.system(size: 18, weight: .semibold))
|
|
|
.foregroundStyle(AppTheme.textPrimary)
|
|
.foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
|
|
|
statsRow
|
|
statsRow
|
|
|
|
|
|
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
|
- ZStack(alignment: .topLeading) {
|
|
|
|
|
- ThinCaretTextEditor(
|
|
|
|
|
- text: premiumGatedText,
|
|
|
|
|
- placeholder: "Type or paste your text here to count words...",
|
|
|
|
|
- maxWords: WordsCountViewModel.maxWords,
|
|
|
|
|
- onWordLimitReached: viewModel.notifyWordLimitReached
|
|
|
|
|
- )
|
|
|
|
|
- .font(.system(size: 14))
|
|
|
|
|
- .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
- .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
|
- .frame(minHeight: 96)
|
|
|
|
|
- .background(AppTheme.surface)
|
|
|
|
|
- .clipShape(RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius))
|
|
|
|
|
- .overlay(
|
|
|
|
|
- RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius)
|
|
|
|
|
- .stroke(AppTheme.inputBorder, lineWidth: 1)
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if !hasPremium {
|
|
|
|
|
|
|
+ ThinCaretTextEditor(
|
|
|
|
|
+ text: $viewModel.text,
|
|
|
|
|
+ placeholder: "Type or paste your text here to count words...",
|
|
|
|
|
+ maxWords: WordsCountViewModel.maxWords,
|
|
|
|
|
+ onWordLimitReached: viewModel.notifyWordLimitReached
|
|
|
|
|
+ )
|
|
|
|
|
+ .font(.system(size: 14))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
|
+ .frame(minHeight: 96)
|
|
|
|
|
+ .background(AppTheme.surface)
|
|
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius))
|
|
|
|
|
+ .overlay(
|
|
|
RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius)
|
|
RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius)
|
|
|
- .fill(Color.clear)
|
|
|
|
|
- .contentShape(RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius))
|
|
|
|
|
- .onTapGesture {
|
|
|
|
|
- didPromptWordCountPaywall = false
|
|
|
|
|
- _ = requirePremiumAccess()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .stroke(AppTheme.inputBorder, lineWidth: 1)
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- Text(hasPremium ? viewModel.wordCountLimitLabel : "Upgrade to count words")
|
|
|
|
|
|
|
+ Text(viewModel.wordCountLimitLabel)
|
|
|
.font(.system(size: 11))
|
|
.font(.system(size: 11))
|
|
|
.foregroundStyle(AppTheme.textMuted)
|
|
.foregroundStyle(AppTheme.textMuted)
|
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
@@ -121,12 +100,10 @@ struct WordsCountView: View {
|
|
|
|
|
|
|
|
HStack(spacing: 8) {
|
|
HStack(spacing: 8) {
|
|
|
ToolbarButton(title: "Paste Text", iconName: "doc.on.clipboard") {
|
|
ToolbarButton(title: "Paste Text", iconName: "doc.on.clipboard") {
|
|
|
- guard requirePremiumAccess() else { return }
|
|
|
|
|
viewModel.pasteText()
|
|
viewModel.pasteText()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ToolbarButton(title: "Upload File", iconName: "square.and.arrow.up") {
|
|
ToolbarButton(title: "Upload File", iconName: "square.and.arrow.up") {
|
|
|
- guard requirePremiumAccess() else { return }
|
|
|
|
|
viewModel.uploadFile()
|
|
viewModel.uploadFile()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -134,7 +111,6 @@ struct WordsCountView: View {
|
|
|
|
|
|
|
|
ClearButton {
|
|
ClearButton {
|
|
|
viewModel.clearText()
|
|
viewModel.clearText()
|
|
|
- didPromptWordCountPaywall = false
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -149,38 +125,12 @@ struct WordsCountView: View {
|
|
|
.shadow(color: AppTheme.cardShadow, radius: 16, y: 6)
|
|
.shadow(color: AppTheme.cardShadow, radius: 16, y: 6)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private var premiumGatedText: Binding<String> {
|
|
|
|
|
- Binding(
|
|
|
|
|
- get: { viewModel.text },
|
|
|
|
|
- set: { newValue in
|
|
|
|
|
- let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
- if trimmed.isEmpty {
|
|
|
|
|
- didPromptWordCountPaywall = false
|
|
|
|
|
- viewModel.text = newValue
|
|
|
|
|
- viewModel.handleTextChange()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- guard subscriptions.hasPremiumAccess else {
|
|
|
|
|
- if !didPromptWordCountPaywall {
|
|
|
|
|
- didPromptWordCountPaywall = true
|
|
|
|
|
- requirePremiumAccess()
|
|
|
|
|
- }
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- viewModel.text = newValue
|
|
|
|
|
- viewModel.handleTextChange()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
private var statsRow: some View {
|
|
private var statsRow: some View {
|
|
|
HStack(spacing: 12) {
|
|
HStack(spacing: 12) {
|
|
|
- StatItem(title: "Words", value: hasPremium ? "\(viewModel.wordCount)" : "—")
|
|
|
|
|
- StatItem(title: "Sentences", value: hasPremium ? "\(viewModel.sentenceCount)" : "—")
|
|
|
|
|
- StatItem(title: "Paragraphs", value: hasPremium ? "\(viewModel.paragraphCount)" : "—")
|
|
|
|
|
- StatItem(title: "Characters", value: hasPremium ? "\(viewModel.characterCount)" : "—")
|
|
|
|
|
|
|
+ StatItem(title: "Words", value: "\(viewModel.wordCount)")
|
|
|
|
|
+ StatItem(title: "Sentences", value: "\(viewModel.sentenceCount)")
|
|
|
|
|
+ StatItem(title: "Paragraphs", value: "\(viewModel.paragraphCount)")
|
|
|
|
|
+ StatItem(title: "Characters", value: "\(viewModel.characterCount)")
|
|
|
Spacer(minLength: 0)
|
|
Spacer(minLength: 0)
|
|
|
}
|
|
}
|
|
|
.padding(.vertical, 6)
|
|
.padding(.vertical, 6)
|