Giter Club home page Giter Club logo

roadmap's Introduction

RoadmapHeader Copy@1x

⚠️ The countAPI which we use in Roadmap to let users vote has been down for six weeks or so. They're looking into it, but currently roadmap is unfortunately not working. The provider of the countapi has let us know that they don't expect the API to be fixed anytime soon, so we are looking for alternatives.

Have a look at this PR for an alternative that we might introduce as the default soon! #71

Here's a step by step guide to host your own server with Vapor (Swift): https://github.com/valentin-mille/RoadmapBackend

Roadmap

Publish your roadmap inside your app and allow users to vote for upcoming features, without having to create a backend!

Example

RoadmapHeader@1x

Setting up Roadmap

Create a Roadmap JSON

Roadmap works with a remote JSON configuration listing all features and their statuses. We recommend hosting it on GitHub Pages or simplejsoncms.com.

An example JSON looks as follows:

[
    {
        "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
    }
]

The keys id, title are mandatory and all have to be strings. You can use any value for status or description.

Support For Localization

If you are looking to support localization, then you need to add extra optional parameters in your JSON localizedTitle, localizedDescription and localizedStatus like:

[
  {
    "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"
      }
    ]
  }
]

Keep a list of finished features

If you add isFinished as true to a feature in your JSON, the voting view will be hidden for the users & no API call will be made to fetch votes. This is an optional value and it's default value is false.

Add Roadmap using Swift Package Manager

Add https://github.com/AvdLee/Roadmap.git within Xcode's package manager.

Create a Roadmap Configuration instance

Create a new Roadmap configuration following the documentation:

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

Optional you can also handover a request for more advanced endpoints, for example protected by 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
)

Use the configuration to construct the view

And use the configuration inside the 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

Styling

By initializing the RoadmapConfiguration with a RoadmapStyle you can create your own styling.

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

Templates

If you don't wan't to configure your own style you can also use one of the templates. You have the option between Standard, Playful, Classy and Technical so pick whichever works best for your app.

Example

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)
    }
}

Persisting Votes

By default, Roadmap will utilise the Free Counting API to store votes, you can check out their website for more information. A namespace is provided for you, utilising your application's bundle identifier, but you can override this when initalising the RoadmapConfiguration.

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

Defining Custom Voter Service

If you'd rather use your own API, you may create a new struct conforming to FeatureVoter. This has two required functions in order to retrieve the current vote count and to cast a new vote.

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
    }
}

You may then pass an instance of this struct to the RoadmapConfiguration.

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

FAQ

Does Roadmap prevent users from voting multiple times?

Yes, if a user has voted on a feature they won't be able to vote again from within your app. Users can intercept your network traffic and replay the api call if they're really desperate to manipulate your votes.

Can Roadmap be customized to fit the look and feel of my app?

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.

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.

Do I need to make changes to my app privacy report if I use Roadmap?

Roadmap does not do any analytics or tracking. If a user voted on a feature it will increment a number on the count api. No identifiers are stored, not even anonymous ones.

Is it possible for stupid people to manipulate my roadmap?

Yes, we wanted to keep Roadmap as simple as possible to setup. If you're worried about competitors (or a user that really wants a specific feature) messing with your priority list, maybe use something else.

Can I help contribute?

Yes, please! We would love to invite you to pick up any of the open issues. We'll review your Pull Requests accordingly.

Projects using Roadmap

If you've integrated Roadmap into your app and you want to add it to this list, please make a Pull Request.

Authors

This library is created in collaboration between Jordi Bruin, Hidde van der Ploeg, and Antoine van der Lee

License

Roadmap is available under the MIT license. See the LICENSE file for more info.

roadmap's People

Contributors

anmolmalhotra avatar avdlee avatar codebeauty avatar cs4alhaider avatar hiddevdploeg avatar iosjess avatar jordibruin avatar juansanzone avatar leaderboy avatar maartenborsje avatar madeiraalexandre avatar ryanfielder avatar sherlouk avatar tylerstillwater avatar vdhamer avatar vinestro89 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

roadmap's Issues

Handle Loading State

When the features are loading initially I'd like to either have a loading indicator or skeleton shimmer views.

Too Many API Requests

Currently the code in this package performs 1 API request per item in the feature list to get its vote count.
I understand why, because that is how countapi works/ed. Since this project seems to be actively seeking a replacement API, it would be a good time to do better.

