Zhenyi Tan And a Dinosaur

I’m Back

Err, umm, hello. 😬

Long time no see, it’s been a while since I last wrote anything. Sorry for my absence — I took some time off to deal with a burnout.


What happened

So long story short, I stupidly tried to create a custom video player that looks like the Safari video player, but with features like chapters support, showing thumbnail images when you mouse over the progress bar, and other customization options. It turned out to be too difficult for me. But due to sunk cost fallacy, I continued my attempts.

Since I can’t promote my apps on Twitter now (more on that later), I wanted the video player update to be a big, newsworthy one. The problem was, the more time and effort I put into it, the longer I neglected to update my existing apps, and the more their quality deteriorated. Then I felt more pressured, like “I really have to deliver this thing now.” Then I began to lose self-confidence.

Eventually, I found myself with no motivation left for my work. Although my brain knew what needed to be done, my body just couldn’t follow through. It’s like a self-fulfilling impostor syndrome, and now I am the impostor.


Other stuff

Then I started worrying about the business-y part of my business.

Twitter (and sometimes Reddit) used to be my go-to place for announcing my apps and talking to users. It was a valuable platform for small-time indie developers like me. But then Twitter changed their owner, Reddit enshittified their platform, Mastodon is niche, Bluesky is invite-only, and Threads… just doesn’t seem like the right fit. I’m left wondering: Where do I go from here?

I also thought about the potential unsustainability of my business model. My apps are paid upfront, and I’ve tried to keep their prices (I think) reasonably affordable. And I don’t charge anything for updates. However, this means I’ll have to rely on a steady influx of new users to sustain my business. I mean, I won’t switch Vinegar to a subscription model, but I just feel concerned about whether I can maintain this approach?

Then I listened to Under the Radar #234, where Marco Arment said:

I can’t tell you how many ideas I’ve had for other content-blocking methods and ways to make the web more tolerable. I hate the modern web. But if I made an app that was like made the web more tolerable, it would feel like janitorial work to me. Now every day I have a new pile of crap that the web has given me that I have to figure out how to deal with. And that’s just like a negative life for me. I don’t want that.

This really made me think: most of my apps fall into the “make the crappy web tolerable” category, and sometimes, it does feel like janitorial work. Do I want that?

I don’t have the answers to any of these questions.


Now what

I still haven’t recovered from the burnout. But I shipped an update to History Book a while ago, so yay me? I’ll now focus on shipping some inconsequential updates to Vinegar next and slowly ease my way back. I hope I made it.

Adding “Open in Mastodon” to your Mastodon App

I’m not launching a new app. This is just a simple guide for developers of Mastodon apps.

On iOS, the “open in X” behavior is typically implemented with Universal Links. However, since Mastodon is decentralized, there is no official list of URLs developers can use. Fortunately, we can create a simple Safari extension to work around this issue.

“I call on the power of the Mastodon!”


Creating the Safari Extension

  1. Register a custom URL scheme for your app from the Info tab of your project settings.

  2. Create a new Safari extension by choosing File → New → Target…, then choose Safari Extension.

  3. In the manifest.json file, update the "content_scripts" property to match every web page.

    "matches": [ "http://*/*", "https://*/*" ]
    

    You can also remove the "service_worker" and the "default_popup" properties if you don’t plan to use them.

  4. In the content.js file, paste in the following code:

    // A more reliable way is to check for an API response,
    // e.g. https://domain.name/api/v1/timelines/public?limit=1
    // but that requires an extra round trip to the server.
    // So we just do something quick and dirty:
    
    if (document.querySelector('[id*=mastodon]')) {
    
        // If you want to open your app only from the profile and post pages,
        // you can add an additional if statement.
        // if (location.pathname.match(/@(.+?)(\/|$)(\d+)?/))
        // The regex in this statement also lets you access the username and post ID,
        // in case you want to use a custom URL format.
        location.href = `blackranger:${location.host}${location.pathname}${location.search}`;
    
    }
    

  5. That’s it!

Note: You will also need to handle the incoming URLs in your app by implementing the application(_:open:options:) method in your AppDelegate. If your app uses SwiftUI, you can use the onOpenURL(perform:) view modifier to handle the URLs.


Customizing the Extension

  1. You should update the extension’s name and description in the messages.json file.

  2. You should also add some icons for the extension. Use your app’s icon as the basis for the extension’s icons, but remember to add the rounded corners yourself. The toolbar icons should be monochrome PNG files with an alpha channel.

  3. To ensure that the extension is effective, you should instruct your users to enable the extension and grant it permission to access all websites.

  4. As an advanced option, you can add the domains of Mastodon URLs to a database when opening the app from those URLs. This will allow you to navigate directly to the appropriate view in your app when the user taps on a link without going through the app-Safari-app hoop.


The source code for this project is available on GitHub, and I’m happy to answer any questions you may have. You can reach out to me on Mastodon or via email.

Launch: Rewinder

Rewinder is a Safari extension that lets you go back in time and see how websites looked in the past.

Okay, but that’s just marketing-speak. Rewinder is actually a front-end for the Wayback Machine API, and its UI is inspired by Time Machine. But instead of using arrow buttons, you can navigate through different snapshots by simply scrolling the page.

