- Dart 95.6%
- C++ 1.4%
- CMake 1%
- Shell 0.5%
- Swift 0.4%
- Other 1%
Added these new features: 1. You can click on a read receipt in a group chat / room to see the profile of the user who has read your message. 2. Ctrl+- and Ctrl++ decrease and increase font size respectively when used in a room. Makes use of and changes the existing "font size" setting in the style settings menu. 3. Fixed a bug where expandable text sections would collapse when scrolling, even if previously expanded. There is currently a bug from previous versions where .djvu and some other file types will be parsed as an image and sent with a thumbnail. I attempted to fix this bug, but failed, and have since removed these changes in this branch. The three features I have listed here are tested and working just fine. Reviewed-on: #4 Co-authored-by: Algebraity <algebraity@tutamail.com> Co-committed-by: Algebraity <algebraity@tutamail.com> |
||
|---|---|---|
| .github | ||
| .vscode | ||
| android | ||
| assets | ||
| integration_test | ||
| ios | ||
| lib | ||
| linux | ||
| macos | ||
| scripts | ||
| snap | ||
| test | ||
| web | ||
| windows | ||
| .gitignore | ||
| .mailmap | ||
| .metadata | ||
| .tool_versions.yaml | ||
| analysis_options.yaml | ||
| AUTHORS.md | ||
| CHANGELOG.md | ||
| config.sample.json | ||
| CONTRIBUTING.md | ||
| dart_dependency_validator.yaml | ||
| devtools_options.yaml | ||
| Dockerfile | ||
| l10n.yaml | ||
| LICENSE | ||
| licenses.yaml | ||
| PRIVACY.md | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
| recommended_homeservers.json | ||
| SECURITY.md | ||
A Matrix chat client focused on local privacy, per-chat control, send reliability, and cross-device consistency. Derived from FluffyChat. Written in Flutter.
Overview
Mochi is a Matrix client forked from FluffyChat. It is protocol-compatible with every other Matrix client (Element, Nheko, NeoChat, Cinny, and others) and inherits FluffyChat's full feature surface. The fork diverges substantially from upstream on defaults, per-conversation control, message-send reliability, sticker and emoji management, and cross-device state. None of the additions break interop: every device-to-device mechanism rides on standard Matrix room or account data, which other clients ignore silently.
The canonical source of the upstream project is krille-chan/fluffychat. Upstream-compatible changes that are useful outside Mochi's opinionated scope are encouraged to be contributed back.
Differences from upstream FluffyChat
Security and local privacy
- App Lock. PIN, passphrase, or 3×3 pattern. Variable-length PIN with a length-hidden glyph during entry. Optional scrambled numeric keypad. Configurable auto-lock timeouts. Biometric unlock with enrollment-change detection via
local_auth.FLAG_SECUREscreenshot protection (Android). Opt-in wipe or sign-out after a configurable number of failed attempts. - Database key derivation from the lock secret. When enabled (default on), the SQLCipher database key is wrapped by an Argon2id-derived KEK; the on-disk database is not decryptable without unlocking the app. Device-local, never synced.
- Opt-in link previews. OpenGraph card previews and direct-media auto-load are separate systems, both off by default, both gated per-domain. Media URLs only render inline when the source domain is in
chat.mochi.trusted_media_domains; OpenGraph cards can be suppressed per-domain viachat.mochi.dismissed_embed_domainswith selectable expiry (1 day through forever, auto-expiring). A subdomain-level domain trust takes precedence over a parent-domain dismissal, so whitelistingmedia.tenor.comdoes not get silenced by dismissingtenor.com. - Opt-in media auto-download. Attachment auto-loading can be disabled globally or per room; a tap-to-load placeholder replaces the eager fetch.
Conversational control
- Per-chat preferences. Read receipts, typing indicators, URL previews, OpenGraph previews, attachment auto-load, autoplay, member-change visibility, and reaction visibility can each be overridden per room via
im.mochi.chat_prefsroom account data. Invisible to other room members; roams across the user's own devices. - Per-key cross-device sync opt-out. The synced subset of application settings (stored in
im.mochi.settingsaccount data) can be included or excluded on a per-key, per-device basis. The opt-out list is itself device-local to prevent self-defeating overwrites. - Timed mute. Per-room mute with preset durations (15 minutes, 1 hour, 8 hours, 24 hours, 1 week, forever). The unmute timestamp is written to
im.mochi.mute_untilroom account data, so every logged-in device decides independently whether the mute is still active from local time alone. No homeserver round-trip is needed once the timestamp is known; expired mutes auto-clear on startup and on each sync. - Pinned spaces. Discord-style navigation rail with pinned spaces synchronized cross-device via
chat.mochi.pinned_spacesaccount data. - Chat list ordering and default tab. Default tab (pinned, favorites, direct, tag, filter) is user-configurable. Drag-to-reorder within each tab persists per-filter; room order roams across devices via
im.mochi.chat_order.
Message send reliability
- Persistent outbound queue. Outgoing text, edits, stickers, and emoji reactions pass through a per-room queue stored in
im.mochi.outbound_queueroom account data. The queue survives app restart, network loss, and device handoff. Failed sends do not block the tail: after a capped backoff sequence (2s, 10s, 30s, 2m, 5m) the entry is marked failed and the pump advances, so a single bad message cannot stall the queue behind it. - Cross-device send coordination. A short-lived lease (
claim) in each queue entry prevents two devices from dispatching the same message twice. If the holding device goes offline, the lease expires after 60 seconds and another device takes over, re-dispatching with the same transaction ID so the homeserver deduplicates. - Interactive ghost bubbles. A pending or failed entry renders as a ghost bubble in place of the eventual message. Tap a failed ghost to retry; long-press to cancel (the cancel propagates to every device). Bubbles show "pending on
<device>" when another device is holding the lease.
Content and media
- Unified emoji picker. One scroll surface with recently-used (unicode and custom emoji mixed), the room's custom emoji organized by pack, and the standard unicode categories, with a sticky nav bar that jump-scrolls between sections. Stickers remain on a separate tab.
- Signal-style sticker picker. Single vertical scroll with a sticky pack-icon bar. Density presets (small / medium / large columns). Long-press preview for animated stickers. Frequency-weighted recently-used surface shared with the emoji reaction bar.
- Multi-pack sticker management. Multiple user-owned sticker packs via
im.ponies.user_emotes~<slug>account data alongside the default pack. Room-pack discovery banner when entering a room advertisingim.ponies.room_emotes..mxpacktransport format (ZIP archive withmimetype,pack.jsonmanifest,README.txt, and rawstickers/directory) with graceful degradation on clients that do not implement the format. - Copy-to-your-pack. Long-press any sticker in the picker, any sticker in a message, any custom emoji tile, or any custom emoji reaction to save it into one of your user packs. Shortcodes collide-safely (
:smile:becomes:smile_2:if already taken). The MXC URL is reused directly, so no re-upload is needed when copying from another user on the same homeserver. - GIF decoder fallback. When the platform codec fails on an animated GIF (commonly: frames with unusual disposal or sub-rect layouts, such as those produced by ezgif-style video-to-GIF tools), Mochi transparently transcodes the file to APNG on first decode using the pure-Dart
imagepackage and renders the APNG instead. Animation is preserved; the transcode is cached per image. - Video first-frame thumbnails. Generated from a partial-byte download of the source video (256 KB) to avoid full-file retrieval on scroll. A privacy blur is applied by default and fades to zero on hover (desktop) or on tap (mobile).
- GIF favorites. Persisted to Matrix account data with both the source URL and an MXC upload, indexed by SHA-256 so favorites survive source-URL rot. Smart send prefers a probe-alive source URL over re-uploading; a 5-minute probe cache avoids re-fetching on every send.
- Body-only-media collapse. When a text message's body is nothing but a media URL, the text row is collapsed entirely and the embed fills the bubble. HTML entity decoding is applied before URL matching so CDN URLs with
&-encoded query strings are detected correctly.
Editor and composer
- Slash-command autocomplete with argument hints. The suggestion row shows the expected signature (
/invite <user>,/op <user> [power-level]) alongside the localized description. - Frequency-boosted emoji and emote suggestions. The
:shortcodeautocomplete ranks by usage frequency, tracked via a single store (PickerHistory) shared across the emoji picker, the reaction quick-bar, and the custom emoji picker, so every surface converges on the same recents. - Custom emoji insertion. The composition picker surfaces every custom emoji from the active room's packs and inserts
:shortcode:at the cursor. Frequency feeds back into the picker's recents. - Escape dismisses the autocomplete overlay. Previously a silent no-op.
Reactions
- Quick-reaction bar. The hover / long-press reaction bar is frequency-ranked (top five usages, with a fallback set used when fewer than three are tracked). A custom list is configurable via
chat.mochi.quick_reactions. - Shared frequency pool. Unicode emoji, custom emoji (reaction or insertion), and stickers all write into the same
PickerHistorystore, so usage in any surface influences recents everywhere. Usage is weightedcount * 2 + recency_bonus(bonus decays from 5 to 0 over 30 days). - Tooltips on sticker and custom-emoji surfaces. Hovering a sticker inside a message or a custom-emoji reaction chip reveals its
:shortcode:. Unread-count badges surface correctly for rooms that have mentions but zero regular notifications (an upstream edge case that hid the badge entirely).
Other
- Self Notes. Auto-pinned encrypted DM to the user, tagged
im.mochi.self_notes, with a first-run onboarding tile in the chat list. - Advanced-settings gate. A single "Show advanced" toggle reveals platform-irrelevant and experimental options. Default-hidden options are no longer scattered behind ad-hoc heuristics.
- Data migration from FluffyChat. On first launch, Mochi copies legacy
chat.fluffy.*local preferences andim.fluffychat.*account-data events into their Mochi equivalents; settings and bundles survive the rename. Guarded against running on accounts already using Mochi.
Inherited feature surface
End-to-end encryption (E2EE by default for direct chats), cross-signing, emoji verification, encrypted key backup, multi-account, SSO / OIDC, federation, spaces, public channels with thousands of participants, all standard Matrix message types (text, image, video, audio, file, location), voice messages, Firebase Cloud Messaging and UnifiedPush push backends, Material You theming, dark mode, and onboarding via Matrix ID or QR code.
Refer to the upstream FluffyChat README for additional context on inherited functionality.
License and attribution
Mochi is distributed under the GNU Affero General Public License v3 (inherited from FluffyChat). The license file is at LICENSE.
Every contributor to FluffyChat prior to the fork is preserved in the initial commit via Co-authored-by trailers and listed in AUTHORS.md. Copyright notices embedded in platform project files (macOS PRODUCT_COPYRIGHT, native source headers) remain attributed to the original authors, as required by the license and as a matter of accuracy.
The app icon and banner assets derive from artwork originally commissioned by the FluffyChat project from Fabiyamada. The emoji verification glyph set is maintained by The Matrix Foundation and licensed under Apache 2.0. Notification sound assets are from WoodenBeaver.
Build instructions
Mochi builds using standard Flutter tooling. Vodozemac (Rust) is required for encryption support on web; cargo must be installed in that case.
git clone https://git.keimai.space/alex/Mochi.git
cd Mochi
flutter pub get
Linux
sudo apt install libjsoncpp1 libsecret-1-dev libsecret-1-0 librhash0 libwebkit2gtk-4.0-dev lld
flutter build linux --release
Android
flutter build apk --release
Firebase Cloud Messaging may be enabled before build by running ./scripts/add-firebase-messaging.sh and providing a google-services.json for the Mochi bundle ID (im.mochi.app). Without Firebase, builds fall back to UnifiedPush.
iOS and iPadOS
A Mac with Xcode and code-signing configured is required. The Mochi bundle identifier is im.mochi.app. Build via ./scripts/build-ios.sh; see the script for expected MOCHI_* environment variables.
macOS
flutter build macos --release
Windows
flutter build windows --release
Web
./scripts/prepare-web.sh
flutter build web --release
Runtime configuration may be supplied via a config.json served from the same origin as the application. See config.sample.json for the recognised keys.
Integration testing
Integration tests require Docker. A Synapse fixture is bootstrapped by ./scripts/prepare_integration_test.sh prior to each run.
./scripts/prepare_integration_test.sh
flutter test integration_test/mobile_test.dart
Contributing
Issues and merge requests are accepted at the project's repository. Mochi is an opinionated fork, and behavioural changes that diverge from upstream FluffyChat are appropriate here. Changes that are protocol-agnostic or broadly applicable are encouraged to be contributed to upstream FluffyChat as well.
See CONTRIBUTING.md for style and review guidelines.
Security
Security issues may be reported privately in accordance with SECURITY.md. Please do not file public issues for undisclosed vulnerabilities affecting either Mochi or upstream FluffyChat.