Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions Sources/Roadmap/Extensions/ArrayExtensions.swift

This file was deleted.

4 changes: 2 additions & 2 deletions Sources/Roadmap/Extensions/ColorExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ extension Color {

static public var defaultCellColor : Color {
#if os(macOS)
return Color.primary.opacity(0.08)
.primary.opacity(0.08)
#else
return Color(uiColor: .secondarySystemFill)
Color(uiColor: .secondarySystemFill)
#endif
}
}
10 changes: 5 additions & 5 deletions Sources/Roadmap/Models/RoadmapFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ public struct RoadmapFeature: Codable, Identifiable, Sendable {

var hasVoted: Bool {
get {
guard let votes = UserDefaults.standard.array(forKey: "roadmap_votes") as? [String] else { return false }
let votes = Set(UserDefaults.standard.stringArray(forKey: "roadmap_votes") ?? [])
return votes.contains(id)
}
nonmutating set {
var votes = (UserDefaults.standard.array(forKey: "roadmap_votes") as? [String]) ?? []
var votes = Set(UserDefaults.standard.stringArray(forKey: "roadmap_votes") ?? [])
if newValue {
votes.append(id)
votes.insert(id)
} else {
votes.removeAll(where: { $0 == id })
votes.remove(id)
}
UserDefaults.standard.set(votes, forKey: "roadmap_votes")
UserDefaults.standard.set(Array(votes), forKey: "roadmap_votes")
}
}

Expand Down
10 changes: 5 additions & 5 deletions Sources/Roadmap/RoadmapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ public struct RoadmapView<Header: View, Footer: View>: View {

private var filterHorizontalPadding: CGFloat {
#if os(macOS)
return 12
12
#elseif os(visionOS)
return 42
42
#else
return 22
22
#endif
}

private var filterTopPadding: CGFloat {
#if os(macOS)
return 12
12
#else
return 0
0
#endif
}

Expand Down
40 changes: 8 additions & 32 deletions Sources/Roadmap/RoadmapViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,14 @@ final class RoadmapViewModel {
var statusToFilter = RoadmapViewModel.allStatusFilter

var filteredFeatures: [RoadmapFeature] {
if statusToFilter == "all" && searchText.isEmpty {
return features
} else if statusToFilter != "all" && searchText.isEmpty {
if searchText.isEmpty {
return features.filter { feature in
feature.localizedFeatureStatus == statusToFilter
}
} else {
return features.filter { feature in
(feature
.localizedFeatureTitle // check title field...
.lowercased() // Roadmap localizes strings in Roadmap.json, so avoid .localizedCaseInsensitiveContains()
.contains(searchText.lowercased()) ||
feature
.localizedFeatureDescription // ...and check description field
.lowercased()
.contains(searchText.lowercased()))
&&
feature.localizedFeatureStatus == statusToFilter
}
}
} else {
return features.filter { feature in
feature
.localizedFeatureTitle // check title field...
.lowercased() // Roadmap localizes strings in Roadmap.json, so avoid .localizedCaseInsensitiveContains()
.contains(searchText.lowercased()) ||
feature
.localizedFeatureDescription // ...and check description field
.lowercased()
.contains(searchText.lowercased())
}
features.filter { feature in
let matchesStatus = statusToFilter == "all" || feature.localizedFeatureStatus == statusToFilter
let matchesSearch =
searchText.isEmpty ||
feature.localizedFeatureTitle.lowercased().contains(searchText.lowercased()) ||
feature.localizedFeatureDescription.lowercased().contains(searchText.lowercased())

return matchesStatus && matchesSearch
}
}

Expand Down
5 changes: 1 addition & 4 deletions Sources/Roadmap/RoadmapVoteButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,7 @@ struct RoadmapVoteButton: View {
// MARK: - A11y helpers

private var accessibilityValue: String {
let c = viewModel.voteCount
if c == 0 { return "0 votes" }
if c == 1 { return "1 vote" }
return "\(c) votes"
"\(viewModel.voteCount) vote\(viewModel.voteCount == 1 ? "" : "s")"
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

[nitpick] The new implementation no longer handles the '0 votes' case specially. While '0 votes' is grammatically correct, screen readers may benefit from the explicit handling. Consider if the previous distinct handling of '0 votes' was intentional for accessibility purposes.

Suggested change
"\(viewModel.voteCount) vote\(viewModel.voteCount == 1 ? "" : "s")"
viewModel.voteCount == 0
? "No votes yet"
: "\(viewModel.voteCount) vote\(viewModel.voteCount == 1 ? "" : "s")"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Not as per the previous behavior, but since this improves clarity for accessibility, I think it’s a good change to keep.

}

private func announceAccessibility(_ text: String) {
Expand Down
12 changes: 6 additions & 6 deletions Sources/Roadmap/Styling/RoadmapStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,19 @@ public struct RoadmapStyle: Sendable {
var cellBackground: some ShapeStyle {
#if os(visionOS)
if let cellMaterial {
return AnyShapeStyle(cellMaterial)
AnyShapeStyle(cellMaterial)
} else if let cellColor {
return AnyShapeStyle(cellColor)
AnyShapeStyle(cellColor)
} else {
return AnyShapeStyle(Material.defaultCellMaterial)
AnyShapeStyle(Material.defaultCellMaterial)
}
#else
if let cellColor {
return AnyShapeStyle(cellColor)
AnyShapeStyle(cellColor)
} else if let cellMaterial {
return AnyShapeStyle(cellMaterial)
AnyShapeStyle(cellMaterial)
} else {
return AnyShapeStyle(Color.defaultCellColor)
AnyShapeStyle(Color.defaultCellColor)
}
#endif
}
Expand Down
36 changes: 16 additions & 20 deletions Sources/Roadmap/Styling/RoadmapTemplates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,61 +49,57 @@ public enum RoadmapTemplate: CaseIterable {

var fontDesign: Font.Design {
switch self {
case .playful:
return .rounded
case .classy:
return .serif
case .technical:
return .monospaced
default:
return .default
case .playful: .rounded
case .classy: .serif
case .technical: .monospaced
default: .default
}
}

var titleFont: Font {
#if os(macOS)
if #available(macOS 13.0, *) {
return Font.system(.headline, design: self.fontDesign, weight: .bold)
.system(.headline, design: self.fontDesign, weight: .bold)
} else {
return Font.headline
.headline
}
#else
if #available(iOS 16.0, *) {
return Font.system(.headline, design: self.fontDesign, weight: .bold)
.system(.headline, design: self.fontDesign, weight: .bold)
} else {
return Font.system(.headline, design: self.fontDesign).weight(.bold)
.system(.headline, design: self.fontDesign).weight(.bold)
}
#endif
}

var numberFont: Font {
#if os(macOS)
if #available(macOS 13.0, *) {
return Font.system(.body, design: self.fontDesign, weight: .semibold).monospacedDigit()
.system(.body, design: self.fontDesign, weight: .semibold).monospacedDigit()
} else {
return Font.body.monospacedDigit()
.body.monospacedDigit()
}
#else
if #available(iOS 16.0, *) {
return Font.system(.body, design: self.fontDesign, weight: .semibold).monospacedDigit()
.system(.body, design: self.fontDesign, weight: .semibold).monospacedDigit()
} else {
return Font.system(.body, design: self.fontDesign).weight(.semibold).monospacedDigit()
.system(.body, design: self.fontDesign).weight(.semibold).monospacedDigit()
}
#endif
}

var captionFont: Font {
#if os(macOS)
if #available(macOS 13.0, *) {
return Font.system(.caption, design: self.fontDesign, weight: .bold)
.system(.caption, design: self.fontDesign, weight: .bold)
} else {
return Font.caption
.caption
}
#else
if #available(iOS 16.0, *) {
return Font.system(.caption, design: self.fontDesign, weight: .bold)
.system(.caption, design: self.fontDesign, weight: .bold)
} else {
return Font.system(.caption, design: self.fontDesign).weight(.bold)
.system(.caption, design: self.fontDesign).weight(.bold)
}
#endif
}
Expand Down