Activity

schwenoldnoah

Wrapped everything up to get the plugin ready to ship.

Set up a GitHub Actions workflow that builds the plugin and automatically publishes a release when I push a version tag. The release ships two things: the plugin JAR you install directly from disk, and the KotlinProject zip so people can try it out without setting up their own Lyricist project first.

Also wrote a proper README with installation steps, feature overview, known limitations, and credits. Added a pluginIcon.svg so it actually shows up with a logo in the plugin settings instead of a blank square.

Attachment
0
schwenoldnoah

You can now drag keys and groups around in the tree, reorder within the same group, move to a different group, nest into a child. I encountered a couple of bugs during the implementation: The drop zone landing in the wrong group when releasing between two groups, group moves defaulting to root when they shouldn’t. Got there eventually.

Also had to completely rethink how drag starts, the default drag-enabled was blocking cell editing, so I switched to manual drag detection that only kicks in after 8px of movement.

Fixed navigation on click stealing focus in Android Studio. Ctrl+click now navigates to source, plain click just edits.

Attachment
0
schwenoldnoah

Fixed the group header text getting cut off. Headers now span the full table width instead of being clipped to the first column. Took a bit of effort, but it works nicely now.

Added a lock feature. You can lock any key or group from the toolbar. Locked rows can’t be edited through the plugin anymore, the icon shows up in the row, and it persists across restarts. Useful for keys with custom lambda logic You don’t want accidentally overwritten.

Also fixed lambda creation. You can now actually specify types like (Int, Int) -> String instead of being stuck with String. The dialog now has a params field with ‘name: Type’ syntax and a separate return type field.

Attachment
0
schwenoldnoah

I fixed a few things in the IntelliJ plugin.

The biggest one was click-to-navigate. When you click a cell in the table, it now jumps to the right place in the Kotlin file. The tricky part was that the string groups can be nested so I had to walk the whole tree recursively instead of just checking the top level.

I also fixed the column order in the table. Keys were showing up in the wrong order because nested groups were collected after the parent’s own keys. I fixed it by first emitting the current group’s keys, then going deeper.

Attachment
0
schwenoldnoah

I’ve been working on Unspeakable, a KMP word game for a while now, and managing translations has always been annoying. The Lyricist strings file is massive: nested data classes, lambda keys, maps of game mode strings. Editing it by hand across multiple locales just sucks.

So about a month ago I started building an IntelliJ plugin that turns the whole thing into an editable table. Rows are keys, columns are locales, you click a cell and type. It writes back directly to the Kotlin source file.

The annoying part was lambda keys — things like { teamName -> “No players in $teamName” }.

IntelliJ’s threading rules also bit me hard. Opening a rename dialog directly from the table caused a crash because the dialog constructor internally touches the workspace index, which isn’t allowed on the main thread anymore in IDEA 2024.3. Took a while to figure out the right way to route it through the platform’s own rename action instead.

Today’s the last day of Flavortown and I need those cookies for a 3D printer, so here we are.

Attachment
0
schwenoldnoah

Shipped this project!

Hours: 123.7
Cookies: 🍪 2580
Multiplier: 20.86 cookies/hr

I built Unspeakable, a digital version of the classic forbidden-word party game Taboo, designed for chaotic in-person play with one phone per team. The idea was simple: physical cards can’t react to what’s happening in the game, but separate connected devices can.

The biggest challenge was the multiplayer architecture. I built a host/client system over WebSockets using Ktor, where one phone acts as the game server and others connect over LAN. Getting state to stay in sync across devices took a lot of debugging. Reusing the host and client logic for the local mode was quite of a hussle. At one point I spent over an hour debugging a stuttering animation, only to realize I’d set my phone’s animation speed to 0.5x ._.

I’m most proud of the UI, it looks really clean and the little details make it feel polished My favourite is the pill-shaped dark/light mode toggle animation.

I never found the right moment to post a devlog, because I always wanted one more feature done first. That’s why they ended up so far apart.

The app ships with ~1.4k English and ~1.4k German cards, a full custom card editor, and bulk JSON import so you can drop in AI-generated decks instantly. Built with Kotlin Multiplatform + Jetpack Compose, runs on Android and Desktop.

schwenoldnoah

I designed the app logo and added a about screen. Players can now reconnect if they drop out mid-game. I also implemented various bug fixes to prepare the project for release.

Attachment
0
schwenoldnoah

I built a new custom card and category editor. You can now create your own categories with name, icon and manage all the cards inside them directly in the app. You can also view, edit and delete the pre-shipped cards/categories. There now also is bulk JSON import option, so you can add much more cards/categories at once. This makes it really easy to just let AI generate you some new cards.

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
schwenoldnoah

I made local mode work. Now, you can add more players and move them between teams in local mode.

Attachment
0
schwenoldnoah

