diff --git a/docs/CNAME b/docs/CNAME
deleted file mode 100644
index 90803b272..000000000
--- a/docs/CNAME
+++ /dev/null
@@ -1 +0,0 @@
-blog.githawk.com
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index e77f231bd..000000000
--- a/docs/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Docs
-
-In this folder, you can find the blog posts about GitHawk.
-The blog is hosted on [githawk.com](http://blog.githawk.com).
-
-## Writing
-
-If you're interested in writing a post, feel free to do so! You can begin writing a post in the `_posts` directory. To preview the site locally, install [Jekyll](https://jekyllrb.com) and run `jekyll serve`.
diff --git a/docs/_config.yml b/docs/_config.yml
deleted file mode 100644
index 3c87240f6..000000000
--- a/docs/_config.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-title: GitHawk Blog
-author: Ryan Nystrom
-twitter_username: githawk
-theme: minima
diff --git a/docs/_data/authors.yml b/docs/_data/authors.yml
deleted file mode 100644
index 7ea267658..000000000
--- a/docs/_data/authors.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Author details.
-ryannystrom:
- name: Ryan Nystrom
- web: https://twitter.com/_ryannystrom
-basthomas:
- name: Bas Broek
- web: https://twitter.com/basthomas
\ No newline at end of file
diff --git a/docs/_includes/header.html b/docs/_includes/header.html
deleted file mode 100644
index 979152157..000000000
--- a/docs/_includes/header.html
+++ /dev/null
@@ -1,35 +0,0 @@
-{{ page.title | escape }}
-
-
- - -So if you remove downloads from Asia Pacific, we're hovering between **10-20 downloads a day**. Not great, but hey, it's moving! - -## Ranking and Ratings - -In early September, if you searched "github" on the App Store, GitHawk would show up way, way down. There were at least 25 apps that would show up before it! - -However, as of October 2, 2017, GitHawk is **ranked 10th when searching for "github"!** That's a huge improvement in only a month! - -The iOS 11 App Store update now shows more ratings than just the previous version. That's a blessing because I like to ship pretty fast. - -We also added two different rating prompts: - -- `SKStoreReviewController` for alert-style prompts after making a notification (or all of them) read. -- A dismissable in-feed unit if you haven't been exposed to the native rater in a while - -Here are those prompts in action: - - - -You can [check out the source](https://github.com/rnystrom/GitHawk/blob/master/Classes/Systems/Rating/RatingController.swift) to see how I mix & match the two rating styles. - -Things seem to be working because GitHawk is sitting pretty with 18 reviews displayed in the search results! - - - -## App Store Impressions - -In version 1.10.0, I wanted to try to push GitHawk in front of more eyeballs. I made 3 updates: - -- Wrote a new App Store description focusing on the first "3 lines" that are displayed (before the "more" truncation). - + I also spent a good amount of time writing a detailed description of features and selling points. Some research told me that your sticky-users (e.g. high retention) will often expand and read the description, so you want something meaty! -- Create an App Preview video. - + I followed [Apple's advice](https://developer.apple.com/support/app-previews/imovie/) for editing the video in iMovie. Stupid simple with a couple text overlays. - + Most of my installs come from iPhones, so I only made one format so that my update cost is low. I followed [this article](https://thenextweb.com/insider/2015/11/15/6-tips-for-creating-the-perfect-apple-app-store-video-for-your-app/#.tnw_7J5BWcHx) and learned about using [FFMPEG](http://www.ffmpegmac.net/) to scale and edit one video for other device requirements. -- Tweaked keywords slightly - -I shipped version 1.10.0 on 9/22/17 if that isn't obvious by this iTunes impressions chart: - - - -All in all, I basically tripled my impressions! - -> I do wish that I could measure the effectiveness of the App Preview, description, and keyword changes independently. π for App Store A/B testing! - -### Product-Page View Sources - -Impressions are nice, but they are counted when someone scrolls by GitHawk on a search results page. What about people that actually view the product page? - -Here's a look at our sources: - - - -I've been trying to boost traffic via tweeting about GitHawk (a lot, sorry). How effective has that been? - - - -Nice, **t.co** (Twitter's short domain) makes up 93 of my page views! Not a ton, but its something! May the tweetstorms continue! - -## Usage - -Lastly, just how active are people that install GitHawk? - - - -Sessions/install seems like a pretty good metric for me to measure GitHawk's usefulness. We're flirting with 10 sessions/install, which I think is great: that's a couple uses per week per person! - -I also spend some time supporting iPad with GitHawk, are any of these active users on iPad? - - - -Not many, sadly. That doesn't mean I should forget about iPad, but also gives me some evidence that I shouldn't bend-over-backward to make everything pixel-perfect. - -## Conclusion - -This has been a fun month! GitHawk is getting a healthy volume of ratings, downloads are low but the spam is gone, and our ranking went up quite a lot! - -For the next month, I want to pump out a few more features and write more. Hopefully these blog posts will help drive downloads too! π diff --git a/docs/_posts/2017-8-30-Finding-Your-App-Name.md b/docs/_posts/2017-8-30-Finding-Your-App-Name.md deleted file mode 100644 index 345ad41ef..000000000 --- a/docs/_posts/2017-8-30-Finding-Your-App-Name.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -layout: post -author: ryannystrom -title: Finding Your App Name ---- - -Coming up with app names is _hard_. I spent the last two weeks completely rebranding my app, [GitHawk](https://itunes.apple.com/us/app/githawk-for-github/id1252320249?mt=8). This post is a reflection on my process for generating ideas, iterating, and validating them. - -## Backstory - -I've been working on an open source GitHub app for a few months now. When I first released the app, I came up with what I thought was a clever name: "Freetime". Developers like to joke about how they have no free time to write open source, so this app was supposed to help get that time back. - -But if you have even the slightest experience with App Store marketing, you probably _cringed_ seeing me use a name with the word "free" in it. Little did I realize that there were _thousands_ of other results. - -That's why I recently [rebranded](http://blog.githawk.com/2017/08/16/Rebranding.html) to the name **GitHawk**, which I wont lie, is an _awesome_ name. In one word it: - -- It expresses that the app does stuff with git -- A hawk is a fast, efficient, and beautiful animal that watches the world from above. Much like GitHawk helps you watch your GitHub projects! -- The capital "G" and "H" in **G**it**H**awk compliments **G**it**H**ub really well, especially when read as "GitHawk for GitHub" -- There are **zero** App Store search results when searching for "githawk" -- The [@githawk](https://twitter.com/githawk) Twitter handle was available, as well as [githawk.com](http://githawk.com) π± - -## What's in a name? - -Creating the perfect app name feels impossible. There are names that work in your head but nobody else understands (_ahem_, "Freetime"). There are names which are _perfect_, only to discover they're taken for something totally unrelated. - -After a while it feels pointless. Every good name is taken. Every other idea you have is awful. - -Are there even any good app names left? Maybe I should just remove a bunch of vowels and slap a "ly" on the end. - -_Don't give up!_ Finding the right app name is hard, but not impossible. - -### Ground Rules - -First, let's set some ground rules for picking a good name: - -- There should be almost no results when searching the App Store - + Results also shouldn't contain any competition! -- A Twitter handle should be available - + Preferably **@name**, but **@nameapp** is ok too -- A **.com** domain should be available - + You can be more loose with this - + name.com, name-app.com, nameapp.com - + Nobody goes to or remembers "name-app.io". Make it easy by sticking to .com - -Whenever you have an idea, run it through this framework. If it doesn't pass all of these tests, just forget it and move on. The last thing you want is to get stuck with a great name that is saturated. - -> This is my thought process. You might not care about Twitter, or maybe you need a Facebook page too. Adjust the rules depending on what you need. - -I thought I struck gold when I came up with the name "Gitbox": there were no App Store results, and @gitboxapp was available on Twitter. I was so excited until I went to [gitbox.com](http://gitbox.com) and saw it was already a Mac app! - -I could have grabbed `gitboxapp.com` and continued, but I predicted lots of confusion. - -### Brainstorm - -I like to write down _tons_ of possible app names that span a wide range of ideas. - -**Start with competitors** - -Research your competition and write down some keywords. For GitHawk, I searched "github" on the App Store and wrote down words that I saw in app titles. Things like "git", "code", "hub", "box", "fork", "branch", and "octo". - -Not a terribly diverse set of words, but its a starting point. - -I also studied the names themselves. _Does this name express what the app does?_ If the names were all over the place, I could probably get away with something more wild. - -However, most GitHub apps have pretty explicit names. That tells me I should avoid getting too weird with the name. - -**[ProductHunt](http://wwwproducthunt.com) and [Dribbble](http://dribbble.com)** - -I use these two sites **a lot** for idea generation. The content centers around apps, so simply by searching for a topic you can search through hundreds of other apps to discover new names. - -Again I searched for "github" on both of these platforms. I found things like "chirp", "tree", "merge". - -### Merge and Iterate - -Go through the words that you wrote down and start prefixing and suffixing them with other words, either ones that you found or new ideas. - -Take note of common patterns that start to emerge. - -- Do you gravitate towards pairing a certain word (e.g. I was obsessed with appending "box" to everything) -- Are there words that compliment each other? -- What common themes start to emerge? - - - -While you're doing this, some other words or ideas might pop up. **Write them down** but don't stop iterating! Those might be good ideas, but don't get distracted from mining your brain, there could be even more gems down there. - -Keep doing this as more ideas come up. You should end up with dozens of options, with a couple of them standing out. - -For me, there were a few pairs that I really liked: - -- "Gitbox" -- "Gitmark" -- "Boltbox" - -However, most of them were either taken or too weird. - -### Start Branching - -Scan over the names you like, and see if any other ideas pop up. - -For me, I ran into things like "tree" and "branch". I started to think about nature. Git has "branches" and "trees", what else do trees do? - -That's when I came up with the name "twig". I loved it! It's like a small tree, sort of like how my app is a small subset of GitHub. - -However I couldn't get a good Twitter handle or domain, even though the App Store had very few results for "twig". - -Continue to go back over all the brainstorming you've done. Sometimes new ideas will pop out. - -### Take a breather - -Now you might have some names that you're starting to gravitate towards. Maybe they sound cool. Or they're really expressive. Or maybe the name is totally unique, and the Twitter and domain are available. - -Act fast, right?!?! - -I actually grabbed gitmarkapp.com and @gitmark on Twitter and _almost_ released an update using this name. But after a night's rest I backed out of the idea. - -You should obviously grab handles and domains if you really love the name, but don't immediately commit. Take a night to sleep on it, think about it in the shower. Go to work and think about it on breaks. - -If you try to grind out a name overnight, you'll burn out and settle on a bad app name. Take a few days for a new name to sink in, and let other ideas blossom while you do other things. - -### Finding GitHawk - -After sitting on "gitmark" for a few days, I reread my notebook full of ideas when the word "sparrow" stood out in my notes next to some brainstorms about trees. - -I explored this space: "bird", "tweet", "fly", "wing", "feather", "crow", "eagle", "hawk". - -Wo, wait. - -"Hawk". - -Let's go back to my keywords and mash some together, does anything stick? I immediately ran into "GitHawk" and _loved_ it. It totally conveys what the app does. - -Next up was Twitter: holy cow [@githawk](https://twitter.com/githawk) is available. Nabbed. - -What about the domain? Somehow `githawk.com` is available and for only **$10/mo**?! Boom, done. - -### End to End - -All said and done, it took me almost a week to create a new name. I did **not** come up with "GitHawk" overnight, nor did the name just "come to me". I sludged through tons of _terrible_ names, mining for that one gem. - -Having a way to seed ideas, branch new words, and setting ground rules gave me a framework to work towards a new app name. Without it, I could have gotten distracted, stressed out, or settled on a stupid name (_again_, "Freetime"). - -Hopefully this helps next time you're trying to name that new app! \ No newline at end of file diff --git a/docs/_posts/2018-03-08-Immutable-Model-Mutability.md b/docs/_posts/2018-03-08-Immutable-Model-Mutability.md deleted file mode 100644 index cf863919e..000000000 --- a/docs/_posts/2018-03-08-Immutable-Model-Mutability.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -layout: post -author: ryannystrom -title: Immutable Model Mutability ---- - -GitHawk is powered by an architecture and a [few](https://github.com/GitHawkApp/FlatCache) [libraries](https://github.com/Instagram/IGListKit) that shine when fed with _immutable models_. - -Why? Because once a model is initialized it cannot be changed, making it safe to read its values across threads and contexts. - -Imagine we're building an employee management app with the following model: - -```swift -Person { - let name: String - let job: String? -} -``` - -We can toss this simple, lightweight model to different threads and controllers without concern for crashes, bugs, or performance issues. - -However sometimes we need to change a model's values. Say this app has a feature that lets you update an employee's job title. - -```swift -let me = Person(name: "Ryan", job: "Engineer") -me.job = "Manager" // ERROR -``` - -## New Mutated Models - -Instead of changing the model instance, we can initialize a _new instance_ with the updated values. - -```swift -let me = Person(name: "Ryan", job: "Engineer") -let newJob = Person(name: me.name, job: "Manager") -``` - -Hurray! We have a new _immutable_ model with our updated values. - -However that's a lot of code to change a single value. What happens if we add a new property? - -```swift -Person { - let name: String - let age: Int // new - let job: String? -} -``` - -We're going to spend the rest of the day chasing down compiler errors. Boo! - -## Less Code with Default Values - -We can take advantage of Swift's default parameter values by adding a function: - -```swift -func update( - name: String? = nil, - age: Int? = nil - ) -> Person { - return Person( - name: name ?? self.name, - age: age ?? self.age, - job: self.job - ) -} -``` - -Not only does this save us from refactor-headaches when adding new properties, but its also less boilerplate when updating a single value! - -```swift -let me = Person(name: "Ryan", age: 29, job: "Engineer") -let birthday = me.update(age: 30) -print(birthday.name) // "Ryan" -``` - -We didn't include `job` in the parameters because it's **optional**. If someone wanted to change `job` to `nil`, then `job ?? self.job` would maintain the _old_ value instead of setting it to `nil`. - -We can solve this by creating a function for each optional value: - -```swift -func with(job: String?) -> Person { - return Person( - name: self.name, - age: self.age, - job: job - ) -} -``` - -Bear in mind that `Person` only has 3 properties. If this were a more complex model, just imagine how much code we'd have to write! π± - -## Saved by Sourcery - -_Full disclosure, this is the first time I've ever used Sourcery._ - -Saving time from writing boilerplate code is _exactly_ what [Sourcery](https://github.com/krzysztofzablocki/Sourcery) was made for. - -We need a template that does two things: - -1. Create an `update(...)` function with every non-optional property as a parameter. -2. Create a `with(...)` function for every optional property. - -We add a new, empty protocol to tell Sourcery which models to run this template on: - -```swift -protocol AutoMutatable {} -``` - -Then our Stencil template creates the `update(...)` and each `with(...)` function based on the type's variables: - -``` -{% raw %} -{% for type in types.implementing.AutoMutatable %} -extension {{ type.name }} { - func update( - {% for variable in type.storedVariables where not variable.isOptional %} - {{ variable.name }}: {{ variable.typeName }}? = nil{% if not forloop.last %},{% endif %} - {% endfor %} - ) -> {{ type.name }} { - return {{ type.name }}( - {% for param in type.storedVariables %} - {{ param.name }}: {% if not param.isOptional %}{{ param.name}} ?? {% endif %}self.{{ param.name }}{% if not forloop.last %},{% endif %} - {% endfor %} - ) - } - - {% for variable in type.storedVariables where variable.isOptional %} - func with({{ variable.name}}: {{ variable.typeName}}) -> {{ type.name }} { - return {{ type.name }}( - {% for param in type.storedVariables %} - {{ param.name }}: {% if not param.isOptional %}self.{% endif %}{{ param.name }}{% if not forloop.last %},{% endif %} - {% endfor %} - ) - } - {% endfor %} -} -{% endfor %} -{% endraw %} -``` - -When Sourcery runs, we get an auto-generated extension that looks like this: - -```swift -extension Person { - func update( - name: String? = nil, - age: Int? = nil - ) -> Person { - return Person( - name: name ?? self.name, - age: age ?? self.age, - job: self.job - ) - } - - func with(job: String?) -> Person { - return Person( - name: self.name, - age: self.age, - job: job - ) - } -} -``` - -Now any time we make a change to the `Person` model or add new `AutoMutatable` models, Sourcery will create mutation methods while keeping our app architecture safe from _actual_ mutable models. - -## Where to now? - -GitHawk isn't actually using Sourcery yet. We have some big [IGListKit changes](https://github.com/Instagram/IGListKit/pull/1081) that have to land, and then embark on some model refactors. However we've been using this [immutable-model-mutability architecture](https://github.com/rnystrom/GitHawk/blob/master/Classes/Issues/IssueResult.swift) for a little while with [great results](https://github.com/rnystrom/GitHawk/blob/45335eb4a0822c07abfb10e9b6f8bc5d1d85282b/Classes/Issues/Merge/GithubClient%2BMerge.swift#L36-L39)! \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index e4d427d21..000000000 --- a/docs/index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -layout: home ----@camroth Had a theory about this: "...backfill their purchase history to make them look legit when they use those accounts to sell reviews"
— Arthur A. Sabintsev (@ArtSabintsev) September 23, 2017