Screenshots

    A bit more on the Godot game this morning, this time working on background tiles artwork. Made some grey masonry tiles for the end castle sequences. Also tried some background rocks for underground areas. I’m pretty rubbish at anything organic, but they didn’t turn out too bad.

    Auto-generated description: Two rectangular pixel art frames with stone textures, one in brown and the other in gray, are displayed with matching filled versions inside them.
    Right side has the background tiles surrounded with their complementary foreground tiles on the left.

    It pains me that Forgejo’s CI “pipeline running” animation spins anti-clockwise, as if you’re going backwards in time. A metaphor, perhaps? Services get undeployed, binaries go back to the source code, projects return to their seeds of ideas. 🤔💭

    Oh, build’s done. Never-mind. 😀

    A commit titled 'Added the grid image processor' by user 'lmika' was pushed to the main branch with the workflow file 'deploy.yaml', with a yellow spinner spinning in an anti-clockwise direction.

    Was looking at how I could add hazards to my Godot project, such as spikes. My first idea was to find a way to detect collisions with tiles in a TileMap in Godot. But there was no real obvious way to do so, suggesting to me that this was not the way I should be going about this. Many suggested simply using an Area2D node to detect when a play touches a hazard.

    I was hesitant to copy and paste the scene node I had which handled the collision signal and kill the player — the so-called “kill zone” scene —but today I learnt that it’s possible to add multiple CollisionShape2D nodes to an Area2D node. This meant I needed only a single “kill zone” scene node, and just draw out the kill zones over the spikes as children. The TileMap simply provides the graphics.

    Auto-generated description: A game development interface displaying level design with a grid layout, tiles, and collision shapes is shown.

    This discovery may seem a little trivial, but I’d prefer to duplicate as few nodes as a can, just so I’ve got less to touch when I want to change something.

    Tried opening my Godot project this morning and was greeted with the following error:

    Auto-generated description: A computer screen displays a development environment with an error message pop-up about an invalid or missing scene file.
    scene/resources/resource_format_text.cpp:284 - res://scenes/falling_platform.tscn:14 - Parse Error: 
    Failed loading resource: res://scenes/falling_platform.tscn. Make sure resources have been imported by opening the project in the editor at least once.
    Failed to instantiate scene state of "res://scenes/falling_platform.tscn", node count is 0. Make sure the PackedScene resource is valid.
    Failed to load scene dependency: "res://scenes/falling_platform.tscn". Make sure the required scene is valid.
    

    Traced it back to the technique I was using to respawn the falling platform. Looks like Godot didn’t like the two preloads I included:

    # file: scripts/falling_platform.gd
    
    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    @onready var FallingPlatformScript = preload("res://scripts/falling_platform.gd")
    

    This resulted in a parse error and Godot thinking the level scene was corrupted. In retrospect, this kinda makes sense. What I doing was technically a circular dependency, where the scene and script was trying to preload itself. I was hoping Godot was smart enough to recognise this, but I guess not.

    So I had to change the respawn code. I modified it to make use the duplicate method. Here’s the revised version:

    func respawn():	
        var dup = self.duplicate()
        dup.global_position = init_global_position	
    
        # Duplicate also copies the velocity so zero it out here
        dup.velocity = Vector2(0, 0)
        get_tree().current_scene.add_child(dup)
    

    After some limited testing, this seems to work. One good thing about this approach is that it looks like duplicate copies the script, so I no longer need to do anything special here.

    So I guess the lesson here is don’t try to preload the script within itself.

    Adventures In Godot: Respawning A Falling Platform

    My taste of going through a Godot tutorial last week has got me wanting more, so I’ve set about building a game with it. Thanks to my limited art skills, I’m using the same asset pack that was used in the video, although I am planning to add a bit of my own here and there.

    But it’s the new mechanics I enjoy working on, such as adding falling platforms. If you’ve played any platformer, you know what these look like: platforms that are suspended in air, until the player lands on them, at which point gravity takes a hold and they start falling, usually into a pit killing the player in the process:

    The platforms are built as a CharacterBody2D with an Area2D that will detect when a player enters the collision shape. When they do, a script will run which will have the platform “slipping” for a second, before the gravity is turned on and the platform falls under it’s own weight. The whole thing is bundled as a reusable scene which I could drag into the level I’m working on.

    Auto-generated description: A game development interface with a sprite and code editor is shown, from a software environment like Godot.

    I got the basics of this working reasonably quickly, yet today, I had a devil of a time going beyond that. The issue was that I wanted the platform to respawn after it fell off the map, so that the player wouldn’t get soft-locked at at an area where the platform was needed to escape. After a false-start trying to reposition the platform at it’s starting point after it fell, I figured it was just easier to respawn the platform when the old one was removed. To do this I had to solve two problems:

    1. How do I get the platform starting point?
    2. How can I actually respawn the platform?

    Getting The Starting Point

    A Node2D object has the property global_position, which returns the position of the object based on the world coordinates. However, it seems like this position is not correct when the _init function of the attached script is called. I suspect this is because this function is called before the platform is added to the scene tree, when the final world coordinates are known.

    Fortunately, there exists the _ready notification, which is invoked when the node is added to the scene tree. After some experimentation, I managed to confirm that global_position properly was correct. So tracking the starting point is a simple as storing that value in a variable:

    var init_global_position = null
    
    func _ready():
    	init_global_position = global_position
    

    Another option is to use the _enter_tree() notification. From the documentation, it looks like either would probably work here, with the only difference being the order in which this notification is invoked on parents and children (_enter_tree is called by the parent first, whereas _ready is called by the children first).

    Respawning The Platform

    The next trick was finding out how to respawn the platform. The usual technique for doing so, based on the results of my web searching, is to load the platform scene, instantiate a new instance of it, and added it to the scene tree.

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        add_child(dup)
    

    Many of the examples I’ve seen online added the new scene node as a child of the current node. This wouldn’t work for me as I wanted to free the current node at the same time, and doing so would free the newly instantiated child. The fix for this was easy enough: I just added the new node as a child of the current scene.

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    I still had to reposition the new node to the spawn point. Fortunately the global_position property is also settable, so it was simply a matter of setting that property before adding it to the tree (this is so that it’s correct when the newly instantiated node receives the _ready notification).

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        dup.global_position = init_global_position
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    This spawned the platform at the desired positioned, but there was a huge problem: when the player jumped on the newly spawn platform, it wouldn’t fall. The Area2D connection was not invoking the script to turn on the gravity:

    It took me a while to figured out what was going on, but I came to the conclusion that the packed scene was loading properly, but without the script attached. Turns out a Script is a resource separate from the scene, and can be loaded and attached to an object via the set_script method:

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    @onready var FallingPlatformScript = preload("res://scripts/falling_platform.gd")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        dup.set_script(FallingPlatformScript)
    
        dup.global_position = init_global_position
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    Finally, after figuring all this out, I was able to spawn a new falling platform, have it positioned at the starting position of the old platform, and react to the player standing on it.

    The time it took to work this out is actually a little surprising. I was expecting others to run into the same problem I was facing, where they were trying to instantiate a scene only to have the scripts not do anything. Yet it took me 45 minutes of web searching through Stack Overflow and forum posts that didn’t solve my problem. It was only after a bit of experimentation and print-debugging on my own that I realised that I actually had to attached the script after instantiating the node.

    To be fair, I will attribute some of this to not understanding the problem at first: I actually thought the Area2D wasn’t actually being instantiated at all. Yet not one of the Stack Overflow answers or forum post floated the possibility that the script wasn’t being loaded alongside the scene. This does suggest to me that my approach may not be optimal. There does exist a “Local to Scene” switch in the script inspector that could help, although turning it on doesn’t seem to do much. But surely there must be some way to instantiate the script alongside the scene.

    Anyway, that’s for later. For now, I’m happy that I’ve got something that works.

    🔗 cathoderay.tube

    Auto-generated description: A web browser window displays the word computers!!! in large text on a plain white background.

    Yes.

    Running PeerTube In Coolify

    A guide for setting up a basic PeerTube instance on Coolify using a docker-compose file.

    Gave Dave Winer’s Wordland a try today. I like it. It’s quite a nice writing environment to work in. Wrote a couple of long form posts in it, along with a few that were a paragraph or two, and the editor felt great to use. Does a good job growing with the length of the piece too.

    Auto-generated description: A text editor displays a guide titled How To Start A Naming Convention, discussing strategies for naming conventions in technical environments.

    Making A Small Two-Letter Country Code Lookup Page

    A small evening project where I made a simple site designed for Vivalidi’s sidebar to quickly lookup two-letter country codes defined in ISO-3166-1 alpha 2.

    Dusted off Podcast Favourites (last commit 25 April 2022) and fixed a longstanding issue of thumbnails being lost when they’re changed in the feed. Editing the feed properties will now force a refresh of the thumbnail URLs. Didn’t need to change anything else, which was a nice change.

    Auto-generated description: A podcast favourites list showing episodes with titles and descriptions from the ATP - Members Feed.

    🔗 Prefer Numbered Lists to Bullets

    Good arguments for using numbered listed instead of bullets in chat communication. I don’t disagree with any of them. I will say that tend to preferred bulleted lists simply because the chat apps I use tend to make using numbered lists more difficult than it should be. Slack, for example, only starts a “real” numbered list when it detects you type 1.. And once you’ve started, there’s no way to skip ordinals within the same numbered list.

    Auto-generated description: A chat message from Leon Mika lists items with different numbers and includes a section to jot something down.
    Note that "1. This" the only "real" numbered list, and has a different appearance.

    Even Obsidian’s implementation is not perfect. Despite making it easy to start a numbered list at an arbitrary ordinal, it’s still not possible to skip ordinals.

    It’d be simpler if they didn’t try to automatically make “real” numbered lists at all.

    Via: Jim Nielsen

    Trying out Bayou theme by @Mtt on a test blog. Lots to like about it, especially the idea of having the latest micro-post appear in the form of a status message. Very unique.

    Auto-generated description: A minimal blog page titled Leon Mika features categories like About, Archive, and Replies, along with a post mentioning categories being sorted and three other listed posts from 4 February 2025.

    Already making daily note archives for 2025.

    Auto-generated description: A computer interface displaying a folder structure for daily notes from 2022 to 2025, highlighting a note dated January 6, 2025.

    Okay, I think I know why I stopped playing Wordle.

    Auto-generated description: A screenshot of a word-guessing game shows the word TOCK in progress with several guessed words, color-coded hints, and navigation buttons.

    Finding that styling a page with min-height: 100vh causes the need to scroll when I open the page in Vivaldi Mobile, as vh does not recognise vertical space taken up by toolbars. What I actually want is 100dvh (i.e. dynamic view-height) which does. Found this slide helpful (source and via).

    Auto-generated description: Three smartphones display different viewport height measurements labeled as dynamic, largest, and smallest, with Google's branding in the corner.

    This week’s distraction: building a Wordle clone. No particular reason for doing this other than I felt like building one, although I did miss the small time waster of the original Wordle, and watching a game show with my parents that had a similar concept just made those feelings stronger. Main difference between this and Wordle classic: board randomly selects between 4-letter, 5-letter, and 6-letter words; no daily limit or social-media sharing when you guessed the word correctly; and the biggest one: UK English spelling.

    Auto-generated description: A word puzzle game interface shows a grid with the words HOUSE, ALTAR, and POINT, with colour-coded tiles indicating correct and incorrect letter guesses.

    Some remarks on how this was built: I used 11ty to build the static site. It originally started as just a HTML page with some JavaScript, but I wanted to leave the option open for bundling and minifying the JS with Stimulus. The dictionary I got from Hunspell, which is apparently the spell checker Apple has based their work on. There is a little bit of Go to filter and sort the dictionary of words. The words are in sorted order for the binary search algorithm to check if a word exists or not. The puzzle order is predetermined and was done by “shuffling” the indices in a separate array. Base styles are, of course, from simple.css.

    If you’re interested in checking it out, you can find it here. Just be aware that it may not be as polished as much of the other stuff you find out there. Turns out that I can tolerate a fair few shortcomings in things that I build for my own amusement.

    Keyboard Maestro is coming into its own as a way for scheduling recurring tasks. I’ve just set one up for a daily report I need to run. It’s little more than a shell script so I probably could’ve used crontab to do it, but is so much easier configuring and testing it in Keyboard Maestro.

    Auto-generated description: A scheduled report configuration window is shown, with options for date and time selection, and a shell script execution setup displayed.

    I recently got a new phone, a Pixel 9 Pro, which meant I needed to bring Alto Player up to date. I probably could’ve gotten away using the version I was using on my Pixel 6. But I didn’t have a binary build, and I needed to upgrade Gradle anyway, so I decided to spend a bit of time bringing it up to date to API version 35, the version used in Android 15.0. Fortunately it was only a few hours in total, and once I got it running in the simulator, I side-loaded it onto my phone and started using it.

    It worked, but there were some significant UI issues. The title-bar bled into the status bar, the album image in the Now Playing widget was cropped by the curved corners of the phone, and the media notification didn’t display playback controls.

    Auto-generated description: A mobile app displays a list of music tracks, each with a title, duration, and album art symbol.
    Evolution of the window insets, from left-to-right: before any changes, version with the album cover and margin, final version with no album art.

    I set about fixing these issues today, starting with the title-bar and Now Playing widget. These was an issue with the views not respecting the window insets, and after a quick Google search, I found this article showing how one could resolve this by adding a ViewCompat.setOnApplyWindowInsetsListener and reacting to it by adjusting the margins of the view.

    val topLevelLayout = findViewById(R.id.top_level_layout) as CoordinatorLayout
    ViewCompat.setOnApplyWindowInsetsListener(topLevelLayout) { v, windowInsets ->
        val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
        v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
            leftMargin = insets.left
            bottomMargin = insets.bottom
            rightMargin = insets.right
    
            // applying a top margin here would work, but will not have the toolbar
            // background colour "bleed" into the status bar, which is what I want.
        }
    
        val t = v.findViewById<Toolbar>(R.id.toolbar)
        t.updateLayoutParams<ViewGroup.MarginLayoutParams> {
            // make the toolbar a little "narrower" than full height
            topMargin = insets.top * 3 / 4
        }
    
        WindowInsetsCompat.CONSUMED
    }
    

    It took a few attempts, but I managed to get this working. Just using the top inset for the toolbar margin made it a little larger than I liked, so I adjusted the height to be 75% of the inset. This means the toolbar will actually encroach into the area reserved for cut-outs like the front-facing camera. This is arguably not something a “real” Android apps should do, but this is just for me and my phone so it’s fine.

    I went through a few iterations of the album artwork cutoff on the bottom right corner trying to find something I liked. I tried bringing in the horizontal margins a little, but I didn’t like the alignment of the album art in the player, particularly compared to the covers that appear in the track list screen. One thing I didn’t try was raising the bottom margin so that it would fit “above” the curve. But those corners curve in quite a bit, and doing this would sacrifice a lot of vertical space. So I settled on hiding the album art altogether. It’s a bit of a shame to loose it, but at least it looks neater now.

    The next thing I looked at was fixing the playback controls in the media notification. After some investigating, I found that this was because I was not setting the available actions in the PlaybackStateCompat builder. This, if I understand correctly, is used to communicate to various systems the current state of the playing media: what the track name is, whether it’s playing, whether one can skip forward or back. I have my own types for tracking this information — which is probably not correct but I wasn’t completely sure as to what I was doing at the time with Android’s media stack1 — and when I needed to convert this to a type understood by the framework, I made instances of this builder without setting the available actions. Earlier versions of Android seemed not to care, and the controls always appeared on the notification. But I guess they changed that.

    Three smartphone screens display a notification panel, a permission request, and a media notification with music controls.
    Evolution of the playback notification, from left-to-right: before any changes, the request to show notifications upon first launch (this is defined by the system), the playback notifications with controls again.

    One other thing I needed to do was to explicitly ask the user permission to show a notification before I could publish one. This is also relatively new: my experience with Android goes back to the early days where these permissions were disclosed up front when the app was installed. But I can completely understand why they changed it, as it was easy to simply tap through those screens with reading them. I am wondering whether media playback notifications are in some way exempt from these permission checks, as I was actually getting one to appear before I made this changes. But I figured it was probably worth doing anyway, so I added this permission request on first launch. Arguably I should be asking for this permission when playback starts, but again, this is just for me.

    One final thing I needed to address were long album titles. The text view displaying the the album title had a width that autosized to the title itself, and the start and end constraints were set such that it appears centred in the view. This worked for “normal” length titles but when the length became excessive, the text view would fill the entire width of the screen and the title will appear left justified.

    Auto-generated description: Two smartphone screens display a music player with Leaps And Bounds by Paul Kelly.
    Before (left) and after (right) shot of the fixed album title.

    The fix for this was to set the text width to be calculated by the start and end constraints (setting layout_width to 0dp), bringing in the margins a little, and making the label text centre justified. I did this already for the track title, so it was easy to do this here too. Not sure why I didn’t do it earlier, or why I haven’t done it for the artist’s name yet.

    Auto-generated description: A screenshot of a mobile app layout design in Android Studio, featuring a user interface editor with text, images, and buttons.

    This was all rushed, and I’ll admit I wasn’t 100% sure what I was doing. I was going down the route of trial-and-error to get this working, mixed in with web searches and a trip to ChatGPT. And yeah, the methods I used won’t make this a portable Android app that would work on every phone out there. But I’m reasonably happy with how it turned out.


    1. This is still true to this day. ↩︎

    Thanks for my new found fondness of buying mainstream music instead of streaming it, I needed a way to get these albums into Alto Catalogue. There exists a feature for fetching and importing tracks from a Zip referenced by a URL. This works great for albums bought in Bandcamp, but less so for any tracks I may have on my local machine.

    A web interface for uploading a zip file from a URL with fields for artist, album, and default rating.

    I’ve managed to get Alto Catalogue building again after updating Webpack and a few NPM packages, so in theory, I could add an Upload Zip file action. But there’s more to this than simply accepting and unpacking a Zip file. I have to read the metadata, maybe even preview the tracks that will be imported, just in case I’m importing something I rather not (I did see this once, where zipping a bunch of tracks in the Finder introduced duplicates). This already exists for Zip files that are downloadable online.

    I had a though about what my options are, until I remembered that I had a Gokapi instance running in Pikapods. So I tried using that to temporarily host the Zip file with a publicly available URL that could be read by Alto Catalouge.

    The only problem is my internet upload speed is sooooo sloooooow. The Gokapi instance is hosted in Europe, and I suspect the instance itself is a little underpowered. So uploading 100 MB Zip files would take a fair bit of time: maybe 15-30 minutes. When I tried doing this via the web frontend, the connection timed out.

    Fortunately, Gokapi has an API and one of the methods allows you to upload a file in “chunks,” which Gokapi will assemble back into the original file. Even better is that this chunking can be uploaded in parallel.

    So I built a CLI tool which made of this chunking API to upload the Zip files. Once the upload is complete, the tool will display the hot-link URL, which I can copy-and-paste into Alto Catalogue.

    The whole process isn’t fast (again, slow upload speeds). But it works, and I can use this tool to queue a bunch of uploads and let it do its thing while I’m doing something else. I really like tools that do this, where you’re not forced to babysitting them through the process.

    There are a few limitations with it. It doesn’t allow for an awful lot of customisations on the lifecycle of the uploaded file. And the tool stalled out once when my computer went to sleep, and I had to start the upload from scratch. I could probably add something to track the chunks that were successful, allowing one to continue a stalled upload. If this happens frequently, I may look more into adding this.

    But even so, this could be a useful addition to my use of Gokapi for transferring temporary files. If you think this might be useful to you, you can find the tool here.

    Home Screen Of 2024

    It’s just turned 3:00 in the afternoon, and I was alternating between the couch and the computer desk, racking my brain on what to do. With no ongoing projects — a few ideas have been bouncing around, yet none has grabbed me so far, and I had nothing else in a state where I could just slip on some music or a podcast and work on — and seeing a few others make similar posts on their blogs, I’d figured I talk about my home screens.

    A smartphone home screen features various app icons arranged across three panels, set against a blue floral background.

    I realised that I haven’t actually done this before, mainly because my home screens change very slowly (the background hardly ever). Dramatic changes usually come about when I’m setting up a new phone.

    And yet, I do want to talk a little about the apps I have at the moment, and I did want to make sure I had a record of how the home screens looked. And seeing that I needed to keep myself occupied doing something, now is as good a time as any.

    Screen One

    Auto-generated description: A mobile home screen displays weather information, calendar events, and various app icons against a blurred background of yellow and gray flowers.

    This screen contains two widgets — the date and weather widget at the top, and the calendar widget on the right — plus a small collection of apps placed deliberately where they are. The apps I have here are not necessarily the most used (although two of them are) but I like having easy access to them for various reasons.

    Aside from the widgets, the apps I have on this screen — from left to right, top to bottom — are as follows:

    • Micropub Checkin: A silly little Flutter app I used for adding check-ins to lmika.day. The apps in a bit of a neglected state, but I still use it as I get value from tracking places I’ve been.
    • Strata: The note’s app from Micro.blog. This is where I write my short-term notes. I use Google Keep for shopping lists, but everything else goes here.
    • Alto: A music app I wrote, and the main music app I listen to.
    • Pocket Casts: The podcast player app I use. Apart from the web-browser, this and Alto are two of the most used apps I have on my phone.
    • VSReader: Another silly little Flutter app. This is a test build for an RSS reader I was working on a couple of months ago. It’s been a while since I’ve opened this, and I probably should just kill it given that I haven’t made any recent changes to it.
    • Google Wallet: Google’s digital wallet (well, at least their current iteration of their digital wallet). I use it mainly for my train ticket but I do have my credit card in there, just in case I walk out without my “real” wallet.

    The items in the dock are as follows:

    • Phone: My family and I still use the phone quite frequently so this app has remained in the dock since I set the phone up.
    • Messages: This is Android’s messaging app. Much like the phone, I communicate with family mostly via SMS, and now RCS, messages.
    • Play Store: I rarely go to the Play Store, so there’s no real need for this icon to be here. But I haven’t got around to removing it yet.
    • Vivaldi: My web browser of choice.
    • The right most icon changes based on the last used app, which I’m not a huge fan of, as it occasionally changes just as I go to tap it and I launch the wrong app by mistake.

    Screen Two

    Auto-generated description: A smartphone screen displays various app icons arranged in a grid over a floral background.

    A grab-bag of apps I frequently use. Some of them probably should be on the first screen, but since real-estate is at a bit of a premium I just keep them here, and swipe over when I need them.

    From left to right, top to bottom, the apps on this screen is as follows:

    • PTV: The Victorian public transport app. I usually use it to know the arrival time of the tram I take going home. Also useful for trip planning.
    • Plex: I generally don’t watch things on my phone, but before I got my Nvidia Shield, I used this Plex app to Chromecast shows to the TV. It was never great at it though, as it sometimes disconnected from the Chromecast session while the video was running, leaving me with no means of stopping it until I unplugged the Chromecast.
    • Kindle: Kept here as I occasionally use it to read books if I’ve read through my RSS feeds.
    • ChatGPT: I don’t use ChatGPT on my phone that often, but it does occasionally come in useful when a web-search proves fruitless.
    • FastMail: My email provider of choice. Given how often I use it, this is arguably one of those apps that should be on the first screen.
    • Pager Duty: The twenty-four hours on-call paging software I had to use for work. I’m no longer on the on-call roster so it’s probably something I can remove.
    • WhatsApp: What I use for messaging friends. I don’t like the fact that I have a Meta app on my phone, but that’s what my friends chose to use so I’m stuck with it (it’s also better than Viber, which is what we used before).
    • WireGuard: Personal VPN, although I’m currently not using WireGuard for anything right now. I like to keep it mainly because I like the logo.
    • Discord: I’m a member of a few Discord servers, but I use the mobile client mainly to check into the Hemispheric Views Discord.
    • Notion: Where I store my “long term” notes, at least for now.
    • Tusky: Mastodon client.
    • Splitwise: Group expense management and splitting app. This was useful during our European trip last year, where each of us would take in turn to pay for the group.
    • SunSmart: Used to track the current and forecasted UV index. Useful around this time of year if I’m planning to be outside for an extended period of time.
    • Micro.blog: The Micro.blog app, although I occasionally use the web version too.
    • 1Password: My password manager of choice.
    • Realestate.com: Used to browse real-estate, out of curiosity more than anything else.
    • Spotify: My “secondary” music app. I don’t use it for anything that I regularly listen to, but it’s occasionally useful for those once-off tracks.
    • Google Authenticator: Where I keep my 2FA codes.
    • Day One: Before I moved to a web-based journalling app, I used this Day One client for writing journal entries. It wasn’t perfect: there was always syncing delays to/from the Apple platform instances of Day One. But it was fine.
    • Slack: Used mainly for work.
    • Camera: I’m not sure why I have this here, since I almost always use the double power-button tap to bring up the camera. I guess I moved it here from screen one and never removed it.

    Screen Three

    Auto-generated description: A smartphone home screen displays a vibrant wallpaper of yellow flowers and foliage, with apps like Booking, Airalo, and Emirates icons visible at the top.

    This is a screen I hardly ever used, as it’s mainly reserved for apps that are useful while travelling. The Booking.com app and Emirates apps I can probably remove: I was using them mainly to track flights and accomodation during my European trip last year.

    The only one worth keeping is Airalo, which allows you to buy and setup data SIMs that work overseas. This has been really useful to me during my last couple of trips, and I hope to keep using it for trips in the future. It doesn’t offer a lot of data, but any data is better than zero data, as my friends — who continued asking to use my data when we’re out of WiFi range — can attest.

Older Posts →