(Not shown: the time it took to load all of these snapshots.)

You’re probably already familiar with Wayback Machine, so I won’t repeat its benefits here. (In case you’re not familiar with Wayback Machine, here’s a blog post generated by ChatGPT for you.)

Keep in mind that archive.org is very slow, so refresh the page if snapshots take too long (say, longer than 1 minute) to load. And avoid opening multiple archive.org pages too quickly, or you may encounter the 429 Too Many Requests error.


Rewinder Pricing

Rewinder is available for $1.99 on the App Store with no subscriptions, in-app purchases, ads, or tracking. Plus, it’s a universal purchase, so you only need to buy it once to use it on all your Apple devices. I hope you find Rewinder useful.


Rewinder Privacy Policy

Rewinder uses the Wayback Machine API, so check out their privacy policy. The Safari extension does not collect, store, or transmit any personal information.


Rewinder Support

If you have any questions, you can contact me via email or Mastodon.

Launch: Medley Music Player

Medley is a music player that looks like the pre-iOS 6 Music app.


The Backstory

A few weeks ago, I was dealing with some SwiftUI bugs in the iOS 16 beta.

The toolbar was buggy, so I replaced it with a custom view. The navigation bar was buggy, so I replaced it with another custom view.

Then it suddenly hit me: if I can replace the navigation bar and toolbar, I can probably build an iOS 6 style UI with SwiftUI. Then I added background images and text shadows to my custom views, and it just… worked.


Sidenote: SwiftUI and Customization

The system components are notoriously hard to customize in SwiftUI. Like you’d get 4 methods for customizing something, and if they don’t do what you want, that’s it. The navigation bar and toolbar are probably the hardest to customize because they’re not even Views in SwiftUI. (They get automatically generated when you use some modifiers.)

So if I can do without the navigation bar and toolbar, I should be able to build the rest of the iOS 6 style UI with relative ease.


Anyway

So I wanted to make an app that looks like a pre-iOS 7 app. Ideally, it should have a healthy mix of images and text in the UI to keep it interesting, it shouldn’t require a lot of text input because the flat keyboard will break the illusion, and it shouldn’t be a UITableView snooze-fest. In the end, I decided to make a music player. I call it Medley.

Medley is not a pixel-perfect re-creation of the Music app. For example, it uses San Francisco instead of Helvetica, and almost all the icons are SF Symbols. It doesn’t have Cover Flow because I’m not sure why Apple discontinued it after the patent suit.

There’s a Cover… Wall thingy, though.

You can play music from Apple Music if you’ve already added them to your music library. But Medley doesn’t support browsing the Apple Music catalog or adding songs from Apple Music into your library. I hope you’ll like it.


Medley Pricing

Medley is available for $2.99 on the App Store, with no subscriptions, no in-app purchases, no ads, and no tracking.

Currently, Medley is only available on the iPhone. I will consider making an iPad version if there’s enough interest.


Medley Privacy Policy

Medley does not collect, store, or transmit any personal information.


Medley Support

If you have any questions, email me or contact me on Twitter.

Launch: Sideways

Sideways is a Safari extension for rotating webpages when screen rotation is off.

I still can’t get over how weird this looks.

If you always keep the screen orientation on your phone locked, but… gah! I think this app is quite self-explanatory. Check it out if you want.


Sideways Pricing

Sideways is free, with no in-app purchases, no ads, and no tracking. Get it in the App Store today.


Sideways Privacy Policy

Sideways does not collect, store, or transmit any personal information.


Sideways Support

If you have any questions, email me or contact me on Twitter.

Launch: Monies

Now you can also track your monies like it’s 1979!

Monies is a very basic expense tracker app I made for my wife. It looks like a spreadsheet because she’d been using Excel to track her expenses. But it’s like a spreadsheet on… whatever the opposite of steroids is.

The whole point of the interface is to let you add items quickly. Just tap anywhere to start editing, then tap next on the keyboard to jump to the next cell. To delete an item, just clear its price and name, then tap Save.

There are no fancy features like linking bank accounts, charts, or even a settings screen. It is available for free on iPhone and iPad, and it syncs via iCloud. I hope you find it useful.


Monies Privacy Policy

Monies does not collect, store, or transmit any personal information.


Monies Support

If you have any questions, email me or contact me on Twitter.

Launch: Sessions

Sessions is a free Mac app for watching WWDC session videos.

I made it because the videos in Apple’s Developer app are too small and not resizable:

In Sessions, the video view looks like this:

That’s it. That’s the only feature. The app is probably worse than the Developer app in every other way. If you’re interested, you can download it here.

Note: Sessions does not auto-update itself. You must visit this page and manually download new versions as needed.


Sessions Changelog

Sep 2022 - The videos in Apple’s Developer app are now resizable, Sessions is no longer supported.
1.0.5 - Added events (e.g. WWDC22, WWDC21) to the sidebar.
1.0.4 - Added a preference to set SD/HD videos as the default.
1.0.3 - Added keyboard shortcuts ⌘ + ←/→ to skip backward/forward 10 seconds.
1.0.2 - Added “All Topic” sidebar item for easier searching.
1.0.1 - Added playback speed control.

