MiniABS 1.5: Audiobookshelf icon and purple theme

- Launcher icon from the Audiobookshelf icon pack (art/icon_master_512.png),
  including an xhdpi size
- Notification icon cut out of the same artwork; player icons redrawn in the
  icon's lilac (tools/make_icons.py)
- Purple colour scheme taken from the icon: gradient accent buttons, lilac
  text accents (accent_light), purple-tinted backgrounds
- README: where the icon and colours come from

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xockoandClaude Opus 5.5 committed 2026-10-15 10:07:49 +02:00
1 parent 0fea07f1b2
commit 2c23ec9131
36 files changed
+70 -65

No files matched your search

+2 -2
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.miniabs" package="com.miniabs"
android:versionCode="5" android:versionCode="6"
android:versionName="1.4" android:versionName="1.5"
android:installLocation="auto"> android:installLocation="auto">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" /> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
+1 -1
View File
@@ -1,6 +1,6 @@
# MiniABS # MiniABS
A tiny Audiobookshelf **podcast** client for the Samsung Galaxy Mini (GT-S5570) and other Android 2.2+ phones. Native Java, no libraries, ~130 KB. The interface is in Galician, with large text and touch targets sized for the Mini's 240×320 screen. A tiny Audiobookshelf **podcast** client for the Samsung Galaxy Mini (GT-S5570) and other Android 2.2+ phones. Native Java, no libraries, ~130 KB. The interface is in Galician, with large text and touch targets sized for the Mini's 240×320 screen. Its purple colour scheme and launcher icon come from the Audiobookshelf icon pack (`art/icon_master_512.png`); `tools/make_icons.py` derives the notification and player icons from it.
## Install ## Install
Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 B

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 B

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 B

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 467 B

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 B

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 B

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+2 -2
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"> <item android:state_pressed="true">
<shape><solid android:color="#FFC766" /><corners android:radius="6dp" /></shape> <shape><gradient android:startColor="#AA5CFF" android:endColor="#7022E6" android:angle="270" /><corners android:radius="6dp" /></shape>
</item> </item>
<item android:state_enabled="false"> <item android:state_enabled="false">
<shape><solid android:color="@color/dim" /><corners android:radius="6dp" /></shape> <shape><solid android:color="@color/dim" /><corners android:radius="6dp" /></shape>
</item> </item>
<item> <item>
<shape><solid android:color="@color/accent" /><corners android:radius="6dp" /></shape> <shape><gradient android:startColor="@color/accent_top" android:endColor="@color/accent_bottom" android:angle="270" /><corners android:radius="6dp" /></shape>
</item> </item>
</selector> </selector>
+1 -1
View File
@@ -4,7 +4,7 @@
<shape> <shape>
<solid android:color="@color/pressed" /> <solid android:color="@color/pressed" />
<corners android:radius="6dp" /> <corners android:radius="6dp" />
<stroke android:width="1dp" android:color="@color/accent" /> <stroke android:width="1dp" android:color="@color/accent_light" />
</shape> </shape>
</item> </item>
<item android:state_enabled="false"> <item android:state_enabled="false">
+2 -2
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"> <item android:state_pressed="true">
<shape><solid android:color="#FFC766" /><corners android:radius="23dp" /></shape> <shape><gradient android:startColor="#AA5CFF" android:endColor="#7022E6" android:angle="270" /><corners android:radius="23dp" /></shape>
</item> </item>
<item> <item>
<shape><solid android:color="@color/accent" /><corners android:radius="23dp" /></shape> <shape><gradient android:startColor="@color/accent_top" android:endColor="@color/accent_bottom" android:angle="270" /><corners android:radius="23dp" /></shape>
</item> </item>
</selector> </selector>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/accent" /> <solid android:color="@color/accent_light" />
<stroke android:width="2dp" android:color="@color/bg" /> <stroke android:width="2dp" android:color="@color/bg" />
<size android:width="@dimen/seek_thumb" android:height="@dimen/seek_thumb" /> <size android:width="@dimen/seek_thumb" android:height="@dimen/seek_thumb" />
</shape> </shape>
+1 -1
View File
@@ -12,7 +12,7 @@
android:text="MiniABS" android:text="MiniABS"
android:textSize="@dimen/header_text" android:textSize="@dimen/header_text"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/accent" /> android:textColor="@color/accent_light" />
<TextView <TextView
android:id="@+id/library" android:id="@+id/library"
+3 -3
View File
@@ -34,7 +34,7 @@
android:text="MiniABS" android:text="MiniABS"
android:textSize="@dimen/login_title" android:textSize="@dimen/login_title"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/accent" /> android:textColor="@color/accent_light" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -82,7 +82,7 @@
android:text="Conectar" android:text="Conectar"
android:textStyle="bold" android:textStyle="bold"
android:textSize="@dimen/login_btn_text" android:textSize="@dimen/login_btn_text"
android:textColor="#121519" /> android:textColor="#FFFFFF" />
<TextView <TextView
android:id="@+id/status" android:id="@+id/status"
@@ -90,7 +90,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:textSize="@dimen/login_status" android:textSize="@dimen/login_status"
android:textColor="@color/accent" /> android:textColor="@color/accent_light" />
<TextView <TextView
android:layout_width="fill_parent" android:layout_width="fill_parent"
+1 -1
View File
@@ -14,7 +14,7 @@
android:paddingRight="10dp" android:paddingRight="10dp"
android:textSize="@dimen/header_sub" android:textSize="@dimen/header_sub"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/accent" android:textColor="@color/accent_light"
android:singleLine="true" android:singleLine="true"
android:ellipsize="end" android:ellipsize="end"
android:background="@drawable/row_sel" /> android:background="@drawable/row_sel" />
+1 -1
View File
@@ -27,7 +27,7 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/text_show" android:textSize="@dimen/text_show"
android:textColor="@color/accent" android:textColor="@color/accent_light"
android:singleLine="true" android:singleLine="true"
android:ellipsize="end" /> android:ellipsize="end" />
+1 -1
View File
@@ -6,4 +6,4 @@
android:gravity="center" android:gravity="center"
android:textSize="@dimen/text_more" android:textSize="@dimen/text_more"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/accent" /> android:textColor="@color/accent_light" />
+13 -9
View File
@@ -2,16 +2,20 @@
<resources> <resources>
<string name="app_name">MiniABS</string> <string name="app_name">MiniABS</string>
<color name="bg">#121519</color> <!-- Colours taken from the Audiobookshelf icon: gradient #9437FC to #570DCF, waves #BC8AFC -->
<color name="surface">#1C2127</color> <color name="bg">#13111A</color>
<color name="header">#0B0D10</color> <color name="surface">#1D1928</color>
<color name="line">#2A3038</color> <color name="header">#0C0A12</color>
<color name="text">#ECEFF2</color> <color name="line">#2C2638</color>
<color name="sub">#A9B2BC</color> <color name="text">#EEEBF3</color>
<color name="dim">#6E7882</color> <color name="sub">#ADA6BC</color>
<color name="accent">#F5A623</color> <color name="dim">#736C82</color>
<color name="accent">#7A25EC</color> <!-- fills: tabs, progress, seek bar -->
<color name="accent_light">#BC8AFC</color> <!-- text and small marks on dark backgrounds -->
<color name="accent_top">#9437FC</color> <!-- button gradient, as in the icon -->
<color name="accent_bottom">#5A11D3</color>
<color name="fresh">#6EC1E4</color> <color name="fresh">#6EC1E4</color>
<color name="pressed">#2C333C</color> <color name="pressed">#2E2642</color>
<style name="MiniTheme" parent="@android:style/Theme.NoTitleBar"> <style name="MiniTheme" parent="@android:style/Theme.NoTitleBar">
<item name="android:windowBackground">@color/bg</item> <item name="android:windowBackground">@color/bg</item>
+1 -1
View File
@@ -50,7 +50,7 @@
<PreferenceCategory android:title="Acerca de"> <PreferenceCategory android:title="Acerca de">
<Preference <Preference
android:key="about" android:key="about"
android:title="MiniABS 1.4" android:title="MiniABS 1.5"
android:summary="Un pequeno cliente de podcasts de Audiobookshelf para Android 2.2+" /> android:summary="Un pequeno cliente de podcasts de Audiobookshelf para Android 2.2+" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
+1 -1
View File
@@ -26,7 +26,7 @@ import android.os.Build;
/** Audiobookshelf REST client (API as of server 2.x, including the 2.26+ token system). */ /** Audiobookshelf REST client (API as of server 2.x, including the 2.26+ token system). */
final class Api { final class Api {
static final String VERSION = "1.4"; static final String VERSION = "1.5";
static class ApiException extends IOException { static class ApiException extends IOException {
final int code; final int code;
+1 -1
View File
@@ -201,7 +201,7 @@ final class Rows extends BaseAdapter {
} else { } else {
bar.setVisibility(View.GONE); bar.setVisibility(View.GONE);
} }
int color = act.getResources().getColor(finished ? R.color.line : started ? R.color.accent : R.color.fresh); int color = act.getResources().getColor(finished ? R.color.line : started ? R.color.accent_light : R.color.fresh);
dotView.setBackgroundDrawable(dot(color)); dotView.setBackgroundDrawable(dot(color));
} }
} }
+38 -37
View File
@@ -1,6 +1,11 @@
"""Draws MiniABS's PNG icons for ldpi (Galaxy Mini), mdpi and hdpi. """Draws MiniABS's PNG icons for ldpi (Galaxy Mini), mdpi and hdpi.
Android 2.x has no vector drawables, so every icon is rendered here at 4x and The launcher icon (res/drawable-*/ic_launcher.png) comes from the supplied
Audiobookshelf icon pack and is not generated here. The notification icon is
cut out of that same artwork (art/icon_master_512.png): the white headphones
and equalizer become a white silhouette, as Android 2.3's status bar expects.
Android 2.x has no vector drawables, so the player icons are rendered at 4x and
downsampled for smooth edges. downsampled for smooth edges.
""" """
import os import os
@@ -8,9 +13,9 @@ import sys
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
RES = sys.argv[1] RES = sys.argv[1]
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
DENS = {"ldpi": 0.75, "mdpi": 1.0, "hdpi": 1.5} DENS = {"ldpi": 0.75, "mdpi": 1.0, "hdpi": 1.5}
AMBER = (245, 166, 35, 255) LILAC = (188, 138, 252, 255) # the icon's sound-wave colour (#BC8AFC)
DARK = (18, 21, 25, 255)
WHITE = (255, 255, 255, 255) WHITE = (255, 255, 255, 255)
SS = 4 # supersampling SS = 4 # supersampling
@@ -27,36 +32,32 @@ def save(img, px, folder, name):
out.save(os.path.join(d, name + ".png")) out.save(os.path.join(d, name + ".png"))
def broadcast(d, s, cx, cy, scale, color, bg): def status_silhouette():
"""Podcast mark: a dot with two arcs on each side.""" """White mask of the icon's light parts (headphones, bars, waves) on transparency."""
r = s * 0.075 * scale src = Image.open(os.path.join(ROOT, "art", "icon_master_512.png")).convert("RGBA")
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=color) out = Image.new("RGBA", src.size, (0, 0, 0, 0))
w = max(1, int(s * 0.07 * scale)) sp, op = src.load(), out.load()
for k in (0.22, 0.37): for y in range(src.size[1]):
rr = s * k * scale for x in range(src.size[0]):
box = [cx - rr, cy - rr, cx + rr, cy + rr] r, g, b, a = sp[x, y]
d.arc(box, 140, 220, fill=color, width=w) # the purple background has a low green channel; the white/lilac artwork a high one
d.arc(box, -40, 40, fill=color, width=w) level = min(255, max(0, (g - 80) * 255 // 50))
op[x, y] = (255, 255, 255, level * a // 255)
box = out.getbbox()
out = out.crop(box)
side = max(out.size)
sq = Image.new("RGBA", (side, side), (0, 0, 0, 0))
sq.paste(out, ((side - out.size[0]) // 2, (side - out.size[1]) // 2))
return sq
def launcher(px, folder): def status(sil, px, folder):
img, s = canvas(px) pad = max(1, round(px * 0.06))
d = ImageDraw.Draw(img) inner = sil.resize((px - 2 * pad, px - 2 * pad), Image.LANCZOS)
m = s * 0.06 img = Image.new("RGBA", (px, px), (0, 0, 0, 0))
d.rounded_rectangle([m, m, s - m, s - m], radius=s * 0.2, fill=DARK, outline=AMBER, width=int(s * 0.04)) img.paste(inner, (pad, pad))
broadcast(d, s, s / 2, s * 0.44, 1.0, AMBER, DARK) d = os.path.join(RES, "drawable-" + folder)
# little stand under the mark img.save(os.path.join(d, "ic_stat.png"))
w = s * 0.05
d.rectangle([s / 2 - w / 2, s * 0.5, s / 2 + w / 2, s * 0.74], fill=AMBER)
d.rounded_rectangle([s * 0.36, s * 0.72, s * 0.64, s * 0.78], radius=w / 2, fill=AMBER)
save(img, px, folder, "ic_launcher")
def status(px, folder):
img, s = canvas(px)
d = ImageDraw.Draw(img)
broadcast(d, s, s / 2, s / 2, 1.25, WHITE, None)
save(img, px, folder, "ic_stat")
def play(px, folder, name, color): def play(px, folder, name, color):
@@ -85,12 +86,12 @@ def refresh(px, folder):
save(img, px, folder, "ic_refresh") save(img, px, folder, "ic_refresh")
sil = status_silhouette()
for folder, k in DENS.items(): for folder, k in DENS.items():
launcher(round(48 * k), folder) status(sil, round(25 * k), folder)
status(round(25 * k), folder) play(round(32 * k), folder, "ic_play", WHITE) # on the purple play button
play(round(32 * k), folder, "ic_play", DARK) pause(round(32 * k), folder, "ic_pause", WHITE)
pause(round(32 * k), folder, "ic_pause", DARK) play(round(38 * k), folder, "ic_play_small", LILAC) # mini player bar, on dark
play(round(38 * k), folder, "ic_play_small", AMBER) pause(round(38 * k), folder, "ic_pause_small", LILAC)
pause(round(38 * k), folder, "ic_pause_small", AMBER)
refresh(round(30 * k), folder) refresh(round(30 * k), folder)
print("icons ok") print("icons ok")