Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions app/src/main/kotlin/com/wisp/app/Navigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,9 @@ fun WispNavHost(
feedViewModel.sendZap(event, amountMsats, message, isAnonymous, isPrivate)
},
onGoToWallet = { navController.navigate(Routes.WALLET) },
canPrivateZap = feedViewModel.hasLocalKeypair && userHasDmRelays && recipientHasDmRelays
canPrivateZap = feedViewModel.hasLocalKeypair && userHasDmRelays && recipientHasDmRelays,
recipientPubkey = searchZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}
SearchScreen(
Expand Down Expand Up @@ -1641,7 +1643,9 @@ fun WispNavHost(
},
onGoToWallet = { navController.navigate(Routes.WALLET) },
canPrivateZap = feedViewModel.hasLocalKeypair && feedViewModel.relayPool.hasDmRelays() && recipientHasDmRelays,
initialSatsHint = groupRoomZapInitialSats
initialSatsHint = groupRoomZapInitialSats,
recipientPubkey = groupRoomZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}
val groupRoomMediaLauncher = rememberLauncherForActivityResult(
Expand Down Expand Up @@ -1918,7 +1922,9 @@ fun WispNavHost(
},
onGoToWallet = { navController.navigate(Routes.WALLET) },
canPrivateZap = feedViewModel.hasLocalKeypair && threadUserHasDmRelays && threadRecipientHasDmRelays,
forcePrivate = threadZapTarget?.id?.let { feedViewModel.eventRepo.isPrivate(it) } == true
forcePrivate = threadZapTarget?.id?.let { feedViewModel.eventRepo.isPrivate(it) } == true,
recipientPubkey = threadZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}
val threadSetListedIds by feedViewModel.bookmarkSetRepo.allListedEventIds.collectAsState()
Expand Down Expand Up @@ -2088,7 +2094,9 @@ fun WispNavHost(
feedViewModel.sendZap(event, amountMsats, message, isAnonymous, isPrivate)
},
onGoToWallet = { navController.navigate(Routes.WALLET) },
canPrivateZap = feedViewModel.hasLocalKeypair && hashtagUserHasDmRelays && hashtagRecipientHasDmRelays
canPrivateZap = feedViewModel.hasLocalKeypair && hashtagUserHasDmRelays && hashtagRecipientHasDmRelays,
recipientPubkey = hashtagZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}

Expand Down Expand Up @@ -2240,7 +2248,9 @@ fun WispNavHost(
feedViewModel.sendZap(event, amountMsats, message, isAnonymous, isPrivate)
},
onGoToWallet = { navController.navigate(Routes.WALLET) },
canPrivateZap = feedViewModel.hasLocalKeypair && setFeedUserHasDmRelays && setFeedRecipientHasDmRelays
canPrivateZap = feedViewModel.hasLocalKeypair && setFeedUserHasDmRelays && setFeedRecipientHasDmRelays,
recipientPubkey = setFeedZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}

Expand Down Expand Up @@ -2405,7 +2415,9 @@ fun WispNavHost(
feedViewModel.sendZap(event, amountMsats, message, isAnonymous, isPrivate)
},
onGoToWallet = { navController.navigate(Routes.WALLET) },
canPrivateZap = feedViewModel.hasLocalKeypair && articleUserHasDmRelays && articleRecipientHasDmRelays
canPrivateZap = feedViewModel.hasLocalKeypair && articleUserHasDmRelays && articleRecipientHasDmRelays,
recipientPubkey = articleZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}

Expand Down Expand Up @@ -2612,7 +2624,12 @@ fun WispNavHost(
// DIP-03 needs a concrete note id for the ephemeral key
// derivation; live-stream zaps target an addressable event
// (a-tag) instead, so private zaps don't apply here.
canPrivateZap = false
canPrivateZap = false,
// Live streams: use the streamer override pubkey when set
// (the chat host is what's interesting to identify), else
// fall back to the post author.
recipientPubkey = liveZapRecipientOverride ?: liveZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}
val streamActivityEventId = remember(hostPubkey, dTag) {
Expand Down Expand Up @@ -3175,7 +3192,9 @@ fun WispNavHost(
},
onGoToWallet = { navController.navigate(Routes.WALLET) },
canPrivateZap = feedViewModel.hasLocalKeypair && notifUserHasDmRelays && notifRecipientHasDmRelays,
forcePrivate = notifZapTarget?.id?.let { feedViewModel.eventRepo.isPrivate(it) } == true
forcePrivate = notifZapTarget?.id?.let { feedViewModel.eventRepo.isPrivate(it) } == true,
recipientPubkey = notifZapTarget?.pubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}

Expand All @@ -3193,7 +3212,9 @@ fun WispNavHost(
rumorId = target.rumorId.ifEmpty { null }
)
},
onGoToWallet = { navController.navigate(Routes.WALLET) }
onGoToWallet = { navController.navigate(Routes.WALLET) },
recipientPubkey = notifDmZapTarget?.senderPubkey,
profileLookup = { feedViewModel.profileRepo.get(it) }
)
}

Expand Down
36 changes: 32 additions & 4 deletions app/src/main/kotlin/com/wisp/app/repo/InterfacePreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ class InterfacePreferences(context: Context) {
}
}

companion object {
val postUndoTimerOptions = listOf(5, 10, 15, 20, 30)
}

private val prefs = context.getSharedPreferences("wisp_settings", Context.MODE_PRIVATE)