I added a full game modes system. There are now three modes: Sabotage (opponents can inject extra taboo words mid-round), Survival (timer speeds up or slows down based on how well you’re doing), and Snowball (correctly guessed words become new taboo words for the rest of the round). I also added card category filtering in the lobby, so the host can select different categories for the game. Players now have actual roles: EXPLAINER, GUESSER, and OPPONENT. Every Player now sees a different Screen based on their role. Also added a Palette Style picker in personalization, so you can switch between different Material 3 color palette styles/generations.

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
schwenoldnoah

I added a GameOverScreen that displays the final team rankings and match statistics, such as cards played, accuracy, and pace. I also added a ConnectionLostScreen to handle disconnects. I simplified and standardized the navigation. So Settings and Lobby Settings work the same way now. This eliminated a lot of repetitive code, making the individual settings screens much cleaner. The round count settings finally work. There are now three presets to choose from, as well as a slider to set a custom amount. I used GitHub Copilot to add comments to the project, which makes it easier to maintain.

Attachment
Attachment
Attachment
Attachment
Attachment
0
schwenoldnoah

I have simplified the process of joining the lobby. The local IP address of the host device is now encoded in a Base24 join code. The client player can enter the code or scan a QR code containing the code. I have also tweaked the design, changing the color scheme of the buttons, for example, and updating the design of the enter name screen.

Attachment
0
schwenoldnoah

I created a RoundOverviewScreen that shows at the end of each round. It displays the points earned, as well as three lists of the correct, skipped, and wrong cards. The three buttons at the bottom of the playing screen that are used to mark a card as correct, wrong, or skipped now work. I also fixed an issue with the timeslider in the game settings where it would flicker when moved too quickly.

Attachment
0
schwenoldnoah

I have added a LobbySettingsScreen where the host can change the game settings. Currently, only the round time can be changed. When the round starts the host logic now selects a player as explainer. Each round now tracks the correct, skipped and wrong cards, so that they can be shown on an overview screen that I’ll implement later. Before each round starts, the explainer must confirm, that they are is ready to start. I also fixed an issue where the WebSocket wouldn’t close, if the host navigated back to the app’s home screen.

Attachment
Attachment
0
schwenoldnoah

I improved the network logic. Host- and client-side events and handlers are now separate, which should make future development easier. I added backend functionality for player profile pictures, which are currently a hardcoded color. There is also now a lobby screen where players can join different teams before the game starts.

Attachment
0
schwenoldnoah

I added a personalization screen that allows you to change the seed color of the app’s theme, use your wallpaper’s color, turn on the AMOLED toggle to make the background pitch black, and switch between light and dark mode. I had parts of the UI from another project lying around (Flutter, not Compose, though), which I ported using GitHub Copilot. It took a while to perfect the animation of the pill-shaped Dark Mode toggle.

Attachment
0
schwenoldnoah

I created a local API that makes it much easier to add new subpages to the settings page. I then added Lyricist to the project to support different languages (currently German and English). All the hardcoded strings have been replaced with dynamic ones. I have also added an AppSettingsController that saves the app settings as persistent key-value data.

Attachment
0
schwenoldnoah

I enable Android predictive back gestures, which were a bit tricky to get working. They are still marked as experimental API in Decompose. I also added a navigation bar at the bottom of the page. This will allow you to access the settings and a page where you can edit the word cards later on.

0
schwenoldnoah

I implemented Decompose into the app, to make navigation work. The app now has two new pages: The home page, where the user can select whether he wants to play local, join or host a game. There is also a game settings page. This page and its design are not finished yet. Currently, it only contains a field for the player name and host IP address (if connecting to a host). I plan to add more features, such as a round time setting and a QR code for quick connection.
I have also updated the network logic to support players and enable direct messaging to specific players.

Attachment
0
schwenoldnoah

I have refined the Networking and separation of truth logic. I have implemented the timer with synchronization between the devices. Then I animate the circular-wav-progress-indicator according to the time passed. The problem was that the animation was stuttering: half a second animation, half a second pause. After over an hour of debugging I realized that I had changed the animation speed of my whole phone to 0.5x :^)

0
schwenoldnoah

First, i started working on gathering the terms for the game. I ended up using Perplexity, because this was the most easy and qualitative way. I initially started the project with flutter, but realized that Kotlin Multiplatform in combination with Jetpack Compose would be the better choice, because i wanted to use the ‘new’ Material 3 Expressive design in this app. Then I setup the database and seeded it with the around 1.8k English and 1.7k German terms. After that, I created the foundation for the planed local multiplayer using WebSocket. Then I spent a lot of time refining the UI, which I am very happy with now.
Now I’ll need to update the game-state to make the timer and points work.

Attachment
0
schwenoldnoah

I’m working on my first project! This is so exciting. I can’t wait to share more updates as I build.

Attachment
0