mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-20 12:40:15 +02:00
replace android default player icons with astra icons
This commit is contained in:
+9
-1
@@ -15,6 +15,14 @@ import com.facebook.react.HeadlessJsTaskService
|
|||||||
import com.facebook.react.bridge.Arguments
|
import com.facebook.react.bridge.Arguments
|
||||||
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
||||||
|
|
||||||
|
private const val NOTIFICATION_ICON_RESOURCE = "astra_notification_icon"
|
||||||
|
|
||||||
|
private fun notificationSmallIcon(context: Context): Int =
|
||||||
|
context.resources
|
||||||
|
.getIdentifier(NOTIFICATION_ICON_RESOURCE, "drawable", context.packageName)
|
||||||
|
.takeIf { it != 0 }
|
||||||
|
?: android.R.drawable.ic_media_play
|
||||||
|
|
||||||
class AstraCarCommandService : HeadlessJsTaskService() {
|
class AstraCarCommandService : HeadlessJsTaskService() {
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
// We're started via startForegroundService (so transport from the car works even when
|
// We're started via startForegroundService (so transport from the car works even when
|
||||||
@@ -72,7 +80,7 @@ class AstraCarCommandService : HeadlessJsTaskService() {
|
|||||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
.setContentTitle("Astra")
|
.setContentTitle("Astra")
|
||||||
.setContentText("Handling car controls")
|
.setContentText("Handling car controls")
|
||||||
.setSmallIcon(android.R.drawable.ic_media_play)
|
.setSmallIcon(notificationSmallIcon(this))
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
+8
-1
@@ -35,6 +35,13 @@ private const val ACTION_NEXT = ACTION_PREFIX + "NEXT"
|
|||||||
private const val ACTION_TOGGLE_FAVORITE = ACTION_PREFIX + "TOGGLE_FAVORITE"
|
private const val ACTION_TOGGLE_FAVORITE = ACTION_PREFIX + "TOGGLE_FAVORITE"
|
||||||
private const val ACTION_STOP = ACTION_PREFIX + "STOP"
|
private const val ACTION_STOP = ACTION_PREFIX + "STOP"
|
||||||
private const val MAX_ART_EDGE = 512
|
private const val MAX_ART_EDGE = 512
|
||||||
|
private const val NOTIFICATION_ICON_RESOURCE = "astra_notification_icon"
|
||||||
|
|
||||||
|
private fun notificationSmallIcon(context: Context): Int =
|
||||||
|
context.resources
|
||||||
|
.getIdentifier(NOTIFICATION_ICON_RESOURCE, "drawable", context.packageName)
|
||||||
|
.takeIf { it != 0 }
|
||||||
|
?: android.R.drawable.ic_media_play
|
||||||
|
|
||||||
class AstraDesktopRemoteSessionState : Record {
|
class AstraDesktopRemoteSessionState : Record {
|
||||||
@Field
|
@Field
|
||||||
@@ -240,7 +247,7 @@ object AstraDesktopRemoteSessionController {
|
|||||||
}
|
}
|
||||||
val largeIcon = decodeDataUrlBitmap(state.artworkDataUrl)
|
val largeIcon = decodeDataUrlBitmap(state.artworkDataUrl)
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.ic_media_play)
|
.setSmallIcon(notificationSmallIcon(context))
|
||||||
.setContentTitle(state.title?.ifBlank { "Unknown track" } ?: "Unknown track")
|
.setContentTitle(state.title?.ifBlank { "Unknown track" } ?: "Unknown track")
|
||||||
.setContentText(state.artist?.ifBlank { state.desktopName } ?: state.desktopName ?: "Astra Desktop")
|
.setContentText(state.artist?.ifBlank { state.desktopName } ?: state.desktopName ?: "Astra Desktop")
|
||||||
.setSubText(state.desktopName ?: "Desktop Remote")
|
.setSubText(state.desktopName ?: "Desktop Remote")
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
const fs = require('fs/promises');
|
||||||
|
const path = require('path');
|
||||||
const {
|
const {
|
||||||
AndroidConfig,
|
AndroidConfig,
|
||||||
|
withDangerousMod,
|
||||||
withAndroidManifest,
|
withAndroidManifest,
|
||||||
withAppBuildGradle,
|
withAppBuildGradle,
|
||||||
withProjectBuildGradle,
|
withProjectBuildGradle,
|
||||||
@@ -10,6 +13,41 @@ const SETTINGS_MARKER = '// ASTRA VENDORED KOTLIN AUDIO';
|
|||||||
const PROJECT_BUILD_MARKER = '// ASTRA KOTLIN AUDIO SUBSTITUTION';
|
const PROJECT_BUILD_MARKER = '// ASTRA KOTLIN AUDIO SUBSTITUTION';
|
||||||
const SIGNING_MARKER = '// ASTRA RELEASE SIGNING';
|
const SIGNING_MARKER = '// ASTRA RELEASE SIGNING';
|
||||||
const CAMERA_FEATURE = 'android.hardware.camera';
|
const CAMERA_FEATURE = 'android.hardware.camera';
|
||||||
|
const NOTIFICATION_ICON_FILE = 'astra_notification_icon.xml';
|
||||||
|
const NOTIFICATION_OVERRIDE_FILE = 'astra_notification_icon_overrides.xml';
|
||||||
|
|
||||||
|
// A notification small icon is an alpha mask: Android supplies the color for
|
||||||
|
// the status bar, lock screen and media controls. Keep only the main Astra mark
|
||||||
|
// here—launcher backgrounds, shadows and brand colors do not belong in it.
|
||||||
|
const NOTIFICATION_ICON_VECTOR = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="1024"
|
||||||
|
android:viewportHeight="1024">
|
||||||
|
<group
|
||||||
|
android:scaleX="1.726813"
|
||||||
|
android:scaleY="1.726813"
|
||||||
|
android:translateX="-660.505902"
|
||||||
|
android:translateY="-373.7">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M526.083,500.65C529.86,496.662 535.112,494.402 540.605,494.402C553.071,494.402 576.056,494.402 588.831,494.402C594.652,494.402 600.185,496.939 603.984,501.35C610.054,508.396 619.61,519.49 627.207,528.31C633.905,536.085 633.631,547.668 626.573,555.117C603.295,579.689 553.937,631.788 536.916,649.755C533.139,653.742 527.889,656 522.397,656L452,656C440.954,656 432,647.046 432,636C432,626.32 432,615.247 432,607.967C432,602.851 433.96,597.93 437.478,594.215C454.783,575.942 508.184,519.551 526.083,500.65Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M580,389.237C580,378.578 588.641,369.937 599.3,369.937C625.097,369.937 669.782,369.937 688.899,369.937C694.682,369.937 700.183,372.436 703.987,376.792C736.676,414.222 893.163,593.401 921.571,625.929C924.427,629.198 926,633.392 926,637.733C926,637.733 926,637.734 926,637.734C926,648.379 917.371,657.008 906.726,657.008L817.1,657.008C811.318,657.008 805.817,654.51 802.013,650.155C769.332,612.742 612.909,433.673 584.448,401.092C581.58,397.809 580,393.598 580,389.239C580,389.238 580,389.237 580,389.237Z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// RNTP 4.1.2 uses ExoPlayer's generic circular-play drawable whenever no JS
|
||||||
|
// icon resolves. An app-level resource with the same name overrides that
|
||||||
|
// default in release, debug and the temporary foreground-service notification.
|
||||||
|
const NOTIFICATION_ICON_OVERRIDE = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<item name="exo_notification_small_icon" type="drawable">@drawable/astra_notification_icon</item>
|
||||||
|
</resources>
|
||||||
|
`;
|
||||||
|
|
||||||
const SETTINGS_BLOCK = `
|
const SETTINGS_BLOCK = `
|
||||||
|
|
||||||
@@ -158,9 +196,35 @@ function ensureOptionalCameraFeature(androidManifest) {
|
|||||||
manifest['uses-feature'] = features;
|
manifest['uses-feature'] = features;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeNotificationIconResources(platformProjectRoot) {
|
||||||
|
const resourceRoot = path.join(platformProjectRoot, 'app', 'src', 'main', 'res');
|
||||||
|
const drawableDir = path.join(resourceRoot, 'drawable');
|
||||||
|
const valuesDir = path.join(resourceRoot, 'values');
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
fs.mkdir(drawableDir, { recursive: true }),
|
||||||
|
fs.mkdir(valuesDir, { recursive: true }),
|
||||||
|
]);
|
||||||
|
await Promise.all([
|
||||||
|
fs.writeFile(path.join(drawableDir, NOTIFICATION_ICON_FILE), NOTIFICATION_ICON_VECTOR),
|
||||||
|
fs.writeFile(path.join(valuesDir, NOTIFICATION_OVERRIDE_FILE), NOTIFICATION_ICON_OVERRIDE),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withAstraNotificationIcon(config) {
|
||||||
|
return withDangerousMod(config, [
|
||||||
|
'android',
|
||||||
|
async (mod) => {
|
||||||
|
await writeNotificationIconResources(mod.modRequest.platformProjectRoot);
|
||||||
|
return mod;
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
function withAstraAndroidRelease(config) {
|
function withAstraAndroidRelease(config) {
|
||||||
config = withVendoredKotlinAudio(config);
|
config = withVendoredKotlinAudio(config);
|
||||||
config = withReleaseSigning(config);
|
config = withReleaseSigning(config);
|
||||||
|
config = withAstraNotificationIcon(config);
|
||||||
return withProfileableRelease(config);
|
return withProfileableRelease(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +233,12 @@ module.exports._internal = {
|
|||||||
PROJECT_BUILD_MARKER,
|
PROJECT_BUILD_MARKER,
|
||||||
SETTINGS_MARKER,
|
SETTINGS_MARKER,
|
||||||
SIGNING_MARKER,
|
SIGNING_MARKER,
|
||||||
|
NOTIFICATION_ICON_FILE,
|
||||||
|
NOTIFICATION_ICON_VECTOR,
|
||||||
|
NOTIFICATION_OVERRIDE_FILE,
|
||||||
|
NOTIFICATION_ICON_OVERRIDE,
|
||||||
addReleaseSigning,
|
addReleaseSigning,
|
||||||
appendBlock,
|
appendBlock,
|
||||||
ensureOptionalCameraFeature,
|
ensureOptionalCameraFeature,
|
||||||
|
writeNotificationIconResources,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { createRequire } from 'node:module';
|
import { createRequire } from 'node:module';
|
||||||
|
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
@@ -65,3 +68,36 @@ test('marks QR scanning camera hardware as optional without duplicating it', ()
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('writes deterministic Astra and RNTP notification icon resources', async (t) => {
|
||||||
|
const projectRoot = await mkdtemp(path.join(tmpdir(), 'astra-notification-icon-'));
|
||||||
|
t.after(() => rm(projectRoot, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
await _internal.writeNotificationIconResources(projectRoot);
|
||||||
|
const drawablePath = path.join(
|
||||||
|
projectRoot,
|
||||||
|
'app/src/main/res/drawable',
|
||||||
|
_internal.NOTIFICATION_ICON_FILE
|
||||||
|
);
|
||||||
|
const overridePath = path.join(
|
||||||
|
projectRoot,
|
||||||
|
'app/src/main/res/values',
|
||||||
|
_internal.NOTIFICATION_OVERRIDE_FILE
|
||||||
|
);
|
||||||
|
const firstDrawable = await readFile(drawablePath, 'utf8');
|
||||||
|
const firstOverride = await readFile(overridePath, 'utf8');
|
||||||
|
|
||||||
|
assert.equal(firstDrawable, _internal.NOTIFICATION_ICON_VECTOR);
|
||||||
|
assert.match(firstDrawable, /android:width="24dp"/);
|
||||||
|
assert.match(firstDrawable, /android:fillColor="#FFFFFFFF"/);
|
||||||
|
assert.doesNotMatch(firstDrawable, /background|logoShadow/);
|
||||||
|
assert.equal(firstOverride, _internal.NOTIFICATION_ICON_OVERRIDE);
|
||||||
|
assert.match(
|
||||||
|
firstOverride,
|
||||||
|
/name="exo_notification_small_icon" type="drawable">@drawable\/astra_notification_icon/
|
||||||
|
);
|
||||||
|
|
||||||
|
await _internal.writeNotificationIconResources(projectRoot);
|
||||||
|
assert.equal(await readFile(drawablePath, 'utf8'), firstDrawable);
|
||||||
|
assert.equal(await readFile(overridePath, 'utf8'), firstOverride);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user