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 (6433 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 
99     /** One watched app: what is installed, and what the check found. */
100     private fun appRow(col: LinearLayout, r: Checker.Row) {
101         val result = r.result
102         val state = when (result) {
103             is Result.Update -> getString(R.string.update_to, result.versionName)
104             is Result.UpToDate -> getString(R.string.up_to_date)
105             is Result.LeftToPlay -> getString(R.string.play_updates)
106             else -> getString(R.string.no_answer)
107         }
108         val page = when (result) {
109             is Result.Update -> result.page
110             is Result.LeftToPlay -> r.app.watched.playPage()
111             else -> null
112         }
113         col.row(
114             title = r.app.watched.title,
115             subtitle = r.app.versionName,
116             state = state,
117             onClick = if (page == null) null else ({ open(page) })
118         )
119     }
120 
121     /** The one line that says when the app last looked. */
122     private fun status(): String = when {
123         checking -> getString(R.string.checking)
124         store.checkedAt == 0L -> getString(R.string.never_checked)
125         else -> {
126             val ago = DateUtils.getRelativeTimeSpanString(
127                 store.checkedAt, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS
128             ).toString()
129             if (store.lastOk) getString(R.string.checked, ago)
130             else getString(R.string.could_not_check, ago)
131         }
132     }
133 
134     // ---- the work ----------------------------------------------------------
135 
136     private fun check() {
137         if (checking) return
138         checking = true
139         draw()
140         Thread {
141             try {
142                 Checker.checkAll(applicationContext, store)
143             } catch (e: Exception) {
144                 store.checked(System.currentTimeMillis(), false)
145             }
146             runOnUiThread {
147                 checking = false
148                 if (!isFinishing && !isDestroyed) draw()
149             }
150         }.start()
151     }
152 
153     private fun setDaily(on: Boolean) {
154         store.daily = on
155         if (on) CheckJob.schedule(this) else CheckJob.cancel(this)
156         draw()
157     }
158 
159     // ---- notifications -----------------------------------------------------
160 
161     private fun notificationsOn(): Boolean =
162         getSystemService(NotificationManager::class.java)?.areNotificationsEnabled() ?: false
163 
164     /** Asks for the notification permission. Android shows the box once. */
165     private fun ask() {
166         if (notificationsOn()) return
167         if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
168             != PackageManager.PERMISSION_GRANTED
169         ) {
170             requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
171         }
172     }
173 
174     override fun onRequestPermissionsResult(
175         code: Int,
176         permissions: Array<out String>,
177         results: IntArray
178     ) {
179         super.onRequestPermissionsResult(code, permissions, results)
180         if (!checking) draw()
181     }
182 
183     // ---- links -------------------------------------------------------------
184 
185     private fun open(url: String) {
186         try {
187             startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
188         } catch (e: Exception) {
189             // No app takes the link. There is nothing to do about it.
190         }
191     }
192 
193     private companion object {
194         const val SOURCE = "https://github.com/equwal/ink-update"
195         const val KOFI = "https://ko-fi.com/truex"
196     }
197 }