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 }