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/MainActivity.kt (6626 bytes)

1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // Copyright (c) 2026 equwal
3 package dev.equwal.inkupdate
4 
5 import android.Manifest
6 import android.app.Activity
7 import android.app.NotificationManager
8 import android.content.Intent
9 import android.content.pm.PackageManager
10 import android.net.Uri
11 import android.os.Bundle
12 import android.text.format.DateUtils
13 import android.widget.LinearLayout
14 import dev.equwal.inkupdate.Ui.button
15 import dev.equwal.inkupdate.Ui.header
16 import dev.equwal.inkupdate.Ui.note
17 import dev.equwal.inkupdate.Ui.page
18 import dev.equwal.inkupdate.Ui.primaryButton
19 import dev.equwal.inkupdate.Ui.row
20 import dev.equwal.inkupdate.Ui.title
21 
22 /**
23  * The one screen.
24  *
25  * It shows each installed watched app, what is installed, and what the last
26  * check found. The check runs on a background thread, because the main thread
27  * must not wait for the network.
28  */
29 class MainActivity : Activity() {
30 
31     /** Another app asks for a check with this action. Rebind uses it. */
32     private val checkAction = "dev.equwal.inkupdate.CHECK"
33 
34     private lateinit var store: Store
35     private var checking = false
36 
37     override fun onCreate(saved: Bundle?) {
38         super.onCreate(saved)
39         store = Store(this)
40 
41         val first = !store.started
42         if (first) {
43             store.started = true
44             if (store.daily) CheckJob.schedule(this)
45             ask()
46         }
47         draw()
48         if (first || intent?.action == checkAction) check()
49     }
50 
51     override fun onNewIntent(i: Intent?) {
52         super.onNewIntent(i)
53         intent = i
54         if (i?.action == checkAction) check()
55     }
56 
57     override fun onResume() {
58         super.onResume()
59         if (!checking) draw()
60     }
61 
62     // ---- the screen --------------------------------------------------------
63 
64     private fun draw() {
65         val col = page(this)
66         col.title(getString(R.string.app_name))
67         col.note(status())
68 
69         val rows = Checker.rows(this, store)
70         if (rows.isEmpty()) {
71             col.note(getString(R.string.none_installed))
72         } else {
73             for (r in rows) appRow(col, r)
74         }
75 
76         col.primaryButton(
77             if (checking) getString(R.string.checking) else getString(R.string.check_now)
78         ) { check() }
79 
80         col.row(
81             title = getString(R.string.daily),
82             state = if (store.daily) getString(R.string.on) else getString(R.string.off)
83         ) { setDaily(!store.daily) }
84 
85         if (!notificationsOn()) {
86             col.row(
87                 title = getString(R.string.notifications),
88                 state = getString(R.string.off)
89             ) { ask() }
90         }
91 
92         col.header(getString(R.string.about))
93         col.row(title = getString(R.string.version), subtitle = BuildConfig.VERSION_NAME)
94         col.row(title = getString(R.string.licence), subtitle = "GPL-3.0-or-later")
95         col.row(title = getString(R.string.source_code)) { open(SOURCE) }
96         col.row(title = getString(R.string.coffee)) { open(KOFI) }
97 
98         col.header(getString(R.string.more_apps))
99         for (app in MORE_APPS) {
100             col.row(title = getString(app.name), subtitle = getString(app.line)) { open(app.url) }
101         }
102     }
103 
104     /** One watched app: what is installed, and what the check found. */
105     private fun appRow(col: LinearLayout, r: Checker.Row) {
106         val result = r.result
107         val state = when (result) {
108             is Result.Update -> getString(R.string.update_to, result.versionName)
109             is Result.UpToDate -> getString(R.string.up_to_date)
110             is Result.LeftToPlay -> getString(R.string.play_updates)
111             else -> getString(R.string.no_answer)
112         }
113         val page = when (result) {
114             is Result.Update -> result.page
115             is Result.LeftToPlay -> r.app.watched.playPage()
116             else -> null
117         }
118         col.row(
119             title = r.app.watched.title,
120             subtitle = r.app.versionName,
121             state = state,
122             onClick = if (page == null) null else ({ open(page) })
123         )
124     }
125 
126     /** The one line that says when the app last looked. */
127     private fun status(): String = when {
128         checking -> getString(R.string.checking)
129         store.checkedAt == 0L -> getString(R.string.never_checked)
130         else -> {
131             val ago = DateUtils.getRelativeTimeSpanString(
132                 store.checkedAt, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS
133             ).toString()
134             if (store.lastOk) getString(R.string.checked, ago)
135             else getString(R.string.could_not_check, ago)
136         }
137     }
138 
139     // ---- the work ----------------------------------------------------------
140 
141     private fun check() {
142         if (checking) return
143         checking = true
144         draw()
145         Thread {
146             try {
147                 Checker.checkAll(applicationContext, store)
148             } catch (e: Exception) {
149                 store.checked(System.currentTimeMillis(), false)
150             }
151             runOnUiThread {
152                 checking = false
153                 if (!isFinishing && !isDestroyed) draw()
154             }
155         }.start()
156     }
157 
158     private fun setDaily(on: Boolean) {
159         store.daily = on
160         if (on) CheckJob.schedule(this) else CheckJob.cancel(this)
161         draw()
162     }
163 
164     // ---- notifications -----------------------------------------------------
165 
166     private fun notificationsOn(): Boolean =
167         getSystemService(NotificationManager::class.java)?.areNotificationsEnabled() ?: false
168 
169     /** Asks for the notification permission. Android shows the box once. */
170     private fun ask() {
171         if (notificationsOn()) return
172         if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
173             != PackageManager.PERMISSION_GRANTED
174         ) {
175             requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
176         }
177     }
178 
179     override fun onRequestPermissionsResult(
180         code: Int,
181         permissions: Array<out String>,
182         results: IntArray
183     ) {
184         super.onRequestPermissionsResult(code, permissions, results)
185         if (!checking) draw()
186     }
187 
188     // ---- links -------------------------------------------------------------
189 
190     private fun open(url: String) {
191         try {
192             startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
193         } catch (e: Exception) {
194             // No app takes the link. There is nothing to do about it.
195         }
196     }
197 
198     private companion object {
199         const val SOURCE = "https://github.com/equwal/ink-update"
200         const val KOFI = "https://ko-fi.com/truex"
201     }
202 }