RoadmapHeader Copy@1x

⚠️我们在 Roadmap 中用于让用户投票的 countAPI 已经宕机大约六周了。他们正在调查此事,但不幸的是,目前 roadmap 无法正常工作。countapi 的提供商已经告知我们,他们预计 API 不会在短期内修复,因此我们正在寻找替代方案。

请查看此 PR,了解我们可能很快会引入作为默认设置的替代方案! #71

这里有一个逐步指南,教你如何使用 Vapor (Swift) 托管自己的服务器: https://github.com/valentin-mille/RoadmapBackend

Roadmap

在你的应用中发布你的 roadmap,并允许用户为即将推出的功能投票,而无需创建后端!

示例

RoadmapHeader@1x

设置 Roadmap

创建 Roadmap JSON

Roadmap 通过远程 JSON 配置文件列出所有功能及其状态。我们建议将其托管在 GitHub Pages 或 simplejsoncms.com 上。

JSON 示例看起来如下所示

[
    {
        "id": "1",
        "title": "Combine sentences",
        "status": "planned",
        "description" : "You can add a little bit of extra context here."
    },
    {
        "id": "2",
        "title": "Open with Finder support",
        "status": "planned"
    },
    {
        "id": "3",
        "title": "Initial Launch",
        "status": "finished",
        "description" : "Release v1 to the public.",
        "isFinished": true
    }
]

idtitle 是强制性的,并且都必须是字符串。你可以为 statusdescription 使用任何值。

本地化支持

如果你希望支持本地化,则需要在你的 JSON 中添加额外的可选参数,例如 localizedTitlelocalizedDescriptionlocalizedStatus,就像这样

[
  {
    "id": "0",
    "title": "Adding a map",
    "localizedTitle": [
      {
        "language": "ar",
        "value": "اضافة خارطة"
      },
      {
        "language": "en",
        "value": "Adding a map"
      }
    ],
    "status": "planned",
    "localizedStatus": [
      {
        "language": "ar",
        "value": "مجدولة"
      },
      {
        "language": "en",
        "value": "Planned"
      }
    ],
    "description": "some description",
    "localizedDescription": [
      {
        "language": "ar",
        "value": "اضافة خارطة لمعرفة الاماكن القريبة"
      },
      {
        "language": "en",
        "value": "Adding a map to view nearby places"
      }
    ]
  }
]

保留已完成功能列表

如果你在 JSON 中将某个功能的 isFinished 设置为 true,则投票视图将对用户隐藏,并且不会进行 API 调用来获取投票。这是一个可选值,其默认值为 false

使用 Swift Package Manager 添加 Roadmap

在 Xcode 的 Package Manager 中添加 https://github.com/AvdLee/Roadmap.git

创建 Roadmap Configuration 实例

按照文档创建一个新的 Roadmap 配置

let configuration = RoadmapConfiguration(
    roadmapJSONURL: URL(string: "https://simplejsoncms.com/api/k2f11wikc6")!
)

可选地,你也可以移交一个请求,用于更高级的端点,例如受 OAuth 保护的端点

var request = URLRequest(url: URL(string: "https://simplejsoncms.com/api/k2f11wikc6")!)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer 1234567890", forHTTPHeaderField: "Authorization")

let configuration = RoadmapConfiguration(
    roadmapRequest: request
)

使用配置来构建视图

并在 RoadmapView 中使用该配置

struct ContentView: View {
    let configuration = RoadmapConfiguration(
        roadmapJSONURL: URL(string: "https://simplejsoncms.com/api/k2f11wikc6")!,
        namespace: "yourappname" // Defaults to your apps bundle id
        allowVotes: true, // Present the roadmap in read-only mode by setting this to false
        allowSearching: false // Allow users to filter the features list by adding a searchbar
    )

    var body: some View {
        RoadmapView(configuration: configuration)
    }
}

Post Portrait@1x

样式

通过使用 RoadmapStyle 初始化 RoadmapConfiguration,你可以创建自己的样式。

public struct RoadmapStyle {
    /// The image used for the upvote button
    let upvoteIcon : Image
    
    /// The image used for the unvote button
    let unvoteIcon : Image
    
    /// The font used for the feature
    let titleFont : Font
    
    /// The font used for the count label
    let numberFont : Font
    
    /// The font used for the status views
    let statusFont : Font
    
    /// The tint color of the status view
    let statusTintColor: (String) -> Color
    
    /// The corner radius for the upvote button
    let radius : CGFloat
    
    /// The backgroundColor of each cell
    let cellColor : Color
    
    /// The color of the text and icon when voted
    let selectedForegroundColor : Color
    
