586 Commits

Author SHA1 Message Date
callebtc
1376b55931 Delay nearby peer availability notification 2026-08-02 21:54:03 +02:00
callebtc
49753ccb88
Merge pull request #860 from permissionlesstech/feat/bubble-media-messages
feat(ui): wrap image and voice messages in bubble shells
2026-08-02 20:33:10 +02:00
callebtc
bc572cc2ea fix(ui): make media bubble shell long-clickable
Self and grouped voice-note bubbles had no long-press target: the sender
label is hidden and VoiceNotePlayer controls consume touches, so the
message action sheet was unreachable. combinedClickable on the shell
restores long-press for every media bubble.
2026-08-02 20:28:04 +02:00
GitHub Action
c02dda308e Automated update of relay data - Sun Aug 2 06:55:18 UTC 2026 2026-08-02 06:55:19 +00:00
callebtc
2325b7c9c3 Remove periodic GATT RSSI polling 2026-08-02 01:42:45 +02:00
callebtc
b8c7470ace
Merge pull request #847 from heyaim/hide-command-suggestions-after-send
Hide the command and mention popups after a message is sent
2026-08-02 00:17:28 +02:00
callebtc
b43dc1e48f feat(ui): wrap image and voice messages in bubble shells
Bubble mode previously rendered media as flat header-plus-card rows while
text messages got the tinted bubble treatment. Introduce MediaBubbleShell,
which mirrors BubbleTextMessageLayout (author-colour wash and hairline,
speaker-side tail, in-bubble sender name, flush-right timestamp with
delivery ticks for own private messages), and route ImageMessageItem and
AudioMessageItem through it. Matrix mode is unchanged.
2026-08-01 23:48:13 +02:00
callebtc
2e943b2ebc
Merge pull request #846 from permissionlesstech/bitchat-ui-bubbles
feat(chat): add bubbles chat UI mode with peer-color bubbles
2026-08-01 21:31:57 +02:00
callebtc
aad0c4d7f8 feat(settings): combine theme and chat style into one card
System/Light/Dark and the chat style picker now share the single Theme
card in About -> Settings: theme chips on the first row, chat style
chips on the second, ordered Bubbles then Matrix. Bubbles remains the
default for fresh installs.
2026-08-01 21:17:30 +02:00
callebtc
bd5ae8702a feat(chat): never show our own nickname in bubbles mode
The end side and the author-colour wash already attribute own bubbles,
so the @name heading on the first bubble of an own run was redundant.
Received bubbles keep their sender names; matrix mode is unchanged.
2026-08-01 20:45:42 +02:00
callebtc
d85401fb49 fix(chat): never let the bubble meta cluster influence text wrapping
The no-break-space reservation narrowed the text's own wrap width, so
first lines wrapped early and carried dead slack. The cluster (timestamp
plus delivery checks) is now placed from the laid-out text: it rides
flush-right in the last line's slack when there is room and drops below
the text only when there is not. The body wraps at the full bubble
width either way, one-liners keep hugging their content, and matrix
mode is unchanged. AnnotatedClickableText gains an optional onTextLayout
callback to support the measurement.
2026-08-01 20:10:25 +02:00
callebtc
79d2e0328a fix(chat): keep bubble meta flush-right without extra lines or width
The timestamp + checks cluster now overlays the bubble's bottom-end
corner while the body reserves an invisible no-break-space run (capped
by a zero-width word joiner so it is never trimmed) exactly where the
cluster lands. The reservation rides the last text line when there is
room and wraps only when there isn't, so the cluster sits flush-right
on the last line for every message length: no overlap, no forced new
line, no minimum bubble width. Matrix mode is unchanged.
2026-08-01 19:55:07 +02:00
callebtc
6d9dfd970c fix(chat): trail bubble timestamp and checks inline with the body
Replaces the separate bottom meta row, which forced every bubble to at
least the meta row's width and added a line even to one-line messages.
The timestamp + checks cluster now trails the body inline: it rides
the last text line when there is room and wraps only when there isn't,
so bubbles hug their content again. Own private messages keep the
constant-width grey-to-green checks with the colour tween; matrix mode
is unchanged.
2026-08-01 18:04:39 +02:00
callebtc
498c35188f feat(chat): right-align bubble timestamps beside the delivery checks
Bubbles now park a bottom meta row at their end edge, like classic
messengers: the timestamp sits right-aligned, followed at a fixed gap
by the delivery checks for own private messages, instead of trailing
the body text mid-line. Matrix mode keeps its inline trailing
timestamps.
2026-08-01 17:34:36 +02:00
callebtc
6eafa5932d feat(chat): anchor delivery checks to the bubble's bottom-end corner
Instead of trailing the timestamp mid-line, the delivery checks now
park at the bubble's bottom-end corner like classic messengers, with
the body text reserving a small end inset so the last line never
collides with them. The checks keep their constant-width grey-to-green
behaviour, colour tween, and scale pop (shared DeliveryStatusIcon).
Matrix mode is unchanged.
2026-08-01 17:07:41 +02:00
callebtc
7247d0ad5b feat(chat): constant-width two-check delivery marker that lights up green
Delivery/read markers previously swapped glyphs as acknowledgements
arrived, reflowing the text around them. Both checks now render from
the start in a disabled grey and simply recolour as the state advances
(delivered lights the first check, read lights both), so nothing ever
pushes message text around. Read receipts use the app's primary green
instead of the blue accent, a quick colour tween lights the checks up,
and the standalone marker adds a snappy scale pop when the state
advances. Applies to both matrix and bubbles modes.
2026-08-01 16:06:37 +02:00
callebtc
af91abab01 feat(chat): pull sender and delivery status into bubbles, thin-space hash suffix
Bubbles mode now reads like a classic messenger thread:

- the sender's name heads the first bubble of each run instead of
  floating above it; continuation bubbles skip it
- the delivery/read marker for own private messages trails the
  timestamp inside the bubble (same glyph mapping as the standalone
  marker); media rows keep the beneath-card marker since they have no
  inline text
- display names and their #abcd disambiguation suffix are now
  separated by a thin space (U+2009) in both matrix and bubbles modes
2026-08-01 15:05:41 +02:00
callebtc
7569fba7b4 feat(chat): align self media rows with bubbles chat UI mode
In bubbles mode, self-authored image, voice-note, and file rows now
align to the end side with the same tail-corner cue as text bubbles,
instead of sitting on the received side where they read as someone
else's content. Grouped self voice notes also keep the run on the
correct side. VoiceNotePlayer gains an optional modifier so the player
can hug the end side at a capped width. Received media and matrix mode
are unchanged.
2026-08-01 13:57:40 +02:00
heyaim
862fd8449d Hide the command and mention popups after a message is sent
When a slash command is sent without tapping the autocomplete list, the
command popup stays on screen with its old suggestions. The send path
clears the input field in code, and the popup is hidden only from the
field's text-change handler, which a code-driven clear does not run, so
the show flag is never turned off. The mention popup has the same cause.

CommandProcessor gains clearSuggestions(), which hides both popups and
empties their lists, the same resets the two select functions already do
inline. ChatScreen's send handler calls it where it already resets the
field.

The private chat sheet called updateMentionSuggestions on every keystroke
while rendering its own popups as hidden, so the only effect was on the
main composer behind it: typing @ in the sheet and then sending or
dismissing left a stale mention popup on the main screen. That call is
removed.

Tests in CommandProcessorTest open each popup and assert clearSuggestions
hides it. The unit tests do not drive Compose, so they cover the function
the send handler calls rather than the handler itself.

Full Android unit suite green; lint and the debug build pass.

Fixes #250.
2026-07-31 23:27:10 -05:00
callebtc
94d5fa3084 feat(chat): add bubbles chat UI mode with peer-color bubbles
Add a toggleable chat transcript style alongside the existing matrix
transcript. Bubbles mode renders text messages as classic messenger
bubbles: own messages on the right, peers on the left, each bubble
washed with the author's stable identity-derived peer colour so the
speaker stays identifiable without changing any theme, surface, or
background colours.

- ChatUiMode preference (Matrix / Bubbles, default Bubbles) persisted
  via ChatUiModeManager, mirroring ThemePreferenceManager
- BubbleTextMessageLayout: rounded bubble with a subtle tail on the
  speaker's side, width capped at 80% so long messages wrap; sender
  labels, grouping, timestamps, mentions, links, long-press, and the
  existing spring entry/placement animations are unchanged
- Delivery status for own private messages moves beneath the bubble in
  bubbles mode so it never overlaps the tail
