

I would also heavily assume, that it happens client side before sending the content to Signal servers.


I would also heavily assume, that it happens client side before sending the content to Signal servers.


That sounds like you had your Home Assistant person entity tied to your computers too. If you just go off of your phone’s presence, your laptop or desktop really shouldn’t have any effect on that.


My router (FRITZ!Box) provides entities for all network devices, whether they are currently connected to the network or not.
I trigger all my stuff on when my phone connects/disconnects to my home WiFi. For me this is the best, simplest and least error prone way.
For your use case this would mean needing to have WiFi reception outside your house / in front of your garage, and having your phone reliably and quickly connect to the WiFi when you get home, which might not work great, depending on the strength of your WiFi outside your house.


Fantastic, I’ve always wondered where I can get more insights into the F-Droid build process. Thanks!


Ok yup, I completely missed that all releases in between were pre releases, I thought it was just a few. Thanks for the hint.
I do wonder now, why 2025.12 was never stable…


That’s odd, on Aurora store that’s also what I see. On the official Play Store page, it shows last updated January 29th though.
I don’t know, I always found it pretty fun on my Xbox 360.


I already knew about using this for search, but the ticket example is another great use I didn’t think of at all! Totally using this, thanks.


Oh wow, I didn’t know Grayjay had a Jellyfin plugin. I’m happy with Kodi for now, but that’s also a good idea.


From what I read, the modern solution has smooth 60 fps, compared to 2-10 FPS with the JPEG method. Granted, that probably also factors in low network speeds, but I’d imagine you may hit a framerate cap lower than 60 when just spamming JPEGs.


Sadly, from my experience in my German city we don’t have the upper hand here. All streets in the city center are completely overcrowded between 4 and 10 pm during our christmas market season. Same with parking garages near the city center.


Your mention of unix_surrealism is a good point, it really is quite a unique community.
Also for me, the 3 day no poop question will forever be deeply tied to lemmy. It only unfolded days after I first joined, and I believe it was the first big piece of lemmy lore.


Oh wow, I see you went the Kotlin way of hacking together an object with Pairs and Triples. I used to do that too but then it got too confusing lol


You’re right, iterating through the pairs does seem to be the way. After doing some analyzing of what takes how long, it seems my issue is with (quickly) figuring out whether I’ve already connected all nodes or not.


My algorithm is probably pretty horrible, I was purely out to get a solution as fast as possible. I might try improving it after Day12…


First tricky puzzle today, instantly leading me to more or less brute force for part2. I took a lot of time to understand which data structures I needed today, and how to compute my matrix.
Runtimes:
part1: 141ms
part2: 45.9 seconds .-.
class Day08 : Puzzle {
lateinit var boxes: List<Point3D>
lateinit var edges: List<Pair<List<Point3D>, Double>>
override fun readFile() {
val input = readInputFromFile(2025, 8, false)
boxes = input.lines().filter { it.isNotBlank() }
.map { it.split(",") }
.map { Point3D(it[0].toInt(), it[1].toInt(), it[2].toInt()) }
edges = calculateEdges(boxes)
}
private fun calculateEdges(boxes: List<Point3D>): List<Pair<List<Point3D>, Double>> {
val edges = mutableListOf<Pair<MutableList<Point3D>, Double>>()
for (i in boxes.indices) {
for (j in i + 1 until boxes.size) {
edges.add(Pair(mutableListOf(boxes[i], boxes[j]), boxes[i].dist(boxes[j])))
}
}
return edges.sortedBy { it.second }
}
override fun solvePartOne(): String {
val connections = buildEmptyConnectionMatrix(boxes.size)
val mutableEdges = edges.toMutableList()
for (i in 0..<1000) {
connectNextEdge(mutableEdges, connections)
}
val connectionSet = buildConnectionSet(boxes, connections)
return connectionSet.map { it.size }
.sortedByDescending { it }
.take(3)
.reduce(Int::times)
.toString()
}
override fun solvePartTwo(): String {
val connections = buildEmptyConnectionMatrix(boxes.size)
val mutableEdges = edges.toMutableList()
var result: Long? = null
while (result == null) {
result = connectNextEdge(mutableEdges, connections, true)
}
return result.toString()
}
// size: width & height of (square) matrix
private fun buildEmptyConnectionMatrix(size: Int): Array<Array<Boolean>> {
val connections = Array(size) { Array(size) { false } }
for (i in connections.indices) {
connections[i][i] = true
}
return connections
}
private fun connectNextEdge(mutableEdges: MutableList<Pair<List<Point3D>, Double>>, connections: Array<Array<Boolean>>, part2: Boolean = false): Long? {
if (mutableEdges.isEmpty()) return null
val next = mutableEdges[0]
val point = next.first[0]
val other = next.first[1]
connectAll(boxes.indexOf(point), boxes.indexOf(other), connections)
mutableEdges.remove(next)
// all nodes are connected, assume that this is happening for the first time
return if (part2 && connections[0].all { it }) {
next.first[0].x.toLong() * next.first[1].x.toLong()
} else {
null
}
}
private fun connectAll(index: Int, other: Int, connections: Array<Array<Boolean>>) {
fun connectHelper(hIndex: Int) {
val newConnections = mutableSetOf<Int>()
for (i in connections[hIndex].indices) {
if (connections[hIndex][i]) newConnections.add(i)
}
for (boxIndex in newConnections.filter { it != hIndex }) {
for (conn in newConnections.filter { it != boxIndex }) {
connections[boxIndex][conn] = true
}
}
}
connections[index][other] = true
connectHelper(index) // update matrix with all values from node at [index]
connectHelper(other) // update matrix with all values from node at [other]
}
// returns 2D-list of all indices of currently active connections
private fun buildConnectionSet(boxes: List<Point3D>, connections: Array<Array<Boolean>>): Set<List<Int>> {
val connectionSet = mutableSetOf<List<Int>>()
val indices = (0 until boxes.size).toMutableList()
while (indices.isNotEmpty()) {
val list = mutableListOf<Int>()
val curr = indices.removeFirst()
val array = connections[curr]
for (j in array.indices) {
if (array[j]) list.add(j)
}
connectionSet.add(list)
}
return connectionSet
}
data class Point3D(val x: Int, val y: Int, val z: Int) {
fun dist(other: Point3D): Double {
return sqrt(
(x - other.x).toDouble().pow(2) +
(y - other.y).toDouble().pow(2) +
(z - other.z).toDouble().pow(2)
)
}
}
}
full code on Codeberg


