Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add question/time limit quiz modes #3

Merged
merged 28 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
358b632
move view model properties to extension
twocentstudios Nov 5, 2023
442444c
Add quizMode with infinite
twocentstudios Nov 5, 2023
6463828
Add questionAttack mode and isSessionComplete
twocentstudios Nov 5, 2023
cf8d650
Move to session summary screen
twocentstudios Nov 5, 2023
feca37e
DetermineProgressView for question attack mode
twocentstudios Nov 5, 2023
6aa8403
Move percentages to view state
twocentstudios Nov 5, 2023
dad3752
Change delegate to endSessionButtonTapped
twocentstudios Nov 5, 2023
84f4ee6
view state
twocentstudios Nov 5, 2023
aad229c
pass quizMode and isSessionComplete to summary view
twocentstudios Nov 5, 2023
7ff5989
Show quiz mode in summary
twocentstudios Nov 5, 2023
638e2f8
Remove unused clock
twocentstudios Nov 5, 2023
6e78002
Clamped utility
twocentstudios Nov 5, 2023
9aaf9d0
initial time attack support
twocentstudios Nov 5, 2023
c792eb6
make isSessionComplete computed
twocentstudios Nov 5, 2023
6767b28
force monospace design (not sure why I need this)
twocentstudios Nov 5, 2023
48714ed
time formatting
twocentstudios Nov 5, 2023
42c7fdc
Convert unused TopicSettings to SessionSettings
twocentstudios Nov 5, 2023
45736cc
Initial PreSettingsFeature
twocentstudios Nov 6, 2023
f7642db
rename attack to limit
twocentstudios Nov 6, 2023
b1161cd
Go back to Form for no reason
twocentstudios Nov 6, 2023
4ae45b8
Update packages for sendable support
twocentstudios Nov 6, 2023
0b2d969
PreSettings view as half modal
twocentstudios Nov 6, 2023
981bd4b
Fix typo in binding definition
twocentstudios Nov 6, 2023
f2608ab
Fetch quiz mode on init
twocentstudios Nov 6, 2023
fa00643
Change selectable time limit values
twocentstudios Nov 6, 2023
6ae7139
Other previews
twocentstudios Nov 6, 2023
c3b5b31
Progress bar color light gray
twocentstudios Nov 6, 2023
41ec9d2
Move presentation modifiers into ViewStore scope
twocentstudios Nov 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 89 additions & 0 deletions Dependencies/SessionSettings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import DependenciesAdditions
import Foundation

struct SessionSettingsClient {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This file was technically renamed from the unused TopicSettingsClient for convenience.

var get: @Sendable () -> (SessionSettings)
var set: @Sendable (SessionSettings) throws -> Void
}

struct SessionSettings: Equatable, Codable {
enum QuizMode: Equatable, Identifiable, Codable, CaseIterable {
case infinite
case questionLimit
case timeLimit
Comment on lines +10 to +13
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This QuizMode is separate from the top level QuizMode so that we can persist both questionLimit and timeLimit independently of the last chosen quizMode. There's a conversion function in the ListeningQuizFeature file.


var id: Self { self }
var title: String {
switch self {
case .infinite: "Infinite"
case .questionLimit: "Question Limit"
case .timeLimit: "Time Limit"
}
}
}
var quizMode: QuizMode
var questionLimit: Int
var timeLimit: Int // seconds
Comment on lines +25 to +26
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to save these values as raw Ints but only allow the user to choose from a few different values. In theory this could lead to validation issues down the road if the saved value is removed from the UI.


var showProgress: Bool
var showBiki: Bool
var showConfetti: Bool
var playHaptics: Bool
Comment on lines +28 to +31
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add full support for these in another PR.

}

extension SessionSettings {
static let `default`: Self = .init(quizMode: .infinite, questionLimit: 10, timeLimit: 60, showProgress: true, showBiki: true, showConfetti: true, playHaptics: true)

static let questionLimitValues: [Int] = [5, 10, 20, 30, 50, 100]
static let timeLimitValues: [Int] = [30, 1 * 60, 3 * 60, 5 * 60, 10 * 60, 20 * 60]
}

extension SessionSettingsClient: DependencyKey {
static let settingsKey = "SessionSettingsClient.settings"
static var liveValue: Self {
@Dependency(\.userDefaults) var userDefaults
@Dependency(\.encode) var encode
@Dependency(\.decode) var decode
return .init(
get: {
let data = userDefaults.data(forKey: settingsKey)
let value: SessionSettings
if let data {
value = (try? decode(SessionSettings.self, from: data)) ?? SessionSettings.default
} else {
value = SessionSettings.default
}
return value
},
set: { newValue in
let data = try encode(newValue)
userDefaults.set(data, forKey: settingsKey)
}
)
}
}

extension SessionSettingsClient: TestDependencyKey {
static var previewValue: Self {
let storage = LockIsolated(SessionSettings.default)
return .init(
get: { storage.value },
set: { storage.setValue($0) }
)
}

static var testValue: Self {
let storage = LockIsolated(SessionSettings.default)
return .init(
get: { storage.value },
set: { storage.setValue($0) }
)
}
}