A couple of naive thoughts/options:

  1. Decouple API calls from this package. Give consumer full control.
  2. Refactor to support an endpoint that fetches a feature list with counts in addition to 1 request per item.
  3. Implement a server app. Maybe RoadmapBackend could be updated to maintain features and support fetching a list.

Support for localization

It would be nice to support for multiple languages, we need to think on how we can implement this either to have different APIs or to have extra JSON keys for for title

For example, I think this will be a good approach:

[
  {
    "id": "1",
    "title": "Adding a map",
    "localized_title": [
      {
        "language": "ar",
        "value": "اضافة خارطة"
      },
      {
        "language": "en",
        "value": "Adding a map"
      }
    ],
    "status": "planned", // This also can be the same as title
    "description": "some description" // This also can be the same as title
  }
]

SemVar Tagging

Hi there!

This project currently uses no SPM supported method of versioning. As a result, SPM considers the package unstable which degrades its ability to be easily integrated.

In particular adding the package as a branch based dependency is not recommended:

Note that packages which use branch-based dependency requirements can't be added as dependencies to packages that use version-based dependency requirements; you should remove branch-based dependency requirements before publishing a version of your package.

Please consider adopting semvar tagging.

Limit the upvote amount for a period of time

If a roadmap contains 10 items, and the user can vote once for each of them, the votes will bring no insight to the app's developper.
If the user knows 2 or 3 items only can be upvoted, her contribution will be more meaningful.
Reseting the limit periodically would maintain the usefulness of Roadmap in an app overtime, and allow the user to participate to items updates.

The dev would set: each user can upvote [x] item(s) every [y] months.
The user would see: "You have x items left to vote for", and when none are left "you can vote again in y-z months", z being the number of months elapsed since the most past vote was cast.

Add a search / filter

So that people can easily find the feature they're looking for. I'll make a pr later today.

Another app to manage JSON Features?

I think if we have an app that can represent the features JSON in a way to make it simpler to manage, then this app can export the final JSON to be uploaded into the API

Competitors?

“ If you're worried about competitors messing with your priority list, maybe use something else.”

It’s probably not (just) competitors that you should worry about here. It’s more that a user that really wants a feature is easily able to outvote the other features as the limit of 1 per user is client-side enforced only.

Maybe use a real api behind it? (Or change the readme so users are made more aware to not use this for roadmaps where commercial interests are at play).

Voting down against feature creep

Many users like simple apps and I wonder, if it'd make sense to add down voting.

Currently, if something doesn't get as many votes it's perhaps a less prioritized feature. But there's no way for users to express their concern that this adds too much complexity etc. Over time an unpopular feature still may collect enough votes by a minority of users even if the majority of the users would be strongly against it.

This would be a mechanism against feature creep.

Maybe it's better to use separate counts then to differentiate +10 -10 votes too see a controversial feature vs a 0 votes summed count (looks like nobody cares).

What do you think?

Virtual Camera?

Android emulator somehow did, iOS simulator for too long have not supported a virtual camera or using the hardware camera from the host. Any way to implement this?

Error: Argument passed to call that takes no arguments

So when I tried to implement this, i get this error and I don't know what's the problem here.

error: argument passed to call that takes no arguments

I have this configuration:

let configuration = RoadmapConfiguration(
    roadmapJSONURL: URL(string: "")!,
    allowVotes: false, // Present the roadmap in read-only mode by setting this to false
    allowSearching: true // Allow users to filter the features list by adding a searchbar
)

and this in the View:

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

It's a project only for iOS, targeting 16.2+.
Using Xcode Version 14.2 (14C18).

statusTintColor doesn't work when strings are localised

The statusTintColor param in RoadmapStyle() receives a closure that translates a status string into a Color.

So far, so good. But the string will get translated if you are using translations to other languages for the status strings in the Roadmap.json file.

This means the closure receives e.g., "verwacht" (NL) instead of "planned" (EN). Which implies that the closure has to deal with localised strings ("planned", "verwacht", "erwartet"…). Firstly, the app developer will typically overlook this. Secondly, it is awkward to deal with.

An interim workaround is to localize the base language within the statusTintColor closure:

static func myStatusTintColor(string: String) -> Color {
     let planned = String(localized: "planned", comment: "Status string to indicated that roadmap item is planned.")
     let unplanned = String(localized: "?", comment: "Status string to indicated that roadmap item is not planned.")

     switch string.lowercased() { // string seems to be localized using JSON file before it gets here &^%$
           case planned: return .plannedColor // defined in assets catalog, to support dark/light color scheme
           case unplanned: return .unplannedColor
           default: return Color.red
     }
}

But now I have to duplicate the localisation information in both the JSON file and in Localizable.Strings.
A cleaner solution would pass status instead of featureStatus when calling statusTintColor.
See RoadmapFeature.swift for all occurrences (3) of featureStatus. One use of this computed property should just use status instead of featureStatus.

    var featureStatus: String? {
        localizedStatus.currentLocal ?? status
    }

The fix is easy, so want me to create a pull request? Rhetorical question, I guess.

SSL error when trying to fetch data

First things first

Thanks for the brilliant package, folks! The setup has been easy and I love the predefined styles!

Setting

However, I'm running into an issue. I've defined the JSON locally and loaded it from the bundle. This works but when the screen appears it gives me SSL errors. The same happens when trying to tap the vote button.

Error message

NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection
 to the server cannot be made." UserInfo{NSErrorFailingURLStringKey=
https://api.countapi.xyz/get/<my-bundle-id>/feature2, NSLocalizedRecoverySuggestion=
Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey
=3,_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask 
<0D7EC436-7228-497B-A1CF-28DCE78A28D3>.<3>, _NSURLErrorRelatedURLSessionTaskErrorKey=()

Tried fixes

I thought it may be due to the fact that I need to allow to bypass App Transport Security (although it is an HTTPS domain) and both whitelisting everything as well as specifying the domain to be exempted doesn't work.

Other thoughts

I'm not sure what other reasons it might have, maybe it is due to me having it only locally as files and it is therefore not correctly registered with the countapi?

Anyways: Looking forward to any other ideas you might have. Thanks again for the package!

Get your own roadmap backend server

Hey there 👋,

I made a back-end server with Swift Vapor so everyone can have their own server to use Roadmap.
The server includes the API to call and the database that persists changes made through the API.

I already use it to track user's votes on my server.

The deployment process has been documented so you can quickly start your own server (for free). If you find any unprecise information in the README, please feel free to tell me.

👉 You can check the source code here, too: https://github.com/valentin-mille/RoadmapBackend

Handle Failure to Load data initially

If the data fails to load initially we should have a screen with a warning icon & text that says "Something went wrong" with a "Try Again" button underneath it

Searchable on Feature.description

The Search bar currently only searches the title of each Feature. It ignores the descriptions.

On the one hand, the number of roadmap items to vote on shouldn’t be too long. So Search is not too important.

On the other hand, because you do support an optional Search bar, why not have it search the descriptions and titles simultaneously? After all, the user can’t predict exactly how Searchworks. And the user may not know if the search text is part of the title.

Q: should the Title <> Title+Description need to be configurable for developers?
A: it can obviously be done (Bool > enum?), but probably ok to just switch from Title only to Title+Description. It won’t really break anything, and users will see it as a bug fix. If they notice at all 🤔

If you want me to implement it, let me know preference w.r.t. configurability question.

App icon too small

PlPlease update the app icon to conform to the macos specification. The app icon looks smaller than the mac standard

Data-race conditions

The Swift Package Index says there are 2 errors w.r.t. Swift 6's data-race checking.
If I start re-enabling the code in my own app, I will try to provide a pull-request.
But it is fine if somebody beats me to it.

Don't do api call for features that are finished

I would want to keep a list of all "finished" features as well, but I don't want to show the votes for these features anymore.

Since the status can be anything, I'm not sure if there's a way to link it to that, but maybe we can think of a way to hide the votes for particular situations.

Categorization system

Hi, I think this library's idea is very good and believe could be helpful tools for both of developers and users, and I want contribute this wonderful library.

In order to improve the experience of the voting function of the users, I suggest categorization system, which is able to give more context of feature to users.

For example, developers could classify them into user interface improvements, bug fixes, performance enhancements, and new features.

It's what I think it looks like

Categorization

When the user selects a category, the functions are filtered according to the selected category.

This will allow users to understand more about the background of the feature and to better classify and view the features they want to see.

What do you think of my idea?

