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

Append visionOS support #90

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,11 @@
PRODUCT_BUNDLE_IDENTIFIER = com.swiftlee.RoadmapExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = "1,2,7";
};
name = Debug;
};
Expand Down Expand Up @@ -344,10 +345,11 @@
PRODUCT_BUNDLE_IDENTIFIER = com.swiftlee.RoadmapExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = "1,2,7";
};
name = Release;
};
Expand Down
4 changes: 4 additions & 0 deletions Example/RoadmapExample/RoadmapExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ struct ContentView: View {
var body: some View {
#if os(macOS)
roadmapView
#elseif os(visionOS)
NavigationStack {
roadmapView
}
#else
NavigationView {
roadmapView
Expand Down
5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.7
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -7,7 +7,8 @@ let package = Package(
name: "Roadmap",
platforms: [
.iOS(.v15),
.macOS(.v12)
.macOS(.v12),
.visionOS(.v1)
],
products: [
.library(name: "Roadmap", targets: ["Roadmap"]),
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ Yes, if a user has voted on a feature they won't be able to vote again from with
Roadmap comes with four different preconfigured styles to match most apps. You can change the tintColor, upvote image and more.

### What OS versions are supported?
To keep development of Roadmap easy and fun, we've decided to support iOS 15 & above and macOS Monterey & Ventura for now.
To keep development of Roadmap easy and fun, we've decided to support iOS 15 & above, macOS Monterey & Ventura and visionOS for now.

### Can I sort my roadmap by most voted?
Right now the list of features is loaded in random order. Our thinking is that this will prevent bias for the top voted features. We'll look into ways to make this possible in the future but since the votes are retrieved after the view has been loaded we'll need to look into it.
Expand Down
16 changes: 16 additions & 0 deletions Sources/Roadmap/Extensions/MaterialExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// MaterialExtensions.swift
//
//
// Created by Florent Morin on 04/12/2024.
//

import SwiftUI

extension Material {

static public var defaultCellMaterial : Material {
Material.regular
}

}
2 changes: 1 addition & 1 deletion Sources/Roadmap/RoadmapFeatureView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct RoadmapFeatureView: View {
horizontalCell
}
}
.background(viewModel.configuration.style.cellColor)
.background(viewModel.configuration.style.cellBackground)
.clipShape(RoundedRectangle(cornerRadius: viewModel.configuration.style.radius, style: .continuous))
.task {
await viewModel.getCurrentVotes()
Expand Down
10 changes: 6 additions & 4 deletions Sources/Roadmap/RoadmapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public struct RoadmapView<Header: View, Footer: View>: View {
private var filterHorizontalPadding: CGFloat {
#if os(macOS)
return 12
#elseif os(visionOS)
return 42
#else
return 22
#endif
Expand Down Expand Up @@ -73,25 +75,25 @@ public struct RoadmapView<Header: View, Footer: View>: View {

public extension RoadmapView where Header == EmptyView, Footer == EmptyView {
init(configuration: RoadmapConfiguration) {
self.init(viewModel: .init(configuration: configuration), header: EmptyView(), footer: EmptyView(), selectedFilter: "")
self.init(viewModel: .init(configuration: configuration), header: EmptyView(), footer: EmptyView(), selectedFilter: RoadmapViewModel.allStatusFilter)
}
}

public extension RoadmapView where Header: View, Footer == EmptyView {
init(configuration: RoadmapConfiguration, @ViewBuilder header: () -> Header) {
self.init(viewModel: .init(configuration: configuration), header: header(), footer: EmptyView(), selectedFilter: "")
self.init(viewModel: .init(configuration: configuration), header: header(), footer: EmptyView(), selectedFilter: RoadmapViewModel.allStatusFilter)
}
}

public extension RoadmapView where Header == EmptyView, Footer: View {
init(configuration: RoadmapConfiguration, @ViewBuilder footer: () -> Footer) {
self.init(viewModel: .init(configuration: configuration), header: EmptyView(), footer: footer(), selectedFilter: "")
self.init(viewModel: .init(configuration: configuration), header: EmptyView(), footer: footer(), selectedFilter: RoadmapViewModel.allStatusFilter)
}
}

public extension RoadmapView where Header: View, Footer: View {
init(configuration: RoadmapConfiguration, @ViewBuilder header: () -> Header, @ViewBuilder footer: () -> Footer) {
self.init(viewModel: .init(configuration: configuration), header: header(), footer: footer(), selectedFilter: "")
self.init(viewModel: .init(configuration: configuration), header: header(), footer: footer(), selectedFilter: RoadmapViewModel.allStatusFilter)
}
}

Expand Down
6 changes: 4 additions & 2 deletions Sources/Roadmap/RoadmapViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import Foundation

final class RoadmapViewModel: ObservableObject {
static let allStatusFilter: String = "all"

@Published private var features: [RoadmapFeature] = []
@Published var searchText = ""
@Published var statusToFilter = "all"
@Published var statusToFilter = RoadmapViewModel.allStatusFilter

var filteredFeatures: [RoadmapFeature] {
if statusToFilter == "all" && searchText.isEmpty {
Expand Down Expand Up @@ -73,7 +75,7 @@ final class RoadmapViewModel: ObservableObject {
}

self.statuses = {
var featureStatuses = ["all"]
var featureStatuses = [RoadmapViewModel.allStatusFilter]
featureStatuses.append(contentsOf: Array(Set(self.features.map { $0.localizedFeatureStatus ?? "" })))
return featureStatuses
}()
Expand Down
4 changes: 4 additions & 0 deletions Sources/Roadmap/RoadmapVoteButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ struct RoadmapVoteButton: View {
.overlay(overlayBorder)
}
.buttonStyle(.plain)
#if os(visionOS)
.buttonBorderShape(.roundedRectangle(radius: viewModel.configuration.style.radius))
.clipShape(RoundedRectangle(cornerRadius: viewModel.configuration.style.radius, style: .continuous))
#endif
.onChange(of: viewModel.voteCount) { newCount in
if newCount > 0 {
withAnimation(.spring(response: 0.45, dampingFraction: 0.4, blendDuration: 0)) {
Expand Down
35 changes: 32 additions & 3 deletions Sources/Roadmap/Styling/RoadmapStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ public struct RoadmapStyle {
public var radius: CGFloat

/// The backgroundColor of each cell
public var cellColor: Color

public var cellColor: Color?

/// The backgroundMaterial of each cell
public var cellMaterial: Material?

/// The color of the text and icon when voted
public var selectedForegroundColor: Color

Expand All @@ -48,6 +51,7 @@ public struct RoadmapStyle {
/// - statusTintColor: The tint color of the status view
/// - cornerRadius: The corner radius for the upvote button
/// - cellColor: The backgroundColor of each cell
/// - cellMaterial: The backgroundMaterial of each cell
/// - selectedColor: The color of the text and icon when voted
/// - tint: The main tintColor for the roadmap views.
public init(upvoteIcon: Image,
Expand All @@ -57,7 +61,8 @@ public struct RoadmapStyle {
statusFont: Font,
statusTintColor: @escaping (String) -> Color = { _ in Color.primary },
cornerRadius: CGFloat,
cellColor: Color = Color.defaultCellColor,
cellColor: Color? = nil,
cellMaterial: Material? = nil,
selectedColor: Color = .white,
tint: Color = .accentColor)
{
Expand All @@ -69,7 +74,31 @@ public struct RoadmapStyle {
self.statusTintColor = statusTintColor
self.radius = cornerRadius
self.cellColor = cellColor
self.cellMaterial = cellMaterial
self.selectedForegroundColor = selectedColor
self.tintColor = tint
}

/// Cell background based on current platform
///
/// If current platform is `visionOS`, `cellMaterial` will be preferred. `cellColor` instead.
var cellBackground: some ShapeStyle {
#if os(visionOS)
if let cellMaterial {
return AnyShapeStyle(cellMaterial)
} else if let cellColor {
return AnyShapeStyle(cellColor)
} else {
return AnyShapeStyle(Material.defaultCellMaterial)
}
#else
if let cellColor {
return AnyShapeStyle(cellColor)
} else if let cellMaterial {
return AnyShapeStyle(cellMaterial)
} else {
return AnyShapeStyle(Color.defaultCellColor)
}
#endif
}
}