My SwiftUI Wishlist for WWDC

These are some SwiftUI weirdness I discovered while making History Book. I hope they get fixed at WWDC.

This list is incomplete. I only include things that seem straightforward to fix.


1. View modifiers that work as expected

I wish it is possible to change the background of a List with a .background() modifier. Yes, I’m aware of the UIKit workaround.

I also wish it is possible to change the height and width of a .sheet with a .frame(height:width:) modifier. No, I’m not aware any workaround… unless you want to reimplement your own sheet.


2. Some control over the default spacing

Here’s the toolbar of the Notes app:

Here’s the SwiftUI replica:

The alignment of the toolbar buttons are slightly off:

Why? I don’t know. But if you set the maxHeight and maxWidth of the button to .infinity, you’ll notice some non-removable spacing around the button: And no, .ignoresSafeArea() doesn’t work in this case.

Similarly, it happens to views inside NavigationLink as well:

I wish it is possible to ignore the spacing.


3. Miscellaneous improvements to edit mode in List

  1. The “Done” text should be bold in an EditButton.

  2. The chevrons in NavigationLinks should be hidden in edit mode. (It would be even better if we have some control over the accessoryType.) Note the whacky animation of the chevrons.

  3. The items in a List should be clickable (e.g. to rename the items) in edit mode.

  4. The state of the highlighted item is buggy in a multiple-selectable List when the List enters/leaves edit mode.

  5. If the items in a List have rounded corners, the .swipeActions buttons should have rounded corners as well.

  6. Make it possible to attach a .confirmationDialog to a .swipeActions button. Compare it with the confirmation dialog in the Notes app: Notice how the delete button expands instead of disappears when the confirmation dialog shows up.

Launch: History Book

This is a somewhat common occurrence for me:

Credit: u/kissesnocturnal

You can search your browsing history in Safari, but that only works for the page titles and URLs. It gets tiresome when there are 20 pages with similar titles.

To solve this, some people will bookmark everything and meticulously tag them. Some people will copy URLs into their notes app and then add a description of why they saved them. Some people resort to never closing their browser tabs.

If you have similar problems, you might want to check out History Book.


History Book automatically saves the content of your browsing history for searching. And it does it in a privacy-friendly way.

By default, it only auto-saves pages that contain an article, so you don’t have to worry about it saving your sensitive data.

If you want, you can configure it to auto-save every webpage. If you are feeling paranoid, you can disable auto-save entirely. You can also exclude websites that you never want to auto-save.

Like Safari’s Reader Mode, it extracts and only saves the meaningful text. You can use it as a read-it-later app, but I mostly use it as a search app.


History Book Pricing

History Book is available for $6.99 on the App Store, with no subscriptions, no in-app purchases, no ads, and no tracking.

It’s a universal purchase so you only have to buy it once to use it on iPhone, iPad, and Mac. I hope you find it useful.


History Book Privacy Policy

History Book syncs the saved items via iCloud. Apart from the iCloud sync, History Book does not collect, store, or transmit any personal information.


History Book Support

If you have any questions, email me or contact me on Twitter.

Time Capsule is Dead. Long Live History Book.

This is also a TestFlight announcement post.

Last year, I wrote an app called Time Capsule. Time Capsule saved the entire webpage (including JavaScript/CSS/images) on your devices. There weren’t enough people interested in the app, so I killed the project.

In hindsight, Time Capsule had a few glaring issues:

  1. Web archiving is a niche thing, especially on mobile. Very few people want to save the entire webpage, especially if the saving process is slow.
  2. The saving process is slow. Because the app needs to fetch all the resources, base64-encode everything, and merge them into an archive file. And there are a lot of resources to process.
  3. The archive files were huge. Who would’ve thought an average webpage with an article is about 10MB?
  4. The sync requires a $3/month subscription. I need to rent a sync server to do cross-platform sync. It’s an ongoing cost, and the only way to cover that is to charge a subscription.

It’s not even a good business plan because most paid users will be the people who save tons of data. $3/month may not be enough to break even.


I still like the idea of saving webpages for full-text searching, though, so I gave it another go and created History Book.

Hopefully I got it right this time.

By default, History Book automatically saves webpages as you browse. And it only auto-saves pages that contain an article, so you don’t have to worry about it saving your sensitive data.

Like Safari’s Reader Mode, it extracts and only saves the “meaningful” text. As a result, the saved pages have no ads and are small enough to store in Core Data and sync via CloudKit.

Unlike Time Capsule’s share extension, History Book’s web extension can save pages that require login because it doesn’t need to load the page again in a separate window.

It uses iCloud for storage and sync. Meaning no ongoing server cost. Meaning no subscription.


History Book will be a paid app that costs $5.99 - $9.99 (I’m still deciding) and it will be available on iPhone, iPad, and Mac. I thought about making it free with an in-app purchase to unlock everything, but I don’t know how to cut features without making it crappy.

If you’re interested in beta testing History Book, send me an email or DM me your email on Twitter, and I will invite you to the beta.

Update: History Book is out!