fun getAccentColor(): Int = prefs.getInt("accent_color", 0xFFFF9800.toInt())
Expand Down Expand Up @@ -69,6 +65,38 @@ class InterfacePreferences(context: Context) {
fun isPostUndoTimerForReplies(): Boolean = prefs.getBoolean("post_undo_timer_for_replies", false)
fun setPostUndoTimerForReplies(enabled: Boolean) = prefs.edit().putBoolean("post_undo_timer_for_replies", enabled).apply()

// ── Instant (a.k.a. quick) zaps ─────────────────────────────────────────
// Hold-to-zap on the post-card fires immediately at the configured
// amount when enabled; tap still opens the composer.

fun isQuickZapEnabled(): Boolean = prefs.getBoolean("quick_zap_enabled", false)
fun setQuickZapEnabled(enabled: Boolean) = prefs.edit().putBoolean("quick_zap_enabled", enabled).apply()

fun getQuickZapAmountSats(): Long = prefs.getLong("quick_zap_amount_sats", 100L).coerceIn(1L, QUICK_ZAP_MAX_SATS)
fun setQuickZapAmountSats(amount: Long) {
// Hard clamp at 10K sats so an instant zap never bypasses the soft
// confirmation dialog in the ZapSheet (which fires at >10K).
val clamped = amount.coerceIn(1L, QUICK_ZAP_MAX_SATS)
prefs.edit().putLong("quick_zap_amount_sats", clamped).apply()
}

fun getQuickZapAmountFiat(): Double =
prefs.getString("quick_zap_amount_fiat", "0.10")?.toDoubleOrNull()?.coerceAtLeast(0.0) ?: 0.10
fun setQuickZapAmountFiat(amount: Double) {
// Fiat clamp happens at fire time against the cached exchange rate
// (callers in ZapSheet do `min(localFiat, sats→fiat(10_000))`).
val clamped = amount.coerceAtLeast(0.0)
prefs.edit().putString("quick_zap_amount_fiat", clamped.toString()).apply()
}

fun getQuickZapMessage(): String = prefs.getString("quick_zap_message", "") ?: ""
fun setQuickZapMessage(message: String) = prefs.edit().putString("quick_zap_message", message).apply()

companion object {
val postUndoTimerOptions = listOf(5, 10, 15, 20, 30)
const val QUICK_ZAP_MAX_SATS = 10_000L
}

/** Reset all interface preferences to defaults (called on full logout). */
fun reset() {
prefs.edit()
Expand Down
36 changes: 32 additions & 4 deletions app/src/main/kotlin/com/wisp/app/ui/component/ActionBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fun ActionBar(
onQuote: () -> Unit = {},
hasUserReposted: Boolean = false,
onZap: () -> Unit = {},
onZapLongPress: (() -> Unit)? = null,
hasUserZapped: Boolean = false,
onAddToList: () -> Unit = {},
isInList: Boolean = false,
Expand Down Expand Up @@ -209,12 +210,39 @@ fun ActionBar(
Spacer(Modifier.width(8.dp))
Box {
val zapClickable = !isZapInProgress
IconButton(
onClick = { if (zapEnabled) onZap() else onZapDisabledTap() },
enabled = zapClickable
// combinedClickable lets the zap glyph distinguish tap
// (open composer) from long-press (fire instant zap when
// enabled). Pin a fired-flag so the tap handler doesn't
// also fire when the long-press completes — Compose, like
// SwiftUI, fires both onClick AND onLongClick on release.
val longPressFired = remember { androidx.compose.runtime.mutableStateOf(false) }
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(48.dp)
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = androidx.compose.material3.ripple(bounded = false, radius = 24.dp),
enabled = zapClickable,
onClick = {
if (longPressFired.value) {
longPressFired.value = false
} else if (zapEnabled) {
onZap()
} else {
onZapDisabledTap()
}
},
onLongClick = if (zapEnabled && onZapLongPress != null) {
{
longPressFired.value = true
onZapLongPress()
}
} else null
)
) {
val zapTint = when {
!zapEnabled -> MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)
!zapEnabled -> MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.35f)
hasUserZapped -> WispThemeColors.zapColor
else -> MaterialTheme.colorScheme.onSurfaceVariant
}
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/kotlin/com/wisp/app/ui/component/PostCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ fun PostCard(
hasUserReposted: Boolean = false,
repostCount: Int = 0,
onZap: () -> Unit = {},
onZapLongPress: (() -> Unit)? = null,
onZapDisabledTap: () -> Unit = {},
zapEnabled: Boolean = true,
hasUserZapped: Boolean = false,
Expand Down Expand Up @@ -822,6 +823,9 @@ fun PostCard(
hasUserReposted = hasUserReposted,
repostCount = repostCount,
onZap = onZap,
// Self-zap short-circuit: long-press also disabled
// when zapEnabled is false. iOS does the same.
onZapLongPress = onZapLongPress,
hasUserZapped = hasUserZapped,
onAddToList = onAddToList,
isInList = isInList,
Expand All @@ -835,7 +839,10 @@ fun PostCard(
unicodeEmojis = unicodeEmojis,
onOpenEmojiLibrary = onOpenEmojiLibrary,
isPrivate = isPrivate,
zapEnabled = zapEnabled,
// Self-zap disabled: render at low opacity, both tap
// and long-press become no-ops (the latter via
// `zapEnabled` short-circuit in ActionBar).
zapEnabled = zapEnabled && !isOwnEvent,
onZapDisabledTap = onZapDisabledTap,
modifier = Modifier.weight(1f)
)
Expand Down
Loading