Recently Written · git

sbm-android

sbm bookmarks for Android: fuzzy search, share to add, sync with bm and the browser add-on

git clone https://github.com/equwal/sbm-android

Log | Files | Refs


commit fd327955566004c40f9cbe4a5f0956acbb0ed342
equwal <13551856+equwal@users.noreply.github.com>
2026-09-26 14:36:07 -0700

Add a More apps section with links to the other apps

The menu of the list has a More apps item. It opens a screen with the
other sites and apps of the author. A tap on an entry opens its page in
the browser.

The Google Play build (-PplayStore=true) does not show the item. It does
not show the Ko-fi item either.

Add a More projects section to the README.

 README.md                                          | 24 +++++++++
 app/src/main/AndroidManifest.xml                   |  5 ++
 app/src/main/java/com/equwal/sbm/MainActivity.kt   |  6 +++
 app/src/main/java/com/equwal/sbm/MoreApps.kt       | 34 ++++++++++++
 .../main/java/com/equwal/sbm/MoreAppsActivity.kt   | 62 ++++++++++++++++++++++
 app/src/main/res/layout/activity_more_apps.xml     | 21 ++++++++
 app/src/main/res/layout/item_more_app.xml          | 23 ++++++++
 app/src/main/res/menu/main.xml                     |  4 ++
 app/src/main/res/values/strings.xml                | 30 +++++++++++
 app/src/test/java/com/equwal/sbm/MoreAppsTest.kt   | 24 +++++++++
 10 files changed, 233 insertions(+)
diff --git a/README.md b/README.md
index a30bdc5..15963ba 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,30 @@ outside Google Play:
     ./gradlew bundleRelease -PplayStore=true    # Google Play
     ./gradlew assembleRelease                   # F-Droid and GitHub
 