Use Logger instead of print()

The types FeatureVoterCountAPI and FeaturesFetcher use the print() function to print debug statements to the console. This is effective but could be leveled up by using the Logger type.

A nice to have enhancement could be to add a configuration item to RoadmapConfiguration to disable logging altogether.

Closure for if feature has already been voted

I'd like the ability to show a toast message whenever a user taps on a feature they already voted for.
I can handle showing the message, but I need the RoadmapFeatureViewModel to be able to take in a closure & execute it inside of this guard statement:
func vote() {
guard !feature.hasVoted else {
print("already voted for this, can't vote again")
return
}

Sync with GitHub issues with `roadmap` label, upvotes are 👍

It would be great if this could instead pull from GitHub issues that had a roadmap label, or any user-specified label.

Upvotes would translate into 👍 on GitHub. The user would need to authenticate with GitHub when attempting to interact. We may provide a link to the issue on GitHub as well.

Add support for iOS 15

My project has a minimum deployment target of iOS 15.0. I understand that this was a choice made to make Roadmap's development easier and more fun and it's ok. I'm planning on showing the feature only to iOS 16 users, but when I try to import it on my SwiftUI view, Xcode complains the framework is iOS 16 only.

Screenshot 2023-02-27 at 13 25 45

As you can see, I tried @available and canImport, but the first does not work for imports and the latter is only good for checking platform (i.e. Mac, iOS, watchOS, etc).

Any tips?

CountAPI temporarily not working

For a few days, starting on May 1st 2023, the GitHub README.md file for Roadmap stated that the CountAPI was down:

Unfortunately the service that Roadmap uses to increment votes is currently down. We're looking into replacements / fixes!

Subsequent removal of the README message on May 4th (@jordibruin) suggests that the issue is now resolved. But it doesn't seem to work. In particular, I can't create a new workspace, increment counters or fetch counts (all 3 are done here in a single API call). Or at a more pragmatic level, that my app's voting on Roadmap items doesn't work.

I am aware that CountAPI was "sold" by the original developer @mlomb to a commercial API site in 2021. But it hasn't shown up as a paid service there yet.

Can we get a brief update on status, and whether/when it should work?
In particular, this helps decide whether to (temporarily?) disable the Roadmap feature in our apps? [ not a big deal in my case, just requires an App update ]

Some initial testing on my side:

  • one error message says An SSL error has occurred and a secure connection to the server cannot be made, suggesting a SSL certificate problem. CountAPI uses https.
  • I tried switching to a fresh namespace. Didn't help: so it is not about "API works, but old counts are lost"
  • I tried wiping local UserDefaults (remove and reinstall app). This UserDefaults entry prevents "duplicate voting" by storing an array of the Roadmap keys that your mobile device has already voted on.
  • Ping times to api.countapi.xyz are long: 100 milliseconds (from The Netherlands to DigitalOcean USA).

Remove my vote

Is there a way to remove my vote?
For instance I voted for one feature but then I. saw another one which I actually like more and don't want the first one anymore. I would expect that clicking on the feature I voted before will reset it back.

Thanks

Include multiple abstractions to enable custom implementations

Hey mate, I love your work and I think is a great idea. But I'm curious if there's any reason you wouldn't have implemented this library as an easy way to add voting to the app (will show an example below) without enforcing specific UI?

// the AppStore rating implementation is a nice API IMO
@Environment(\.votingRequest) private var vote
// ...
try await vote("someId")

The UI elements could be an optional added-benefit for those that want to use them wholesale but I feel like the basic feature of just "voting" adds a lot of value as-is. In my experience the styling approach you've chosen doesn't scale well nor to everyone's needs, I think you can see this already in the Issues actually.

So if you were going to provide it as a convenience perhaps you could opt for a couple lower level APIs that you simply build on top of, but make it easy for consumers to build fully custom implementations of their own as well.

// Borrowing from `FetchRequest` CoreData property wrappers, namespace could also be provided, or defaulted perhaps to the Bundle ID
@FetchVotes("someId") private var votes

Then you could use the above to build UI on top, at which point I'd then provide a more SwiftUI-y Style as-in how ButtonStyle, etc are implemented.

Anyway, no criticism just genuinely inspired me a little here and I thought I'd share how I would have approached the problem, perhaps its helpful or inspires some new work 👍

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.