extension DependencyValues {
var sessionSettingsClient: SessionSettingsClient {
get { self[SessionSettingsClient.self] }
set { self[SessionSettingsClient.self] = newValue }
}
}
58 changes: 0 additions & 58 deletions Dependencies/TopicSettings.swift

This file was deleted.

12 changes: 12 additions & 0 deletions Utilities/Clamped.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// https://stackoverflow.com/a/40868784
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
min(max(self, limits.lowerBound), limits.upperBound)
}
}

extension BinaryFloatingPoint {
func clampedPercentage() -> Self {
clamped(to: 0.0 ... 1.0)
}
}
20 changes: 16 additions & 4 deletions count.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@
349A81732AF517AD00782538 /* ListeningQuizNavigationFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349A81722AF517AD00782538 /* ListeningQuizNavigationFeature.swift */; };
349D047A2A84EB3A008AFD0A /* SettingsFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349D04792A84EB3A008AFD0A /* SettingsFeature.swift */; };
349D047D2A88E569008AFD0A /* Topic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349D047C2A88E569008AFD0A /* Topic.swift */; };
349D04802A88F23F008AFD0A /* TopicSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349D047F2A88F23F008AFD0A /* TopicSettings.swift */; };
349D04802A88F23F008AFD0A /* SessionSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349D047F2A88F23F008AFD0A /* SessionSettings.swift */; };
34A9749A2A7AB0E600E6F672 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 34A974992A7AB0E600E6F672 /* ComposableArchitecture */; };
34AD2C9A2AF276DB00422C2B /* SessionSummaryFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34AD2C992AF276DB00422C2B /* SessionSummaryFeature.swift */; };
34BDC4952AA58A51001EF005 /* GetMoreVoicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BDC4942AA58A51001EF005 /* GetMoreVoicesView.swift */; };
34BDC4972AA5C2B6001EF005 /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BDC4962AA5C2B6001EF005 /* Haptics.swift */; };
34E2E3EA2AA8C821002B511A /* TopicsFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E2E3E92AA8C821002B511A /* TopicsFeature.swift */; };
34E2E3EE2AA9C86A002B511A /* ConfettiSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 34E2E3ED2AA9C86A002B511A /* ConfettiSwiftUI */; };
34E53DC42AF793DC007E4BAE /* DeterminiteProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E53DC32AF793DC007E4BAE /* DeterminiteProgressView.swift */; };
34E53DC62AF7AD32007E4BAE /* Clamped.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E53DC52AF7AD32007E4BAE /* Clamped.swift */; };
34E53DC82AF7C2D8007E4BAE /* PreSettingsFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E53DC72AF7C2D8007E4BAE /* PreSettingsFeature.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -86,11 +89,14 @@
349A81722AF517AD00782538 /* ListeningQuizNavigationFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListeningQuizNavigationFeature.swift; sourceTree = "<group>"; };
349D04792A84EB3A008AFD0A /* SettingsFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeature.swift; sourceTree = "<group>"; };
349D047C2A88E569008AFD0A /* Topic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Topic.swift; sourceTree = "<group>"; };
349D047F2A88F23F008AFD0A /* TopicSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicSettings.swift; sourceTree = "<group>"; };
349D047F2A88F23F008AFD0A /* SessionSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionSettings.swift; sourceTree = "<group>"; };
34AD2C992AF276DB00422C2B /* SessionSummaryFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionSummaryFeature.swift; sourceTree = "<group>"; };
34BDC4942AA58A51001EF005 /* GetMoreVoicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMoreVoicesView.swift; sourceTree = "<group>"; };
34BDC4962AA5C2B6001EF005 /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = "<group>"; };
34E2E3E92AA8C821002B511A /* TopicsFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicsFeature.swift; sourceTree = "<group>"; };
34E53DC32AF793DC007E4BAE /* DeterminiteProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminiteProgressView.swift; sourceTree = "<group>"; };
34E53DC52AF7AD32007E4BAE /* Clamped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clamped.swift; sourceTree = "<group>"; };
34E53DC72AF7C2D8007E4BAE /* PreSettingsFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreSettingsFeature.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -176,7 +182,7 @@
children = (
340E70522A820E73004B124B /* SpeechSynthesis.swift */,
340E70572A83738F004B124B /* SpeechSynthesisSettings.swift */,
349D047F2A88F23F008AFD0A /* TopicSettings.swift */,
349D047F2A88F23F008AFD0A /* SessionSettings.swift */,
349D047C2A88E569008AFD0A /* Topic.swift */,
34BDC4962AA5C2B6001EF005 /* Haptics.swift */,
344242202ABDBDA8001E2FC8 /* AppIcon.swift */,
Expand All @@ -198,6 +204,7 @@
children = (
344242312AC41EAD001E2FC8 /* DataState.swift */,
344242362AC41F9C001E2FC8 /* EquatableError.swift */,
34E53DC52AF7AD32007E4BAE /* Clamped.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand All @@ -215,6 +222,7 @@
349D04792A84EB3A008AFD0A /* SettingsFeature.swift */,
34276FBE2AB34EB600647AE9 /* TextSpeechFeature.swift */,
34E2E3E92AA8C821002B511A /* TopicsFeature.swift */,
34E53DC72AF7C2D8007E4BAE /* PreSettingsFeature.swift */,
);
path = Feature;
sourceTree = "<group>";
Expand All @@ -225,6 +233,7 @@
348000E42A9CCD4700E7CCEE /* CountBikiView.swift */,
34BDC4942AA58A51001EF005 /* GetMoreVoicesView.swift */,
349802212A7BDDF200E352F5 /* IndeterminiteProgressView.swift */,
34E53DC32AF793DC007E4BAE /* DeterminiteProgressView.swift */,
340B48D02AE8ACDD0046B31C /* StrokeFillShape.swift */,
);
path = View;
Expand Down Expand Up @@ -349,6 +358,7 @@
files = (
340B48D12AE8ACDD0046B31C /* StrokeFillShape.swift in Sources */,
34AD2C9A2AF276DB00422C2B /* SessionSummaryFeature.swift in Sources */,
34E53DC62AF7AD32007E4BAE /* Clamped.swift in Sources */,
344242252ABDDEA7001E2FC8 /* TranslyvaniaTierFeature.swift in Sources */,
34E2E3EA2AA8C821002B511A /* TopicsFeature.swift in Sources */,
3478331F2AA7343A005160DC /* PlanningTopicsFeature.swift in Sources */,
Expand All @@ -357,21 +367,23 @@
340E70582A83738F004B124B /* SpeechSynthesisSettings.swift in Sources */,
344242272ABEBD6F001E2FC8 /* TierProducts.swift in Sources */,
34BDC4972AA5C2B6001EF005 /* Haptics.swift in Sources */,
349D04802A88F23F008AFD0A /* TopicSettings.swift in Sources */,
349D04802A88F23F008AFD0A /* SessionSettings.swift in Sources */,
34BDC4952AA58A51001EF005 /* GetMoreVoicesView.swift in Sources */,
3442422E2AC1D11A001E2FC8 /* TierPurchaseHistory.swift in Sources */,
349802222A7BDDF200E352F5 /* IndeterminiteProgressView.swift in Sources */,
344242342AC41EAD001E2FC8 /* DataState.swift in Sources */,
348000E52A9CCD4700E7CCEE /* CountBikiView.swift in Sources */,
34276FBF2AB34EB600647AE9 /* TextSpeechFeature.swift in Sources */,
349D047A2A84EB3A008AFD0A /* SettingsFeature.swift in Sources */,
34E53DC42AF793DC007E4BAE /* DeterminiteProgressView.swift in Sources */,
340E70532A820E73004B124B /* SpeechSynthesis.swift in Sources */,
340A51CD2A78EC78007DCA0C /* App.swift in Sources */,
344242232ABDD814001E2FC8 /* AppIconFeature.swift in Sources */,
344242372AC41F9C001E2FC8 /* EquatableError.swift in Sources */,
349A81732AF517AD00782538 /* ListeningQuizNavigationFeature.swift in Sources */,
34276FC42AB5899C00647AE9 /* AboutFeature.swift in Sources */,
349D047D2A88E569008AFD0A /* Topic.swift in Sources */,
34E53DC82AF7C2D8007E4BAE /* PreSettingsFeature.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,37 +48,37 @@
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
"version" : "1.0.4"
"revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307",
"version" : "1.0.5"
}
},
{
"identity" : "swift-composable-architecture",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-composable-architecture.git",
"state" : {
"revision" : "195284b94b799b326729640453f547f08892293a",
"version" : "1.0.0"
"revision" : "9b0f600253f467f61cbd53f60ccc243cc4ff27cd",
"version" : "1.3.0"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
"revision" : "ea631ce892687f5432a833312292b80db238186a",
"version" : "1.0.0"
"revision" : "6155400cb15b0d99b2c257d57603d61d03a817a8",
"version" : "1.0.1"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "edd66cace818e1b1c6f1b3349bb1d8e00d6f8b01",
"version" : "1.0.0"
"revision" : "65fc9e2b62727cacfab9fc60d580c284a4b9308c",
"version" : "1.1.1"
}
},
{
Expand Down Expand Up @@ -113,17 +113,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swiftui-navigation",
"state" : {
"revision" : "f5bcdac5b6bb3f826916b14705f37a3937c2fd34",
"version" : "1.0.0"
"revision" : "74adfb8e48724c50d0ac148c658ae5fa7dfd6b6d",
"version" : "1.0.3"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "302891700c7fa3b92ebde9fe7b42933f8349f3c7",
"version" : "1.0.0"
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
"version" : "1.0.2"
}
}
],
Expand Down
2 changes: 1 addition & 1 deletion count/Feature/AboutFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import StoreKit
import SwiftUI

struct AboutFeature: Reducer {
struct State: Equatable {
struct State: Equatable, Sendable {
var appIcon: AppIconFeature.State
var transylvaniaTier: TransylvaniaTierFeature.State

Expand Down