+## More projects
+
+- [SubRead](https://subread.space/): read along with an audiobook, in the browser.
+  Also [for Android](https://github.com/equwal/subread-android/releases/latest),
+  [for YouTube](https://github.com/equwal/subread-extension/releases/latest)
+  and [for KOReader](https://github.com/equwal/subread.koplugin).
+- [SubRead Overlay](https://github.com/equwal/subread-overlay/releases/latest): subtitle lines over any Android media player.
+- [SubRead Dictionary](https://github.com/equwal/subread-dictionary/releases/latest): a pop-up dictionary for Android that reads Yomitan dictionaries.
+- [SubRead Anki](https://github.com/equwal/subread-anki): one tap makes an Anki card from any Android app.
+- [Subrep](https://github.com/equwal/subrep-android/releases/latest): live captions of the sound of your phone.
+- [Book Simulator](https://booksimulator.com/): a reading room for Aozora Bunko and Project Gutenberg books.
+- [honjimaku.com](https://honjimaku.com/): subtitles for Japanese audiobooks.
+- [sbm Sync](https://sbmsync.com/): your bookmarks, the same on every device,
+  with [sbm](https://github.com/equwal/sbm) for dmenu,
+  [sbm for Android](https://github.com/equwal/sbm-android/releases/latest)
+  and the [sbm add-on](https://github.com/equwal/sbm-extension/releases/latest) for Firefox and Chrome.
+- [Rebind](https://github.com/equwal/rebind/releases): remap the hardware buttons of e-ink readers and Android,
+  with [Ink Recents](https://github.com/equwal/ink-recents/releases/latest),
+  [Ink Dim](https://github.com/equwal/ink-dim/releases/latest)
+  and [Ink Update](https://github.com/equwal/ink-update/releases/latest).
+- [dickt.store](https://dickt.store/): language-learning tools, flashcards and web toys.
+- [hentaibun.online](https://hentaibun.online/): learn kanbun and kobun.
+- [Recently Written](https://recentlywritten.com/): the blog, and a list of [all projects](https://recentlywritten.com/projects.html).
+
 ## License
 
 AGPL-3.0. If sbm is useful to you, you can support it on
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ce21cad..5616ee2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -36,5 +36,10 @@
                 <data android:mimeType="text/plain" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name=".MoreAppsActivity"
+            android:exported="false"
+            android:label="@string/more_apps" />
     </application>
 </manifest>
diff --git a/app/src/main/java/com/equwal/sbm/MainActivity.kt b/app/src/main/java/com/equwal/sbm/MainActivity.kt
index 7b76588..909a1c0 100644
--- a/app/src/main/java/com/equwal/sbm/MainActivity.kt
+++ b/app/src/main/java/com/equwal/sbm/MainActivity.kt
@@ -306,6 +306,8 @@ class MainActivity : Activity() {
         menu.findItem(R.id.preview_on).isChecked = previewOn
         // Google Play does not allow links to payments outside Google Play.
         menu.findItem(R.id.donate).isVisible = BuildConfig.DONATE
+        // The Google Play build has no More apps either.
+        menu.findItem(R.id.more_apps).isVisible = BuildConfig.DONATE
         return true
     }
 
@@ -338,6 +340,10 @@ class MainActivity : Activity() {
             open(KOFI)
             true
         }
+        R.id.more_apps -> {
+            startActivity(Intent(this, MoreAppsActivity::class.java))
+            true
+        }
         R.id.open_file -> {
             val pick = Intent(Intent.ACTION_OPEN_DOCUMENT).addCategory(Intent.CATEGORY_OPENABLE).setType("*/*")
             startActivityForResult(pick, CHOOSE)
diff --git a/app/src/main/java/com/equwal/sbm/MoreApps.kt b/app/src/main/java/com/equwal/sbm/MoreApps.kt
new file mode 100644
index 0000000..b656a9b
--- /dev/null
+++ b/app/src/main/java/com/equwal/sbm/MoreApps.kt
@@ -0,0 +1,34 @@
+package com.equwal.sbm
+
+/**
+ * One link of the "More apps" section: a name and one line about it, as string resources,
+ * and the page that a tap opens.
+ */
+class MoreApp(val name: Int, val line: Int, val url: String)
+
+/**
+ * The other sites and apps of the same author, in the order of the catalog: the sites,
+ * then the Android apps, then the list of all projects. sbm for Android is not in it.
+ */
+val MORE_APPS = listOf(
+    MoreApp(R.string.more_subread, R.string.more_subread_line, "https://subread.space/"),
+    MoreApp(R.string.more_book_simulator, R.string.more_book_simulator_line, "https://booksimulator.com/"),
+    MoreApp(R.string.more_honjimaku, R.string.more_honjimaku_line, "https://honjimaku.com/"),
+    MoreApp(R.string.more_sbm_sync, R.string.more_sbm_sync_line, "https://sbmsync.com/"),
+    MoreApp(R.string.more_subread_android, R.string.more_subread_android_line,
+        "https://github.com/equwal/subread-android/releases/latest"),
+    MoreApp(R.string.more_subread_overlay, R.string.more_subread_overlay_line,
+        "https://github.com/equwal/subread-overlay/releases/latest"),
+    MoreApp(R.string.more_subread_dictionary, R.string.more_subread_dictionary_line,
+        "https://github.com/equwal/subread-dictionary/releases/latest"),
+    MoreApp(R.string.more_subread_anki, R.string.more_subread_anki_line, "https://github.com/equwal/subread-anki"),
+    MoreApp(R.string.more_subrep, R.string.more_subrep_line,
+        "https://github.com/equwal/subrep-android/releases/latest"),
+    MoreApp(R.string.more_rebind, R.string.more_rebind_line, "https://github.com/equwal/rebind/releases"),
+    MoreApp(R.string.more_ink_recents, R.string.more_ink_recents_line,
+        "https://github.com/equwal/ink-recents/releases/latest"),
+    MoreApp(R.string.more_ink_dim, R.string.more_ink_dim_line, "https://github.com/equwal/ink-dim/releases/latest"),
+    MoreApp(R.string.more_ink_update, R.string.more_ink_update_line,
+        "https://github.com/equwal/ink-update/releases/latest"),
+    MoreApp(R.string.more_all_projects, R.string.more_all_projects_line, "https://recentlywritten.com/projects.html"),
+)
diff --git a/app/src/main/java/com/equwal/sbm/MoreAppsActivity.kt b/app/src/main/java/com/equwal/sbm/MoreAppsActivity.kt
new file mode 100644
index 0000000..2b2d67f
--- /dev/null
+++ b/app/src/main/java/com/equwal/sbm/MoreAppsActivity.kt
@@ -0,0 +1,62 @@
+package com.equwal.sbm
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.widget.BaseAdapter
+import android.widget.ListView
+import android.widget.TextView
+import android.widget.Toast
+import android.widget.Toolbar
+
+/** The other sites and apps of the same author, from the menu of the list. A tap opens the page in a browser. */
+class MoreAppsActivity : Activity() {
+    override fun onCreate(state: Bundle?) {
+        super.onCreate(state)
+        setContentView(R.layout.activity_more_apps)
+        val toolbar = findViewById<Toolbar>(R.id.toolbar)
+        setActionBar(toolbar)
+
+        // The same as the list screen: the toolbar colour goes under the
+        // status bar, and the rest of the screen stays clear of the bars.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            findViewById<View>(R.id.root).setOnApplyWindowInsetsListener { v, insets ->
+                val bars = insets.getInsets(WindowInsets.Type.systemBars())
+                toolbar.setPadding(0, bars.top, 0, 0)
+                v.setPadding(bars.left, 0, bars.right, bars.bottom)
+                insets
+            }
+        }
+
+        val list = findViewById<ListView>(R.id.list)
+        list.adapter = Rows()
+        list.setOnItemClickListener { _, _, position, _ -> open(MORE_APPS[position].url) }
+    }
+
+    private fun open(url: String) {
+        try {
+            startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).addCategory(Intent.CATEGORY_BROWSABLE))
+        } catch (e: ActivityNotFoundException) {
+            Toast.makeText(this, getString(R.string.no_app, url), Toast.LENGTH_LONG).show()
+        }
+    }
+
+    private inner class Rows : BaseAdapter() {
+        override fun getCount() = MORE_APPS.size
+        override fun getItem(position: Int) = MORE_APPS[position]
+        override fun getItemId(position: Int) = position.toLong()
+
+        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+            val view = convertView ?: layoutInflater.inflate(R.layout.item_more_app, parent, false)
+            view.findViewById<TextView>(R.id.title).setText(MORE_APPS[position].name)
+            view.findViewById<TextView>(R.id.detail).setText(MORE_APPS[position].line)
+            return view
+        }
+    }
+}
diff --git a/app/src/main/res/layout/activity_more_apps.xml b/app/src/main/res/layout/activity_more_apps.xml
new file mode 100644
index 0000000..39639fb
--- /dev/null
+++ b/app/src/main/res/layout/activity_more_apps.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/colorPrimary"
+        android:minHeight="?android:attr/actionBarSize"
+        android:theme="@android:style/ThemeOverlay.Material.Dark.ActionBar"
+        android:title="@string/more_apps" />
+
+    <ListView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/item_more_app.xml b/app/src/main/res/layout/item_more_app.xml
new file mode 100644
index 0000000..030a7fa
--- /dev/null
+++ b/app/src/main/res/layout/item_more_app.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- One entry of More apps. It is the same as a row of the list, but the
+     line under the name is not cut. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingHorizontal="16dp"
+    android:paddingVertical="10dp">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <TextView
+        android:id="@+id/detail"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary" />
+</LinearLayout>
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index e8e11cc..fce940b 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -34,4 +34,8 @@
         android:id="@+id/donate"
         android:showAsAction="never"
         android:title="@string/donate" />
+    <item
+        android:id="@+id/more_apps"
+        android:showAsAction="never"
+        android:title="@string/more_apps" />
 </menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6db0a41..d856486 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -53,4 +53,34 @@
     <string name="bad_server">%1$s is not a server address.</string>
     <string name="no_version">the server gave no version.</string>
     <string name="file_busy">the file changes all the time. Try again later.</string>
+
+    <string name="more_apps">More apps</string>
+    <string name="more_subread">SubRead</string>
+    <string name="more_subread_line">Read along with an audiobook.</string>
+    <string name="more_book_simulator">Book Simulator</string>
+    <string name="more_book_simulator_line">A reading room for Aozora Bunko and Project Gutenberg books.</string>
+    <string name="more_honjimaku">honjimaku.com</string>
+    <string name="more_honjimaku_line">Subtitles for Japanese audiobooks.</string>
+    <string name="more_sbm_sync">sbm Sync</string>
+    <string name="more_sbm_sync_line">Your bookmarks, the same on every device.</string>
+    <string name="more_subread_android">SubRead for Android</string>
+    <string name="more_subread_android_line">Times an audiobook against its ebook on the device.</string>
+    <string name="more_subread_overlay">SubRead Overlay</string>
+    <string name="more_subread_overlay_line">Subtitle lines over any Android media player.</string>
+    <string name="more_subread_dictionary">SubRead Dictionary</string>
+    <string name="more_subread_dictionary_line">Pop-up dictionary that reads Yomitan dictionaries.</string>
+    <string name="more_subread_anki">SubRead Anki</string>
+    <string name="more_subread_anki_line">One tap makes an Anki card from any app.</string>
+    <string name="more_subrep">Subrep</string>
+    <string name="more_subrep_line">Live captions of the sound of your phone.</string>
+    <string name="more_rebind">Rebind</string>
+    <string name="more_rebind_line">Remap the hardware buttons of e-ink readers and Android.</string>
+    <string name="more_ink_recents">Ink Recents</string>
+    <string name="more_ink_recents_line">A recent-apps switcher for e-ink.</string>
+    <string name="more_ink_dim">Ink Dim</string>
+    <string name="more_ink_dim_line">Frontlight below the lowest system level.</string>
+    <string name="more_ink_update">Ink Update</string>
+    <string name="more_ink_update_line">Tells you when Rebind and its extensions update.</string>
+    <string name="more_all_projects">All projects</string>
+    <string name="more_all_projects_line">Everything, with source code.</string>
 </resources>
diff --git a/app/src/test/java/com/equwal/sbm/MoreAppsTest.kt b/app/src/test/java/com/equwal/sbm/MoreAppsTest.kt
new file mode 100644
index 0000000..9f285fe
--- /dev/null
+++ b/app/src/test/java/com/equwal/sbm/MoreAppsTest.kt
@@ -0,0 +1,24 @@
+package com.equwal.sbm
+
+import net.jqwik.api.Example
+
+class MoreAppsTest {
+    @Example
+    fun `the list leaves out this app`() {
+        check(MORE_APPS.none { "equwal/sbm-android" in it.url })
+    }
+
+    @Example
+    fun `each link opens an https page`() {
+        for (app in MORE_APPS) check(app.url.startsWith("https://")) { app.url }
+    }
+
+    @Example
+    fun `the list keeps the order of the catalog`() {
+        val sites = listOf("https://subread.space/", "https://booksimulator.com/", "https://honjimaku.com/", "https://sbmsync.com/")
+        check(MORE_APPS.take(4).map { it.url } == sites)
+        check(MORE_APPS.last().url == "https://recentlywritten.com/projects.html")
+        // The catalog has 15 entries. This app is the one that is not in the list.
+        check(MORE_APPS.size == 14)
+    }
+}