    /// The main tintColor for the roadmap views.
    let tintColor : Color
    
    public init(upvoteIcon: Image,
                unvoteIcon: Image,
                titleFont: Font,
                numberFont: Font,
                statusFont: Font,
                statusTintColor: @escaping (String) -> Color = { _ in Color.primary },
                cornerRadius: CGFloat,
                cellColor: Color = Color.defaultCellColor,
                selectedColor: Color = .white,
                tint: Color = .accentColor) {
        
        self.upvoteIcon = icon
        self.titleFont = titleFont
        self.numberFont = numberFont
        self.statusFont = statusFont
        self.statusTintColor = statusTintColor
        self.radius = cornerRadius
        self.cellColor = cellColor
        self.selectedForegroundColor = selectedColor
        self.tintColor = tint
        
    }
}

Post Portrait Copy@1x

模板

如果你不想配置自己的样式,你也可以使用模板之一。你可以选择 StandardPlayfulClassyTechnical 中的一个,选择最适合你应用的那个。

示例

struct ContentView: View {
    let configuration = RoadmapConfiguration(
        roadmapJSONURL: URL(string: "https://simplejsoncms.com/api/k2f11wikc6")!,
        namespace: "roadmap",
        style: RoadmapTemplate.playful.style, // You can also create your own RoadmapStyle
    )

    var body: some View {
        RoadmapView(configuration: configuration)
    }
}

持久化投票

默认情况下,Roadmap 将使用 Free Counting API 来存储投票,你可以查看他们的网站以获取更多信息。系统会为你提供一个命名空间,使用你应用程序的 bundle 标识符,但你可以在初始化 RoadmapConfiguration 时覆盖它。

let configuration = RoadmapConfiguration(
    roadmapJSONURL: URL(string: "https://simplejsoncms.com/api/k2f11wikc6")!,
    namespace: "my-custom-namespace"
)

定义自定义投票服务

如果你更愿意使用自己的 API,你可以创建一个新的结构体,使其符合 FeatureVoter 协议。这需要两个必需的函数,以便检索当前的投票数和投出新票。

struct CustomFeatureVoter: FeatureVoter {
    var count = 0

    func fetch(for feature: RoadmapFeature) async -> Int {
        // query data from your API here
        return count
    }
    
    func vote(for feature: RoadmapFeature) async -> Int? {
        // push data to your API here
        count += 1
        return count
    }
}

然后你可以将此结构体的实例传递给 RoadmapConfiguration

let configuration = RoadmapConfiguration(
    roadmapJSONURL: URL(string: "https://simplejsoncms.com/api/k2f11wikc6")!,
    voter: CustomFeatureVoter()
)

FAQ

Roadmap 是否阻止用户多次投票?

是的,如果用户已经对某个功能投过票,他们将无法在你的应用中再次投票。如果用户真的非常想要操纵你的投票,他们可以拦截你的网络流量并重放 API 调用。

Roadmap 是否可以自定义以适应我应用的外观和感觉?

Roadmap 带有四种不同的预配置样式,以匹配大多数应用。你可以更改 tintColor、赞成票图像等等。

支持哪些操作系统版本?

为了保持 Roadmap 的开发轻松有趣,我们决定目前支持 iOS 15 及更高版本,以及 macOS Monterey 和 Ventura。

我可以按投票最多对我的 roadmap 进行排序吗?

目前,功能列表以随机顺序加载。我们的想法是,这将防止对投票最多的功能产生偏见。我们将在未来研究使其成为可能的方法,但由于投票是在视图加载后检索的,因此我们需要对此进行研究。

如果我使用 Roadmap,我是否需要更改我的应用隐私报告?

Roadmap 不进行任何分析或跟踪。如果用户对某个功能投票,它只会增加 count API 上的一个数字。不存储任何标识符,甚至匿名标识符也不存储。

有人可能恶意操纵我的 roadmap 吗?

是的,我们希望保持 Roadmap 设置尽可能简单。如果你担心竞争对手(或真正想要某个特定功能的用户)搞乱你的优先级列表,也许可以使用其他方案。

我可以帮忙贡献吗?

非常欢迎!我们很乐意邀请你认领任何未解决的问题。我们将相应地审查你的 Pull Requests。

使用 Roadmap 的项目

如果你已将 Roadmap 集成到你的应用中,并且想将其添加到此列表中,请提交 Pull Request。

作者

这个库是由 Jordi BruinHidde van der PloegAntoine van der Lee 合作创建的。

许可

Roadmap 在 MIT 许可下可用。有关更多信息,请参阅 LICENSE 文件。