Mitra: branded notification sound (halobestie_notif.ogg)
Drops the system notification ding for incoming mitra FCM (Curhat Baru,
Permintaan Perpanjang) and plays the HaloBestie audio mark instead.
Source: a 2.8s mono AAC inside a 3GPP container the user supplied;
converted to 32 KB OGG (Vorbis q5) for Android since the channel-sound
API needs `res/raw/<name>.<ext>` and OGG is the smallest universally
supported short-sound format on Android 5+.
- mitra_app: bump notification channel ID from `chat_messages` to
`halobestie_chat_v1` (Android binds channel sound at create time
on API 26+, so existing installs with the old channel need a fresh
ID to pick up the new sound — can't mutate in place). Bind
RawResourceAndroidNotificationSound('halobestie_notif') at both
channel-create time and per-notification details (latter covers
API 24/25 where channels don't exist).
- Backend: branch FCM `android.notification.channelId` by recipient
type — mitras → `halobestie_chat_v1`, customers → `chat_messages`
(unchanged). Customer app keeps system sound until/unless we ship
a customer-side sound too.
Verified on emulator-5556 via `adb shell dumpsys notification` — the
new channel resolves to
`android.resource://com.mybestie.mitra/raw/halobestie_notif`. The OGG
ships inside the APK (32092 bytes, confirmed via `unzip -l`).
Follow-up (iOS): bundle the same sound as `.caf` under ios/Runner +
register as a Runner-target resource in pbxproj + reference filename
in the APS payload. Deferred until iOS testing comes back into scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,14 @@ export const sendPushNotification = async (recipientType, recipientId, { title,
|
|||||||
|
|
||||||
if (!user?.fcm_token) return false
|
if (!user?.fcm_token) return false
|
||||||
|
|
||||||
|
// Mitra app ships a branded notification sound on its own channel
|
||||||
|
// (`halobestie_chat_v1`, declared in mitra_app/lib/core/notifications/
|
||||||
|
// notification_service.dart). Customer app keeps the legacy
|
||||||
|
// `chat_messages` channel until/unless we ship a customer sound too.
|
||||||
|
const androidChannelId = recipientType === UserType.MITRA
|
||||||
|
? 'halobestie_chat_v1'
|
||||||
|
: 'chat_messages'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await admin.messaging().send({
|
await admin.messaging().send({
|
||||||
token: user.fcm_token,
|
token: user.fcm_token,
|
||||||
@@ -33,7 +41,7 @@ export const sendPushNotification = async (recipientType, recipientId, { title,
|
|||||||
},
|
},
|
||||||
android: {
|
android: {
|
||||||
priority: 'high',
|
priority: 'high',
|
||||||
notification: { channelId: 'chat_messages' },
|
notification: { channelId: androidChannelId },
|
||||||
},
|
},
|
||||||
apns: {
|
apns: {
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
BIN
mitra_app/android/app/src/main/res/raw/halobestie_notif.ogg
Normal file
BIN
mitra_app/android/app/src/main/res/raw/halobestie_notif.ogg
Normal file
Binary file not shown.
@@ -12,11 +12,18 @@ class NotificationService {
|
|||||||
/// Set this from the app to bridge notifications → Riverpod state.
|
/// Set this from the app to bridge notifications → Riverpod state.
|
||||||
static void Function(String sessionId)? onChatRequestTapped;
|
static void Function(String sessionId)? onChatRequestTapped;
|
||||||
|
|
||||||
|
// Channel ID is bumped (`chat_messages` → `halobestie_chat_v1`) because
|
||||||
|
// Android binds notification sound at channel-creation time on API 26+. An
|
||||||
|
// existing `chat_messages` channel still routes through whatever sound was
|
||||||
|
// set when it was first created (system default), so we mint a fresh ID
|
||||||
|
// for the branded sound. Backend FCM payloads target the same ID — see
|
||||||
|
// backend/src/services/notification.service.js.
|
||||||
static const _channel = AndroidNotificationChannel(
|
static const _channel = AndroidNotificationChannel(
|
||||||
'chat_messages',
|
'halobestie_chat_v1',
|
||||||
'Chat Messages',
|
'Chat HaloBestie',
|
||||||
description: 'Notifications for incoming chat messages',
|
description: 'Notifications for incoming chat messages and extension requests',
|
||||||
importance: Importance.high,
|
importance: Importance.high,
|
||||||
|
sound: RawResourceAndroidNotificationSound('halobestie_notif'),
|
||||||
);
|
);
|
||||||
|
|
||||||
static Future<void> initialize(GoRouter router) async {
|
static Future<void> initialize(GoRouter router) async {
|
||||||
@@ -64,6 +71,9 @@ class NotificationService {
|
|||||||
channelDescription: _channel.description,
|
channelDescription: _channel.description,
|
||||||
importance: Importance.high,
|
importance: Importance.high,
|
||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
|
// API 26+ ignores this in favor of the channel's sound; included for
|
||||||
|
// the API 24/25 path where channels don't exist yet.
|
||||||
|
sound: const RawResourceAndroidNotificationSound('halobestie_notif'),
|
||||||
),
|
),
|
||||||
iOS: const DarwinNotificationDetails(
|
iOS: const DarwinNotificationDetails(
|
||||||
presentAlert: true,
|
presentAlert: true,
|
||||||
@@ -106,6 +116,7 @@ class NotificationService {
|
|||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
playSound: true,
|
playSound: true,
|
||||||
enableVibration: true,
|
enableVibration: true,
|
||||||
|
sound: const RawResourceAndroidNotificationSound('halobestie_notif'),
|
||||||
),
|
),
|
||||||
iOS: const DarwinNotificationDetails(
|
iOS: const DarwinNotificationDetails(
|
||||||
presentAlert: true,
|
presentAlert: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user