Vocab study — per-type session sizes + Review Learned multiple choice
- Settings: split the single session-size picker into separate Verbs / Nouns / Adjectives pickers. Nouns and adjectives previously shared one hidden limit; they now use nounSessionCardLimit / adjectiveSessionCardLimit. - LexemePool.sessionCardLimit is now per part-of-speech. - Multiple-choice views (verb/noun/adjective) gained a kind param so Review Learned can run as multiple choice, not just flashcards. The cram pass drives the in-session queue only and leaves the long-term SRS schedule untouched. - PracticeView: each section now offers Review Learned — Flashcards and Review Learned — Multiple Choice. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -114,9 +114,19 @@ struct LexemeSessionQueue {
|
|||||||
/// `LexemeReviewCard`, then fresh (never-studied) lexemes, capped.
|
/// `LexemeReviewCard`, then fresh (never-studied) lexemes, capped.
|
||||||
enum LexemePool {
|
enum LexemePool {
|
||||||
|
|
||||||
/// Per-session cap. 0/unset → 20. Mirrors `VocabVerbPool.sessionCardLimit`.
|
/// Per-session cap for a part of speech, from its "Cards per session"
|
||||||
static var sessionCardLimit: Int {
|
/// setting. Nouns read `nounSessionCardLimit`, adjectives
|
||||||
let stored = UserDefaults.standard.integer(forKey: "lexemeSessionCardLimit")
|
/// `adjectiveSessionCardLimit`; anything else falls back to the legacy
|
||||||
|
/// shared `lexemeSessionCardLimit`. 0/unset → 20. Mirrors
|
||||||
|
/// `VocabVerbPool.sessionCardLimit`.
|
||||||
|
static func sessionCardLimit(for partOfSpeech: String) -> Int {
|
||||||
|
let key: String
|
||||||
|
switch partOfSpeech {
|
||||||
|
case "noun": key = "nounSessionCardLimit"
|
||||||
|
case "adjective": key = "adjectiveSessionCardLimit"
|
||||||
|
default: key = "lexemeSessionCardLimit"
|
||||||
|
}
|
||||||
|
let stored = UserDefaults.standard.integer(forKey: key)
|
||||||
return stored == 0 ? 20 : stored
|
return stored == 0 ? 20 : stored
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +180,7 @@ enum LexemePool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ordered = due.map(\.lexeme) + fresh
|
let ordered = due.map(\.lexeme) + fresh
|
||||||
return Array(ordered.prefix(sessionCardLimit))
|
return Array(ordered.prefix(sessionCardLimit(for: partOfSpeech)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lexemes the user has already studied at least once for `(POS, drill)`,
|
/// Lexemes the user has already studied at least once for `(POS, drill)`,
|
||||||
|
|||||||
@@ -439,12 +439,22 @@ struct PracticeView: View {
|
|||||||
VocabFlashcardPracticeView(kind: .reviewLearned)
|
VocabFlashcardPracticeView(kind: .reviewLearned)
|
||||||
} label: {
|
} label: {
|
||||||
practiceRowLabel(icon: "clock.arrow.circlepath", color: .purple,
|
practiceRowLabel(icon: "clock.arrow.circlepath", color: .purple,
|
||||||
title: "Review Learned",
|
title: "Review Learned — Flashcards",
|
||||||
subtitle: "Re-review verbs you've studied — schedule unchanged")
|
subtitle: "Re-review verbs you've studied — schedule unchanged")
|
||||||
}
|
}
|
||||||
.tint(.primary)
|
.tint(.primary)
|
||||||
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
||||||
|
|
||||||
|
NavigationLink {
|
||||||
|
VocabMultipleChoicePracticeView(kind: .reviewLearned)
|
||||||
|
} label: {
|
||||||
|
practiceRowLabel(icon: "clock.arrow.circlepath", color: .purple,
|
||||||
|
title: "Review Learned — Multiple Choice",
|
||||||
|
subtitle: "Multiple choice over verbs you've studied — schedule unchanged")
|
||||||
|
}
|
||||||
|
.tint(.primary)
|
||||||
|
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
||||||
|
|
||||||
// Existing: Vocab Review (due cards)
|
// Existing: Vocab Review (due cards)
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
VocabReviewView()
|
VocabReviewView()
|
||||||
@@ -516,12 +526,22 @@ struct PracticeView: View {
|
|||||||
NounFlashcardPracticeView(kind: .reviewLearned)
|
NounFlashcardPracticeView(kind: .reviewLearned)
|
||||||
} label: {
|
} label: {
|
||||||
practiceRowLabel(icon: "clock.arrow.circlepath", color: .teal,
|
practiceRowLabel(icon: "clock.arrow.circlepath", color: .teal,
|
||||||
title: "Review Learned",
|
title: "Review Learned — Flashcards",
|
||||||
subtitle: "Re-review nouns you've studied — schedule unchanged")
|
subtitle: "Re-review nouns you've studied — schedule unchanged")
|
||||||
}
|
}
|
||||||
.tint(.primary)
|
.tint(.primary)
|
||||||
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
||||||
|
|
||||||
|
NavigationLink {
|
||||||
|
NounMultipleChoicePracticeView(kind: .reviewLearned)
|
||||||
|
} label: {
|
||||||
|
practiceRowLabel(icon: "clock.arrow.circlepath", color: .teal,
|
||||||
|
title: "Review Learned — Multiple Choice",
|
||||||
|
subtitle: "Multiple choice over nouns you've studied — schedule unchanged")
|
||||||
|
}
|
||||||
|
.tint(.primary)
|
||||||
|
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
||||||
|
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
NounReviewView()
|
NounReviewView()
|
||||||
} label: {
|
} label: {
|
||||||
@@ -591,12 +611,22 @@ struct PracticeView: View {
|
|||||||
AdjectiveFlashcardPracticeView(kind: .reviewLearned)
|
AdjectiveFlashcardPracticeView(kind: .reviewLearned)
|
||||||
} label: {
|
} label: {
|
||||||
practiceRowLabel(icon: "clock.arrow.circlepath", color: .pink,
|
practiceRowLabel(icon: "clock.arrow.circlepath", color: .pink,
|
||||||
title: "Review Learned",
|
title: "Review Learned — Flashcards",
|
||||||
subtitle: "Re-review adjectives you've studied — schedule unchanged")
|
subtitle: "Re-review adjectives you've studied — schedule unchanged")
|
||||||
}
|
}
|
||||||
.tint(.primary)
|
.tint(.primary)
|
||||||
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
||||||
|
|
||||||
|
NavigationLink {
|
||||||
|
AdjectiveMultipleChoicePracticeView(kind: .reviewLearned)
|
||||||
|
} label: {
|
||||||
|
practiceRowLabel(icon: "clock.arrow.circlepath", color: .pink,
|
||||||
|
title: "Review Learned — Multiple Choice",
|
||||||
|
subtitle: "Multiple choice over adjectives you've studied — schedule unchanged")
|
||||||
|
}
|
||||||
|
.tint(.primary)
|
||||||
|
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
||||||
|
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
AdjectiveReviewView()
|
AdjectiveReviewView()
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import SwiftData
|
|||||||
/// adjective pool; 4 options (1 correct + 3 random distractors from the
|
/// adjective pool; 4 options (1 correct + 3 random distractors from the
|
||||||
/// session). Options are bare base forms — agreement isn't drilled here.
|
/// session). Options are bare base forms — agreement isn't drilled here.
|
||||||
struct AdjectiveMultipleChoicePracticeView: View {
|
struct AdjectiveMultipleChoicePracticeView: View {
|
||||||
|
var kind: LexemeSessionKind = .standard
|
||||||
|
|
||||||
@Environment(\.modelContext) private var localContext
|
@Environment(\.modelContext) private var localContext
|
||||||
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
|
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@@ -33,7 +35,7 @@ struct AdjectiveMultipleChoicePracticeView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.adaptiveContainer(maxWidth: 720)
|
.adaptiveContainer(maxWidth: 720)
|
||||||
}
|
}
|
||||||
.navigationTitle("Adjective Multiple Choice")
|
.navigationTitle(kind == .reviewLearned ? "Review Learned" : "Adjective Multiple Choice")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.onAppear(perform: loadIfNeeded)
|
.onAppear(perform: loadIfNeeded)
|
||||||
.animation(.smooth, value: selectedOption?.id)
|
.animation(.smooth, value: selectedOption?.id)
|
||||||
@@ -185,20 +187,39 @@ struct AdjectiveMultipleChoicePracticeView: View {
|
|||||||
|
|
||||||
private var completionDetail: String {
|
private var completionDetail: String {
|
||||||
let learned = session?.learnedCount ?? 0
|
let learned = session?.learnedCount ?? 0
|
||||||
if learned > 0 { return "\(learned) adjective\(learned == 1 ? "" : "s") learned" }
|
if learned > 0 {
|
||||||
return "No adjectives are due right now. Study Again to review anyway."
|
let verb = kind == .reviewLearned ? "reviewed" : "learned"
|
||||||
|
return "\(learned) adjective\(learned == 1 ? "" : "s") \(verb)"
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case .standard:
|
||||||
|
return "No adjectives are due right now. Study Again to review anyway."
|
||||||
|
case .reviewLearned:
|
||||||
|
return "Finish an adjective session first, then come back to consolidate."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadIfNeeded() {
|
private func loadIfNeeded() {
|
||||||
guard session == nil else { return }
|
guard session == nil else { return }
|
||||||
let progress = ReviewStore.fetchOrCreateUserProgress(context: cloudContext)
|
let lexemes: [Lexeme]
|
||||||
let lexemes = LexemePool.sessionLexemes(
|
switch kind {
|
||||||
partOfSpeech: "adjective",
|
case .standard:
|
||||||
drillMode: Self.drillMode,
|
let progress = ReviewStore.fetchOrCreateUserProgress(context: cloudContext)
|
||||||
enabledLevels: progress.selectedLexemeLevels,
|
lexemes = LexemePool.sessionLexemes(
|
||||||
localContext: localContext,
|
partOfSpeech: "adjective",
|
||||||
cloudContext: cloudContext
|
drillMode: Self.drillMode,
|
||||||
)
|
enabledLevels: progress.selectedLexemeLevels,
|
||||||
|
localContext: localContext,
|
||||||
|
cloudContext: cloudContext
|
||||||
|
)
|
||||||
|
case .reviewLearned:
|
||||||
|
lexemes = LexemePool.reviewLearnedLexemes(
|
||||||
|
partOfSpeech: "adjective",
|
||||||
|
drillMode: Self.drillMode,
|
||||||
|
localContext: localContext,
|
||||||
|
cloudContext: cloudContext
|
||||||
|
)
|
||||||
|
}
|
||||||
distractorPool = lexemes
|
distractorPool = lexemes
|
||||||
session = LexemeSessionQueue(lexemes: lexemes, drillMode: Self.drillMode)
|
session = LexemeSessionQueue(lexemes: lexemes, drillMode: Self.drillMode)
|
||||||
prepareOptions()
|
prepareOptions()
|
||||||
@@ -220,7 +241,9 @@ struct AdjectiveMultipleChoicePracticeView: View {
|
|||||||
private func answer(_ rating: LexemeSessionQueue.Rating) {
|
private func answer(_ rating: LexemeSessionQueue.Rating) {
|
||||||
guard let lexeme = currentLexeme else { return }
|
guard let lexeme = currentLexeme else { return }
|
||||||
let graduation = session?.answer(rating)
|
let graduation = session?.answer(rating)
|
||||||
if let graduation {
|
// Review Learned is a cram pass — graduation drives the in-session
|
||||||
|
// queue only; the long-term schedule is left untouched.
|
||||||
|
if let graduation, kind == .standard {
|
||||||
LexemeReviewStore(context: cloudContext).rate(
|
LexemeReviewStore(context: cloudContext).rate(
|
||||||
lexemeId: lexeme.id,
|
lexemeId: lexeme.id,
|
||||||
partOfSpeech: "adjective",
|
partOfSpeech: "adjective",
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import SwiftData
|
|||||||
/// el problema), example sentence when present, and Again/Hard/Good/Easy
|
/// el problema), example sentence when present, and Again/Hard/Good/Easy
|
||||||
/// rating which drives the `LexemeReviewStore` schedule.
|
/// rating which drives the `LexemeReviewStore` schedule.
|
||||||
struct NounMultipleChoicePracticeView: View {
|
struct NounMultipleChoicePracticeView: View {
|
||||||
|
var kind: LexemeSessionKind = .standard
|
||||||
|
|
||||||
@Environment(\.modelContext) private var localContext
|
@Environment(\.modelContext) private var localContext
|
||||||
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
|
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@@ -35,7 +37,7 @@ struct NounMultipleChoicePracticeView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.adaptiveContainer(maxWidth: 720)
|
.adaptiveContainer(maxWidth: 720)
|
||||||
}
|
}
|
||||||
.navigationTitle("Noun Multiple Choice")
|
.navigationTitle(kind == .reviewLearned ? "Review Learned" : "Noun Multiple Choice")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.onAppear(perform: loadIfNeeded)
|
.onAppear(perform: loadIfNeeded)
|
||||||
.animation(.smooth, value: selectedOption?.id)
|
.animation(.smooth, value: selectedOption?.id)
|
||||||
@@ -193,22 +195,41 @@ struct NounMultipleChoicePracticeView: View {
|
|||||||
|
|
||||||
private var completionDetail: String {
|
private var completionDetail: String {
|
||||||
let learned = session?.learnedCount ?? 0
|
let learned = session?.learnedCount ?? 0
|
||||||
if learned > 0 { return "\(learned) noun\(learned == 1 ? "" : "s") learned" }
|
if learned > 0 {
|
||||||
return "No nouns are due right now. Study Again to review anyway."
|
let verb = kind == .reviewLearned ? "reviewed" : "learned"
|
||||||
|
return "\(learned) noun\(learned == 1 ? "" : "s") \(verb)"
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case .standard:
|
||||||
|
return "No nouns are due right now. Study Again to review anyway."
|
||||||
|
case .reviewLearned:
|
||||||
|
return "Finish a noun session first, then come back to consolidate."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Logic
|
// MARK: - Logic
|
||||||
|
|
||||||
private func loadIfNeeded() {
|
private func loadIfNeeded() {
|
||||||
guard session == nil else { return }
|
guard session == nil else { return }
|
||||||
let progress = ReviewStore.fetchOrCreateUserProgress(context: cloudContext)
|
let lexemes: [Lexeme]
|
||||||
let lexemes = LexemePool.sessionLexemes(
|
switch kind {
|
||||||
partOfSpeech: "noun",
|
case .standard:
|
||||||
drillMode: Self.drillMode,
|
let progress = ReviewStore.fetchOrCreateUserProgress(context: cloudContext)
|
||||||
enabledLevels: progress.selectedLexemeLevels,
|
lexemes = LexemePool.sessionLexemes(
|
||||||
localContext: localContext,
|
partOfSpeech: "noun",
|
||||||
cloudContext: cloudContext
|
drillMode: Self.drillMode,
|
||||||
)
|
enabledLevels: progress.selectedLexemeLevels,
|
||||||
|
localContext: localContext,
|
||||||
|
cloudContext: cloudContext
|
||||||
|
)
|
||||||
|
case .reviewLearned:
|
||||||
|
lexemes = LexemePool.reviewLearnedLexemes(
|
||||||
|
partOfSpeech: "noun",
|
||||||
|
drillMode: Self.drillMode,
|
||||||
|
localContext: localContext,
|
||||||
|
cloudContext: cloudContext
|
||||||
|
)
|
||||||
|
}
|
||||||
distractorPool = lexemes
|
distractorPool = lexemes
|
||||||
session = LexemeSessionQueue(lexemes: lexemes, drillMode: Self.drillMode)
|
session = LexemeSessionQueue(lexemes: lexemes, drillMode: Self.drillMode)
|
||||||
prepareOptions()
|
prepareOptions()
|
||||||
@@ -230,7 +251,9 @@ struct NounMultipleChoicePracticeView: View {
|
|||||||
private func answer(_ rating: LexemeSessionQueue.Rating) {
|
private func answer(_ rating: LexemeSessionQueue.Rating) {
|
||||||
guard let lexeme = currentLexeme else { return }
|
guard let lexeme = currentLexeme else { return }
|
||||||
let graduation = session?.answer(rating)
|
let graduation = session?.answer(rating)
|
||||||
if let graduation {
|
// Review Learned is a cram pass — graduation drives the in-session
|
||||||
|
// queue only; the long-term schedule is left untouched.
|
||||||
|
if let graduation, kind == .standard {
|
||||||
LexemeReviewStore(context: cloudContext).rate(
|
LexemeReviewStore(context: cloudContext).rate(
|
||||||
lexemeId: lexeme.id,
|
lexemeId: lexeme.id,
|
||||||
partOfSpeech: "noun",
|
partOfSpeech: "noun",
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import SwiftData
|
|||||||
/// reveal correct/incorrect, the verb infinitive, an example sentence, and SRS
|
/// reveal correct/incorrect, the verb infinitive, an example sentence, and SRS
|
||||||
/// rating buttons. Again/Hard requeue; a second Good or an Easy graduates.
|
/// rating buttons. Again/Hard requeue; a second Good or an Easy graduates.
|
||||||
struct VocabMultipleChoicePracticeView: View {
|
struct VocabMultipleChoicePracticeView: View {
|
||||||
|
var kind: VocabSessionKind = .standard
|
||||||
|
|
||||||
@Environment(\.modelContext) private var localContext
|
@Environment(\.modelContext) private var localContext
|
||||||
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
|
@Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
|
||||||
@Environment(VerbExampleCache.self) private var exampleCache
|
@Environment(VerbExampleCache.self) private var exampleCache
|
||||||
@@ -37,7 +39,7 @@ struct VocabMultipleChoicePracticeView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.adaptiveContainer(maxWidth: 720)
|
.adaptiveContainer(maxWidth: 720)
|
||||||
}
|
}
|
||||||
.navigationTitle("Vocab Multiple Choice")
|
.navigationTitle(kind == .reviewLearned ? "Review Learned" : "Vocab Multiple Choice")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.onAppear(perform: loadIfNeeded)
|
.onAppear(perform: loadIfNeeded)
|
||||||
.animation(.smooth, value: selectedOption?.id)
|
.animation(.smooth, value: selectedOption?.id)
|
||||||
@@ -221,16 +223,28 @@ struct VocabMultipleChoicePracticeView: View {
|
|||||||
private var completionDetail: String {
|
private var completionDetail: String {
|
||||||
let learned = session?.learnedCount ?? 0
|
let learned = session?.learnedCount ?? 0
|
||||||
if learned > 0 {
|
if learned > 0 {
|
||||||
return "\(learned) verb\(learned == 1 ? "" : "s") learned"
|
let verb = kind == .reviewLearned ? "reviewed" : "learned"
|
||||||
|
return "\(learned) verb\(learned == 1 ? "" : "s") \(verb)"
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case .standard:
|
||||||
|
return "No verbs are due right now. Study Again to review anyway."
|
||||||
|
case .reviewLearned:
|
||||||
|
return "Finish a Vocab session first, then come back to consolidate."
|
||||||
}
|
}
|
||||||
return "No verbs are due right now. Study Again to review anyway."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Logic
|
// MARK: - Logic
|
||||||
|
|
||||||
private func loadIfNeeded() {
|
private func loadIfNeeded() {
|
||||||
guard session == nil else { return }
|
guard session == nil else { return }
|
||||||
let verbs = VocabVerbPool.sessionVerbs(localContext: localContext, cloudContext: cloudContext)
|
let verbs: [Verb]
|
||||||
|
switch kind {
|
||||||
|
case .standard:
|
||||||
|
verbs = VocabVerbPool.sessionVerbs(localContext: localContext, cloudContext: cloudContext)
|
||||||
|
case .reviewLearned:
|
||||||
|
verbs = VocabVerbPool.reviewLearnedVerbs(localContext: localContext, cloudContext: cloudContext)
|
||||||
|
}
|
||||||
distractorPool = verbs
|
distractorPool = verbs
|
||||||
session = VocabSessionQueue(verbs: verbs)
|
session = VocabSessionQueue(verbs: verbs)
|
||||||
prepareOptions()
|
prepareOptions()
|
||||||
@@ -254,7 +268,9 @@ struct VocabMultipleChoicePracticeView: View {
|
|||||||
private func answer(_ rating: VocabSessionQueue.Rating) {
|
private func answer(_ rating: VocabSessionQueue.Rating) {
|
||||||
guard let verbId = currentVerb?.id else { return }
|
guard let verbId = currentVerb?.id else { return }
|
||||||
let graduation = session?.answer(rating) ?? nil
|
let graduation = session?.answer(rating) ?? nil
|
||||||
if let graduation {
|
// Review Learned is a cram pass — graduation drives the in-session
|
||||||
|
// queue only; the long-term schedule is left untouched.
|
||||||
|
if let graduation, kind == .standard {
|
||||||
VerbReviewStore(context: cloudContext).rate(verbId: verbId, quality: graduation)
|
VerbReviewStore(context: cloudContext).rate(verbId: verbId, quality: graduation)
|
||||||
}
|
}
|
||||||
selectedOption = nil
|
selectedOption = nil
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ struct SettingsView: View {
|
|||||||
@State private var showVosotros: Bool = true
|
@State private var showVosotros: Bool = true
|
||||||
@State private var autoFillStem: Bool = false
|
@State private var autoFillStem: Bool = false
|
||||||
|
|
||||||
/// Cards per vocab-practice session. 999 = "All" (no cap).
|
/// Cards per study session, per word type. 999 = "All" (no cap).
|
||||||
@AppStorage("vocabSessionCardLimit") private var vocabSessionCardLimit: Int = 20
|
@AppStorage("vocabSessionCardLimit") private var vocabSessionCardLimit: Int = 20
|
||||||
|
@AppStorage("nounSessionCardLimit") private var nounSessionCardLimit: Int = 20
|
||||||
|
@AppStorage("adjectiveSessionCardLimit") private var adjectiveSessionCardLimit: Int = 20
|
||||||
private let vocabSessionSizes: [Int] = [10, 15, 20, 25, 30, 50, 999]
|
private let vocabSessionSizes: [Int] = [10, 15, 20, 25, 30, 50, 999]
|
||||||
|
|
||||||
private let levels = VerbLevel.allCases
|
private let levels = VerbLevel.allCases
|
||||||
@@ -47,15 +49,13 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Picker("Cards per session", selection: $vocabSessionCardLimit) {
|
sessionSizePicker("Verbs per session", selection: $vocabSessionCardLimit)
|
||||||
ForEach(vocabSessionSizes, id: \.self) { size in
|
sessionSizePicker("Nouns per session", selection: $nounSessionCardLimit)
|
||||||
Text(size == 999 ? "All" : "\(size)").tag(size)
|
sessionSizePicker("Adjectives per session", selection: $adjectiveSessionCardLimit)
|
||||||
}
|
|
||||||
}
|
|
||||||
} header: {
|
} header: {
|
||||||
Text("Vocab Flashcards")
|
Text("Cards Per Session")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("How many verbs a Vocab Flashcards session draws. Overdue verbs are pulled first, then new ones.")
|
Text("How many cards each flashcard or multiple-choice session draws, per word type. Overdue cards are pulled first, then new ones.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
@@ -172,6 +172,14 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func sessionSizePicker(_ title: String, selection: Binding<Int>) -> some View {
|
||||||
|
Picker(title, selection: selection) {
|
||||||
|
ForEach(vocabSessionSizes, id: \.self) { size in
|
||||||
|
Text(size == 999 ? "All" : "\(size)").tag(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func loadProgress() {
|
private func loadProgress() {
|
||||||
let resolved = ReviewStore.fetchOrCreateUserProgress(context: cloudModelContext)
|
let resolved = ReviewStore.fetchOrCreateUserProgress(context: cloudModelContext)
|
||||||
progress = resolved
|
progress = resolved
|
||||||
|
|||||||
Reference in New Issue
Block a user