mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +00:00
fix: let a notification's delete skip the unlock when the mail goes to trash #85
This commit is contained in:
@@ -77,6 +77,10 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
|||||||
val markReadLabel = options.takeIf { it.hasKey("markReadLabel") }?.getString("markReadLabel") ?: "Mark as read"
|
val markReadLabel = options.takeIf { it.hasKey("markReadLabel") }?.getString("markReadLabel") ?: "Mark as read"
|
||||||
val deleteLabel = options.takeIf { it.hasKey("deleteLabel") }?.getString("deleteLabel") ?: "Delete"
|
val deleteLabel = options.takeIf { it.hasKey("deleteLabel") }?.getString("deleteLabel") ?: "Delete"
|
||||||
val replyLabel = options.takeIf { it.hasKey("replyLabel") }?.getString("replyLabel") ?: "Reply"
|
val replyLabel = options.takeIf { it.hasKey("replyLabel") }?.getString("replyLabel") ?: "Reply"
|
||||||
|
// Delete moves the mail to Trash where there is one: no unlock needed
|
||||||
|
// for that (#85). Without a Trash it destroys the mail, so it asks.
|
||||||
|
// Unlock stays the default for a caller that doesn't say.
|
||||||
|
val deleteNeedsUnlock = !options.hasKey("deleteNeedsUnlock") || options.getBoolean("deleteNeedsUnlock")
|
||||||
|
|
||||||
// Bitmap fetch + draw off the bridge thread so the caller doesn't
|
// Bitmap fetch + draw off the bridge thread so the caller doesn't
|
||||||
// block waiting for the favicon request.
|
// block waiting for the favicon request.
|
||||||
@@ -86,7 +90,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
|||||||
postNotification(
|
postNotification(
|
||||||
notificationId, title, body, preview, largeIcon, bgColorHex,
|
notificationId, title, body, preview, largeIcon, bgColorHex,
|
||||||
emailId, threadId, subject, accountId, jmapAccountId, groupKey,
|
emailId, threadId, subject, accountId, jmapAccountId, groupKey,
|
||||||
groupTitle, markReadLabel, deleteLabel, replyLabel,
|
groupTitle, markReadLabel, deleteLabel, replyLabel, deleteNeedsUnlock,
|
||||||
)
|
)
|
||||||
if (groupKey != null) postGroupSummary(groupKey, groupTitle, bgColorHex, accountId)
|
if (groupKey != null) postGroupSummary(groupKey, groupTitle, bgColorHex, accountId)
|
||||||
promise.resolve(null)
|
promise.resolve(null)
|
||||||
@@ -122,6 +126,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
|||||||
markReadLabel: String,
|
markReadLabel: String,
|
||||||
deleteLabel: String,
|
deleteLabel: String,
|
||||||
replyLabel: String,
|
replyLabel: String,
|
||||||
|
deleteNeedsUnlock: Boolean,
|
||||||
) {
|
) {
|
||||||
val ctx = reactApplicationContext
|
val ctx = reactApplicationContext
|
||||||
val intent = Intent(ctx, MainActivity::class.java).apply {
|
val intent = Intent(ctx, MainActivity::class.java).apply {
|
||||||
@@ -209,8 +214,8 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
|||||||
)
|
)
|
||||||
builder.addAction(
|
builder.addAction(
|
||||||
NotificationCompat.Action.Builder(0, deleteLabel, deletePending)
|
NotificationCompat.Action.Builder(0, deleteLabel, deletePending)
|
||||||
// Android 12+: unlock first, so a locked phone can't delete mail.
|
// Android 12+: unlock first, so a locked phone can't destroy mail.
|
||||||
.setAuthenticationRequired(true)
|
.setAuthenticationRequired(deleteNeedsUnlock)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
// #85: a notification's Delete asked to unlock the phone even where it only
|
||||||
|
// moves the mail to Trash. The push task tells the native module
|
||||||
|
// (deleteNeedsUnlock: !hasTrash); the module has to use it on the action.
|
||||||
|
|
||||||
|
const MODULE = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../../android/app/src/main/java/com/anonymous/bulwarkmobile/BulwarkFcmModule.kt',
|
||||||
|
);
|
||||||
|
const source = fs.readFileSync(MODULE, 'utf8');
|
||||||
|
|
||||||
|
function postNotificationCalls(src: string): string[] {
|
||||||
|
const out: string[] = [];
|
||||||
|
const re = /(?<!fun )\bpostNotification\(/g;
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
while ((m = re.exec(src))) {
|
||||||
|
let depth = 0;
|
||||||
|
let i = m.index + 'postNotification'.length;
|
||||||
|
for (; i < src.length; i++) {
|
||||||
|
if (src[i] === '(') depth++;
|
||||||
|
else if (src[i] === ')' && --depth === 0) break;
|
||||||
|
}
|
||||||
|
out.push(src.slice(m.index, i + 1));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('notification Delete action', () => {
|
||||||
|
it('reads deleteNeedsUnlock and keeps unlock as the default', () => {
|
||||||
|
expect(source).toMatch(
|
||||||
|
/val deleteNeedsUnlock = !options\.hasKey\("deleteNeedsUnlock"\) \|\| options\.getBoolean\("deleteNeedsUnlock"\)/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('asks to unlock only when told to', () => {
|
||||||
|
expect(source).toMatch(/\.setAuthenticationRequired\(deleteNeedsUnlock\)/);
|
||||||
|
expect(source).not.toMatch(/\.setAuthenticationRequired\(true\)/);
|
||||||
|
expect(source).toMatch(/private fun postNotification\([^)]*deleteNeedsUnlock: Boolean/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes it on every postNotification call', () => {
|
||||||
|
const calls = postNotificationCalls(source);
|
||||||
|
expect(calls.length).toBeGreaterThan(0);
|
||||||
|
for (const call of calls) expect(call).toMatch(/\bdeleteNeedsUnlock\b/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user