I usually use Longs by default too, but this year I mostly got by with Ints, so I’ve stuck to Ints for less memory (my solutions tend to be rather inefficient in that regard .-.)


Tried recursive for part1, didn’t work. LUCKILY for once I was smart and committed anyway, as it was the right solution for part2! I was losing my mind for a bit though as I had originally written my method with Integers…
class Day07 : Puzzle {
val grid = mutableListOf<MutableList<Char>>()
val partTwoCache = mutableMapOf<Pair<Int, Int>, Long>()
override fun readFile() {
val input = readInputFromFile(2025, 7, false)
for (line in input.lines().filter { it.isNotBlank() }) {
grid.add(line.toCharArray().toMutableList())
}
}
override fun solvePartOne(): String {
grid[1][grid[0].indexOf('S')] = '|'
var splits = 0
for (r in 1..<grid.size - 1) {
for (c in 0..<grid[r].size) {
if (grid[r][c] == '|') {
if (grid[r+1][c] == '.') {
grid[r+1][c] = '|'
} else if (grid[r+1][c] == '^') {
grid[r+1][c-1] = '|'
grid[r+1][c+1] = '|'
splits++
}
}
}
}
return splits.toString()
}
override fun solvePartTwo(): String {
val start = grid[0].indexOf('S')
return (1 + processBeamPartTwo(1, start)).toString() // don't forget to count the original timeline
}
private fun processBeamPartTwo(row: Int, column: Int): Long {
if (partTwoCache.contains(Pair(row, column))) {
return partTwoCache[Pair(row, column)]!!
}
if (row == grid.size) return 0L
if (column == grid[row].size || column < 0) return 0L
val out = if (grid[row][column] == '^') { // splitter
1L + processBeamPartTwo(row, column - 1) + processBeamPartTwo(row, column + 1)
} else {
processBeamPartTwo(row + 1, column)
}
partTwoCache[Pair(row, column)] = out
return out
}
}
full code on Codeberg


I’m not fully happy with my parsing today, but oh well. I also thought about just plain building the grid and then rotating it, but “normal input parsing” works too.
class Day06 : Puzzle {
val numsPartOne = mutableListOf<MutableList<Long>>()
val numsPartTwo = mutableListOf<List<Long>>()
val ops = mutableListOf<(Long, Long) -> Long>()
override fun readFile() {
val input = readInputFromFile("src/main/resources/a2025/day06.txt")
val lines = input.lines().filter { it.isNotBlank() }
// parse part1 input
for (line in lines.dropLast(1)) {
for ((c, num) in line.trim().split(" +".toRegex()).withIndex()) {
if (numsPartOne.getOrNull(c) == null) numsPartOne.add(mutableListOf())
numsPartOne[c].add(num.toLong())
}
}
// parse part2 input
var numList = mutableListOf<Long>()
for (c in 0..<lines.maxOf { it.length }) {
var numStr = ""
for (r in 0..<lines.size - 1) {
numStr += lines[r].getOrElse(c) { ' ' }
}
if (numStr.isBlank()) {
numsPartTwo.add(numList)
numList = mutableListOf()
} else {
numList.add(numStr.trim().toLong())
}
}
numsPartTwo.add(numList)
// parse operators
ops.addAll(
lines.last().split(" +".toRegex())
.map { it.trim()[0] }
.map {
when (it) {
'*' -> { a: Long, b: Long -> a * b }
'+' -> { a: Long, b: Long -> a + b }
else -> throw IllegalArgumentException("Unknown operator: $it")
}
}
)
}
override fun solvePartOne(): String {
return numsPartOne.mapIndexed { c, list -> list.reduce { a, b -> ops[c](a, b) } }.sum().toString()
}
override fun solvePartTwo(): String {
return numsPartTwo.mapIndexed { c, list -> list.reduce { a, b -> ops[c](a, b) } }.sum().toString()
}
}
full code on Codeberg
That shouldn’t be too restricting for people using discord to chat with their friends though.