Goblin Build 116
Floonet scoped Nym exit on the money-path relay (dial a relay operator's co-located exit over a MixnetStream, TLS end to end, no public DNS; anchor + fallback, never pin-only), NIP-44 v3 gift wraps (G4), mix-DNS, localization, GUI polish. Verified end to end: two wallets complete a real gift-wrapped Grin payment over relay.goblin.st, finalized + posted on mainnet.
@@ -23,6 +23,10 @@ public class BackgroundService extends Service {
|
||||
private boolean mStopped = false;
|
||||
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
// One-shot "payment received" notification, separate from the persistent
|
||||
// sync notification above.
|
||||
private static final int PAYMENT_NOTIFICATION_ID = 2;
|
||||
private static final String PAYMENT_CHANNEL_ID = "PaymentReceived";
|
||||
private NotificationCompat.Builder mNotificationBuilder;
|
||||
|
||||
private String mNotificationContentText = "";
|
||||
@@ -189,6 +193,40 @@ public class BackgroundService extends Service {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
// Show a one-shot "payment received" notification (id=2), separate from
|
||||
// the persistent sync notification (id=1). Called from native code via
|
||||
// MainActivity when a payment slatepack is received over nostr, possibly
|
||||
// while the app is backgrounded. Localization of the fixed strings is a
|
||||
// follow-up (text is composed here at Java side).
|
||||
public static void notifyPaymentReceived(Context context, String name, String amount) {
|
||||
NotificationManager manager = context.getSystemService(NotificationManager.class);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
// High-importance channel so the notification pops with sound + vibration.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
PAYMENT_CHANNEL_ID, "Payments", NotificationManager.IMPORTANCE_HIGH
|
||||
);
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
Intent i = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, i, PendingIntent.FLAG_IMMUTABLE);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, PAYMENT_CHANNEL_ID)
|
||||
.setContentTitle("Payment received")
|
||||
.setContentText(name + " paid " + amount + " ツ")
|
||||
.setSmallIcon(R.drawable.ic_stat_name)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setContentIntent(pendingIntent);
|
||||
try {
|
||||
manager.notify(PAYMENT_NOTIFICATION_ID, builder.build());
|
||||
} catch (SecurityException e) {
|
||||
// POST_NOTIFICATIONS not granted: skip the notification, never the payment.
|
||||
}
|
||||
}
|
||||
|
||||
// Start the service.
|
||||
public static void start(Context c) {
|
||||
if (!isServiceRunning(c)) {
|
||||
|
||||
@@ -421,6 +421,12 @@ public class MainActivity extends GameActivity {
|
||||
// Notify native code to stop activity (e.g. node) if app was terminated from recent apps.
|
||||
public native void onTermination();
|
||||
|
||||
// Called from native code to show a "payment received" notification
|
||||
// (BackgroundService id=2) when a payment arrives over nostr.
|
||||
public void notifyPaymentReceived(String name, String amount) {
|
||||
BackgroundService.notifyPaymentReceived(this, name, amount);
|
||||
}
|
||||
|
||||
// Called from native code to set text into clipboard.
|
||||
public void copyText(String data) {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
@@ -619,7 +625,12 @@ public class MainActivity extends GameActivity {
|
||||
// Called from native code to pick the file.
|
||||
public void pickFile() {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("text/*");
|
||||
// Permissive type: custom extensions like .backup have no registered
|
||||
// MIME type, so any narrower filter greys them out in the picker and
|
||||
// locks users out of restoring their identity. Content is validated
|
||||
// on the native side after selection.
|
||||
intent.setType("*/*");
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
try {
|
||||
mFilePickResult.launch(Intent.createChooser(intent, "Pick file"));
|
||||
} catch (android.content.ActivityNotFoundException ex) {
|
||||
|
||||
|
Before Width: | Height: | Size: 984 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 965 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.7 KiB |