- Chat style picker in About -> Settings reusing the ThemeChip pattern
2026-08-01 01:41:36 +02:00
callebtc
cbc59aaf8b feat(voice): add live push-to-talk 2026-07-31 17:40:37 +02:00
callebtc
229d93b54b
Merge pull request #826 from vincenzopalazzo/fix/file-tlv-forward-compat
Skip unknown file TLVs instead of dropping the transfer
2026-07-31 02:57:08 +02:00
callebtc
87184eddde globe fixes 2026-07-30 23:09:53 +02:00
callebtc
1a792ecfec fix: harden unknown file TLV parsing 2026-07-30 19:33:02 +02:00
callebtc
8abe570374 Restore location notes access 2026-07-30 19:29:19 +02:00
callebtc
643f329d9d
Merge pull request #813 from qutad/fix/766-header-location-control
feat: improve location control accessibility and crowding
2026-07-30 19:12:51 +02:00
callebtc
ae70c02149
Merge pull request #833 from a1denvalu3/optimize/geohash-picker-low-end
Optimize geohash globe rendering and gestures
2026-07-30 19:08:38 +02:00
callebtc
aaac810f4f
Merge pull request #829 from TheCodeSmith404/fix/panic-mode-memory-purge-807
fix(panic): clear in-memory networking caches, router outbox, and Nostr queues on panic wipe (#807)
2026-07-30 16:33:06 +02:00
a1denvalu3
0d7f8cef10 Prevent whole-globe land fills 2026-07-30 15:50:21 +02:00
a1denvalu3
6edd361ed3 Fix globe land fill orientation 2026-07-30 15:33:17 +02:00
a1denvalu3
6fc4583a15 Stabilize globe quality selector layout 2026-07-30 15:09:29 +02:00
a1denvalu3
8602db88de Add geohash globe render quality selector 2026-07-30 14:39:29 +02:00
a1denvalu3
5bde4e0b4a Close fully visible geohash outlines 2026-07-30 14:29:22 +02:00
a1denvalu3
2a43265e12 Optimize geohash globe rendering and gestures 2026-07-30 14:01:02 +02:00
callebtc
1a953a17ab Fix private conversation alias isolation 2026-07-30 12:36:46 +02:00
callebtc
b692ec7b44
Merge pull request #828 from moehamade/fix/hotspot-group-consent
fix(hotspot): never disturb another app's Wi-Fi Direct group without consent
2026-07-30 12:30:00 +02:00
TheCodeSmith404
e311649525 fix(panic): clear in-memory networking caches, router outbox, and Nostr queues on panic wipe (#807) 2026-07-30 11:04:17 +05:30
callebtc
b6ad8971a8 fix(hotspot): revalidate ownership during teardown 2026-07-30 03:20:48 +02:00
callebtc
1699cc7986 Header: preserve compact channel accessibility state 2026-07-30 03:00:49 +02:00
callebtc
33538fa9e0
Merge pull request #827 from a1denvalu3/fix/stale-peer-cleanup
Fix stale peer lifecycle cleanup
2026-07-30 02:41:13 +02:00
Moe Hamade
5859ac29cd fix(hotspot): confirm a group is ours before trusting or removing it
Two related ways the app could act on a group it did not create.

createdGroup was set once and never revisited, so it outlived the group it
described. Our group can disappear without us - Wi-Fi toggled, another app
issuing its own device-scoped removeGroup(), a driver reset - and another
app can create one in its place while the sharing screen is still open.
Stop then believed the group present was ours and removed it, disconnecting
a session the user never agreed to touch. Every group snapshot now
reconciles the flag against what is actually on the framework.

isGroupOwner cannot carry that judgement on its own: it reports that this
DEVICE hosts the group, which is equally true of an autonomous group
another app created here. So a replacement group was still being adopted -
its name persisted as ownedGroupName, its credentials shown as ours below Q
- and because ownership is now an exact-name match, the next start
classified that name as ours and removed it with no confirmation. Snapshots
are only read once the group is confirmed to be the one we created: above Q
by the network name we chose, below Q by the first name the framework
reported for our own successful creation, fixed from then on.

Reconciliation waits until our group has been named, because a null
snapshot during formation says nothing about a group that has not appeared
yet. Losing the flag to a transient null is safe in a way that keeping it
is not - the group we created is left behind, and the next start recognises
it by name and removes it silently.

A group-info reply arriving after the stop is now ignored, so it cannot
revive state the stop just cleared.

Device-verified on a Pixel 9a (Android 16, API 37).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:40:17 +03:00
Moe Hamade
141adf5468 fix(hotspot): only remove our own group; ask consent to replace a foreign one
removeGroup() is device-scoped: it removes whatever Wi-Fi Direct group
exists, including one owned by Cast, Android Auto or Quick Share.
stopHotspot() called it unconditionally, so the path built to protect a
foreign group tore that group down anyway. Removal on stop is now gated on
a createdGroup flag, set once our own createGroup command is accepted; with
nothing of ours on the framework, stop closes the channel and leaves the
group alone.

When a group we did not record creating is active at start, the app no
longer guesses about ownership - it asks. A confirmation dialog explains
that starting will disconnect the current Wi-Fi Direct connection;
confirming retries the start with replacement authorized, cancelling
leaves everything untouched. Consent is bound to the group it was given
for: the conflicting group's name travels through the dialog, and the
policy only authorizes removing a group with exactly that name - one that
appeared later, or swapped in mid-retry, re-prompts instead of riding on
stale approval.

Because consent replaces ownership proof, the DIRECT-BC- prefix heuristic
is gone: a prefix match is not ownership (this device can be connected to
another phone's bitchat group), so only the exact recorded name counts.
The record is also kept honest: never taken from a group we do not host,
and never overwritten while an old group of ours may still exist, so a
BUSY retry cannot misclassify our own stale group as foreign.

Also: SecurityException guards on the removeGroup() sites reached from
framework callbacks (permission revoked mid-session crashed instead of
failing cleanly); stopHotspot() takes a completion callback so the
ViewModel releases its Wi-Fi Aware lease only after the framework
acknowledges the removal, with an idempotent 10s fallback so a dropped
acknowledgement cannot pin the mesh down; and the confirm/cancel handlers
guard on the ConfirmDisconnect state so a tap landing through a screen
transition cannot tear down a just-confirmed session.

Replaces the state-machine approach of #811 - same protection at
proportionate cost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 01:28:09 +03:00
Vincenzo Palazzo
46de1a3623 fix: skip unknown file TLVs instead of dropping the transfer
`BitchatFilePacket.decode` resolved every tag through the four-value
`TLVType` enum and bailed on the first miss:

    val t = TLVType.from(data[off].toUByte()) ?: return null

So a file packet carrying one tag this build does not know is not
partially understood — it is discarded whole, media included. The
receiver shows nothing and logs nothing; the sender sees a successful
transfer. Both apps look healthy.

iOS has always skipped unknown tags (`case nil: continue` in its own
`BitchatFilePacket.decode`), so the two implementations disagreed about
what a valid packet is, and the tag list stopped being extensible in
practice: any optional field added by a newer or third-party client
costs every Android peer the whole file rather than just that field.

Unknown tags now advance past the value, exactly as iOS does. Known-tag
handling, the 4-byte CONTENT length, multi-CONTENT concatenation and
every existing rejection are unchanged.

The skip path is deliberately allocation- and log-free per TLV, because
its iteration count is chosen by the sender: a zero-length unknown TLV
costs 3 bytes, so a padded packet would otherwise mean millions of empty
array copies and formatted log lines monopolising the mesh handler. The
count is reported once after the loop instead.

Tests: `decode should skip unknown TLV types instead of dropping the
whole file` (extension before CONTENT), `decode should skip an unknown
TLV that trails the content` (after it), and `decode should handle a
packet padded with many zero-length unknown TLVs` (200k of them). The
first two fail on the old decoder.
2026-07-30 00:11:18 +02:00
Moe Hamade
3562ad10d5 fix(wifi-aware): count hotspot radio holds and never drop a restart request
Two fixes to how Wi-Fi Aware yields the radio to the Wi-Fi Direct hotspot.

A restart request could be swallowed: restartIfStillEnabled() coalesces on
an in-flight flag, so a request arriving while an earlier loop was still
burning attempts against the hotspot hold lost the CAS and was dropped.
The loop then exhausted its attempts without ever seeing the cleared hold,
leaving the mesh down despite the user's setting. Requests are now recorded
before coalescing and re-checked after each pass.

The hold itself was a single flag, so overlapping share sessions could
release each other's claim. It is now a counted, once-releasable
HotspotLease: Aware restarts only when no session still needs the radio,
and a duplicate or late release is a no-op. Publication of a started
service is checked against the hold inside the same lock stop() takes, so
a start racing a new hold cannot resurrect NAN while the hotspot owns the
radio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 00:50:36 +03:00
a1denvalu3
9f0223591f Fix stale peer lifecycle cleanup 2026-07-29 23:15:01 +02:00
callebtc
97d6e8a479
Merge pull request #825 from permissionlesstech/codex/network-tab-list-cleanup
Polish Network list badges and previews
2026-07-29 22:32:04 +02:00
callebtc
1774414f2e
Merge pull request #819 from spradhan7656/main
android 10 or below chat ui input field padding issue
2026-07-29 22:17:16 +02:00
callebtc
a5dfe9fe42 Polish network conversation rows 2026-07-29 21:56:12 +02:00
callebtc
2176160ac0
Merge pull request #823 from permissionlesstech/ui/geohash-globe-picker
feat: animated 3D globe geohash picker with borders and cities
2026-07-29 21:55:07 +02:00
callebtc
57cc39dbdf Merge main into fix/766-header-location-control 2026-07-29 20:52:00 +02:00