Recently Written · git

ink-update

Ink Update: tells you when Rebind, Ink Recents or Ink Dim has a new version. F-Droid, then Google Play, then GitHub.

git clone https://github.com/equwal/ink-update

Log | Files | Refs


app/src/main/java/dev/equwal/inkupdate/Versions.kt (3271 bytes)

1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // Copyright (c) 2026 equwal
3 package dev.equwal.inkupdate
4 
5 /**
6  * A version number, with no Android type in it, so the unit tests run on the JVM.
7  *
8  * The app compares two kinds of text: a git tag ("v0.0.15-alpha") and an
9  * installed versionName ("0.0.15-alpha-full"). Both must give the same version.
10  *
11  * The rules:
12  *
13  *  - A leading "v" or "V" is not part of the number.
14  *  - Up to four numeric parts, separated by full stops. Missing parts are 0.
15  *  - The stage is alpha, beta, rc, or none. None is the newest.
16  *  - Text after the stage, like "-full" or "-debug", says nothing. It is dropped.
17  *  - Text that has no number at the start is not a version. [parse] gives null.
18  *
19  * [parse] never throws.
20  */
21 data class Version(
22     val parts: List<Int>,
23     val stage: Int
24 ) : Comparable<Version> {
25 
26     override fun compareTo(other: Version): Int {
27         for (i in 0 until PARTS) {
28             val d = parts[i].compareTo(other.parts[i])
29             if (d != 0) return d
30         }
31         return stage.compareTo(other.stage)
32     }
33 
34     override fun toString(): String =
35         parts.joinToString(".") + STAGE_NAMES[stage]
36 
37     companion object {
38 
39         /** How many numeric parts a version keeps. */
40         const val PARTS = 4
41 
42         const val ALPHA = 0
43         const val BETA = 1
44         const val RC = 2
45 
46         /** No stage word. A finished release is newer than its own rc. */
47         const val FINAL = 3
48 
49         private val STAGE_NAMES = arrayOf("-alpha", "-beta", "-rc", "")
50 
51         /** More than this many digits in one part is not a version number. */
52         private const val MAX_DIGITS = 9
53 
54         fun parse(text: String?): Version? {
55             if (text == null) return null
56             var s = text.trim()
57             if (s.isEmpty()) return null
58             if (s[0] == 'v' || s[0] == 'V') s = s.substring(1)
59 
60             val parts = ArrayList<Int>(PARTS)
61             var i = 0
62             while (i < s.length && parts.size < PARTS) {
63                 var digits = 0
64                 var value = 0
65                 while (i < s.length && s[i] in '0'..'9') {
66                     if (digits >= MAX_DIGITS) return null
67                     value = value * 10 + (s[i] - '0')
68                     digits++
69                     i++
70                 }
71                 if (digits == 0) return null
72                 parts.add(value)
73                 if (i < s.length && s[i] == '.') i++ else break
74             }
75             if (parts.isEmpty()) return null
76             while (parts.size < PARTS) parts.add(0)
77 
78             return Version(parts, stageOf(s.substring(i)))
79         }
80 
81         /** The stage word in the text that follows the numbers. */
82         private fun stageOf(rest: String): Int {
83             val r = rest.lowercase()
84             return when {
85                 r.contains("alpha") -> ALPHA
86                 r.contains("beta") -> BETA
87                 r.contains("rc") -> RC
88                 else -> FINAL
89             }
90         }
91 
92         /** The text to show for a tag: the same tag without the leading "v". */
93         fun display(tag: String): String {
94             val s = tag.trim()
95             return if (s.isNotEmpty() && (s[0] == 'v' || s[0] == 'V')) s.substring(1) else s
96         }
97     }
98 }