Commit graph

909 commits

Author SHA1 Message Date
Francis Lavoie
b8648426f8
fix(video): keep native player alive across rotation (#575)
Wrap the native player in a GlobalKey so an orientation change re-parents
the existing platform view instead of tearing it down and reloading the
stream.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:23:26 -04:00
Tommy Chow
ef1b1ded5d fix: reclassify transient errors as non-fatal and fix top crashes
- main: record transient I/O, plugin-teardown, and non-crashing UI errors
  (network/websocket/timeout, MissingPluginException, image-load, layout
  overflow) as non-fatal, and add a grouping reason to async errors so
  distinct errors stop collapsing into one catch-all Crashlytics issue
- irc: guard IRCMessage.fromString against malformed/truncated lines that
  threw RangeError, with regression tests
- chat: wrap the emote menu in OverflowBox so it no longer overflows
  (RenderFlex) while the panel collapses
- chat: fix onBadgeError returning a List instead of a Map, which threw a
  Future.catchError type error during channel asset loads
- video: skip setAutoPipMode on Android < 12 where it isn't implemented

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 15:04:11 -04:00
Tommy Chow
f2b932bc93
feat(video,chat): native player, ad-break awareness, chat send/delay reliability, and tab redesign (#567)
Squash-merge commit. Covers four areas: native video player, chat reliability, multi-tab, and hardening.

## Native video player
- Direct HLS playback via `better_native_video_player` (forked from `plug-and-pay/flutter-native-video-player`, patched on `frosty-patches` branch)
- Twitch GQL playback token fetching with platform cookie extraction (iOS Swift + Android Kotlin plugins) for authenticated streams
- Quality selection (source, 720p, 480p, 360p, 160p, audio-only) persisted per channel
- Picture-in-picture: iOS automatic inline PiP, Android auto-PiP on API 31+ guarded against older devices
- Latency polling, chat latency sync, stall detection/recovery, startup auto-pause recovery, watchdog timers
- **Twitch stitched-ad break detection** (HLS `EXT-X-DATERANGE`) on both platforms, emitting `adBreakChanged` so the host suspends latency/stall recovery and chat sync during ads (program-date-time is unreliable then). Matches `CLASS="twitch-stitched-ad"` / `ID="stitched-ad-"` **and** the `X-TV-TWITCH-AD-*` attribute prefix (the durable signal streamlink/yt-dlp use)
- Shared `VideoPlayerInterface` so WebView and native paths are interchangeable via settings toggle
- `PlaybackAccessToken` model, `TwitchGqlApi` client

## Chat delay & send reliability
- **Optimistic send-finalize**: Twitch acks a sent message with a per-PRIVMSG USERSTATE (not an echo), which it can intermittently drop — more so under a long chat delay. A missing ack no longer shows a false "message may not have been sent" error; the send is finalized optimistically (local echo rendered), and only genuine rejection NOTICEs surface an error. Ack timeout scaled by the chat delay.
- **Per-line chat-delay bypass**: control/state lines (USERSTATE/ROOMSTATE/GLOBALUSERSTATE), rejection NOTICEs, the reconnect notice, and untagged server lines bypass the delay so send-acks and PONGs aren't stalled; only displayable chat content is delayed, staying in sync with delayed video.
- **Measured chat bottom-bar height**: the message list reflows to the composer's real measured height, fixing the input overlapping messages when a long message expands it (replaces a fixed-constant estimate).
- IRC tag parsing handles valueless and escaped tags per the IRCv3 message-tags spec (fixes a recent-messages RangeError on certain tags).

## Chat / multi-tab
- Redesigned tab chips: unread dot indicator, chips dim until WebSocket connects
- Animated tab options popover replacing `MenuAnchor` (no animation hook in Flutter 3.44)
- Simplified `ChatTabs` from `StatefulWidget` to `StatelessWidget`
- Focus-current-channel fade bump to 0.55; section-header alpha lifted to match the muted-label tier
- `addTab` now calls `setActiveTab` to snapshot outgoing tab's `lastSeenMessageId` — fixes spurious unread dots after adding a tab
- `onActivateSourceTab` threaded into `ChatUserModal` so Reply in merged mode switches to the correct source tab's composer

## Bug fixes and hardening
- **Reverted the experimental iOS low-latency mode** (5s live-edge join + 1.05x catch-up): AVPlayer can't sustain <~18s on Twitch (declared `TARGETDURATION:6` paces playlist refreshes), and the offset starved the buffer into pause-on-launch. Default join distance restored.
- Fork (Android): **cap `BEHIND_LIVE_WINDOW` recovery** so a stream repeatedly falling off the live window can't silently re-prepare forever; emits `error` on exhaustion so the host recovery ladder takes over
- Guard all Android `setAutoPipMode` calls with `_autoPipAvailable` — prevents `PlatformException` on Android 8–11; check `_disposed` after `isAutoPipAvailable` await to prevent post-dispose auto-PiP re-enable
- Remove `_isInPipMode` guard from `_onPaused` so `_paused` tracks actual state, fixing PiP play/pause toggle getting stuck
- Tighten iOS cookie domain check to exact `twitch.tv` / `.twitch.tv` suffix
- Invalidate the stored web session token when Twitch rejects it with a 401 (profile card flips back to unlinked; future loads skip the doomed authenticated request), and re-extract the cookie on launch when logged in without a linked session
- Send `play_session_id` and `player_backend=mediaplayer` to Usher, matching the web client / streamlink
- Fork (iOS): restore program-date-time latency measurement (a prior rev measured distance to the manifest edge, reading ~0s and silently disabling high-latency recovery / chat-delay auto-sync); release platform views on dispose (per-stream view + AVPlayerViewController leak), track the observed AVPlayerItem for KVO removal (sibling-view crash), main-thread seek results, resolve superseded load futures
- Fork (Android): clean up method handlers orphaned by PiP-time view disposal (leaked audio-focus listeners), drop the live-offset re-anchoring seekTo on metadata updates (latency creep), restore pre-duck volume, respect user pause in the stall watchdog, resolve superseded load futures
- Deep review pass: expire the quality-switch flag so stall recovery isn't disabled for the session, cap latency-driven recovery (clock-skew loop), generation-guard the error overlay, two-strike offline confirmation, controller null-safety on quality switch
- Tighten `/me` local-echo match (no longer renders `/meow ...` as an action); clear pending send text on disconnect

## Web / docs
- Remove stale Cloudflare Images binding from `wrangler.jsonc` (covered by `images.unoptimized: true`)
- Correct `web/AGENTS.md` Cache Components claim (feature not enabled)
- Repo and agent config cleanup

## Test plan
- [x] `flutter analyze` — clean (frosty + plugin lib)
- [x] `flutter test` — 319/319 pass (adds `ChatLatencySync`, `StreamInfoPoller`, video timing invariants, `PlaybackAccessToken`, IRC tag parsing)
- [x] Android plugin Kotlin compiles against the bumped fork rev
- [ ] Native player plays live streams on iOS and Android
- [ ] Quality switching works and persists across channel reopens
- [ ] PiP enters/exits correctly; pause/resume toggle in PiP window works
- [ ] Audio-only mode does not crash on Android < API 31
- [ ] Sending under a long synced chat delay no longer shows a false "may not have been sent" error
- [ ] Long composer text reflows the message list (no input/message overlap)
- [ ] Multi-tab: adding a tab does not mark previous tab as unread
- [ ] Merged chat: Reply from user modal targets the correct tab's composer

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-13 17:06:24 -04:00
Tommy Chow
f5718e3656 fix(chat): restore channel assets when leaving shared chat
When a shared chat session ended, the participants' merged emotes and badges
stayed in the channel asset maps, so the other streamer's emote set and badges
kept showing (#522). Only the enter transition was handled.

Detect the leave transition (was shared, no longer shared) and reset: clear the
loaded-participant tracking and re-run getAssets(), which replaces the channel
emote/badge maps with this channel's own assets. Guarded on having actually
loaded shared assets so a flapping source-room-id tag can't cause redundant
refetches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:42:37 -07:00
Tommy Chow
d2b4f0f39e chore(chat): migrate reorderable tab list to onReorderItem for flutter 3.44
ReorderableListView's onReorder was deprecated after Flutter 3.41 and
fails flutter analyze on the CI's pinned 3.44.1 toolchain. Switch to
onReorderItem, which adjusts newIndex for the removed item itself, and
drop the now-redundant manual off-by-one correction in reorderTab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:01:47 -07:00
Tommy Chow
c27a37d52f fix recent messages by handling valueless and escaped irc tags
The recent-messages API now serializes empty IRCv3 tags as bare keys
(e.g. `emotes`) instead of `emotes=`, which threw a RangeError in the
tag parser and silently dropped every recent message. Parse tags per the
IRCv3 spec: treat bare and empty values as equivalent, split on the first
`=` so values may contain `=`, and unescape values in a single pass
(\: \s \ \r \n). Also guard user extraction against prefixes without
`!`, and isolate per-message parse failures in _handleIRCData so one bad
message can no longer abort an entire batch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:53:53 -07:00
Tommy Chow
c58c9a9a25 guard crashlytics call in error handler for test environments
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 20:55:59 -07:00
Tommy Chow
5be231da73 fix landscape safe area padding across settings and list screens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 20:04:00 -07:00
Tommy Chow
1dab572280 remove redundant edge borders where blur effect provides separation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:48:14 -07:00
Tommy Chow
872362d5a6 fix recent searches not saving from type-to-search
History was only saved via handleQuery (keyboard submit), but
type-to-search calls _performSearch directly through the debounce
timer, bypassing history. Move the save into _performSearch so all
search paths record history.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:33:17 -07:00
Tommy Chow
6ede000c66 improve Firebase integration across Crashlytics, Analytics, and Performance
- Fix cold-start privacy bug: apply persisted collection preference on launch
- Disable Firebase collection in debug builds to avoid polluting prod data
- Add Crashlytics custom keys (is_logged_in, channel), user identifier, and
  WebSocket reconnection breadcrumbs for actionable crash reports
- Record non-fatal errors for server failures, WebSocket errors, and global
  asset fetch failures (skip timeouts/network errors to avoid noise)
- Update is_logged_in key and user identifier on login/logout transitions
- Log auth reconnection exhaustion before forced logout
- Add FirebaseAnalyticsObserver with named routes for automatic screen tracking
- Add static routeName constants to avoid duplicated string literals
- Add Firebase Performance Dio interceptor for automatic HTTP request tracing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 00:18:21 -08:00
Tommy Chow
f07fe621bc fix stale fullscreen button icon and system UI mode not reacting to settings
Wrap fullScreenButton in Observer so icon/tooltip update immediately
when toggling fullscreen. Move system UI immersive mode sync into the
videoChat Observer so it reacts to landscapeForceVerticalChat changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:46:41 -08:00
Tommy Chow
820e563516 upgrade dependencies and regenerate codegen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:42:51 -08:00
Tommy Chow
eb91146f63 fix auto sync chat delay not applying when toggled on mid-session
When showLatency was already tracking, toggling autoSyncChatDelay on
would wait up to 60s for the next tracker cycle instead of using the
cached latency value immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:34:01 -08:00
Tommy Chow
9cec89a67c fix video refresh race condition causing black screen with play button
Replace JS-side `window._injected` guard with a Dart-controlled `_needsInit`
flag so `initVideo()` reliably runs after every refresh. The previous approach
loaded `about:blank` then `videoUrl` back-to-back, but `loadRequest` resolves
on navigation start (not page load), so the JS context often survived and
`onPageFinished` skipped re-initialization. Also adds an explicit `play()`
call to handle cases where the Twitch player loads in a paused state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:22:44 -08:00
Tommy Chow
929d525c13 add error handling to getRecentMessage and getAssets in chat store
getRecentMessage had no try/catch, so a failure from the third-party
API would silently prevent connectToChat from ever being called.
getAssets was fire-and-forget without a catchError, risking uncaught
Future errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:46:40 -08:00
Tommy Chow
7a9af238b6 switch token script to implicit flow and fix interceptor hanging Future
The script used authorization code flow (response_type=code) which produces
tokens expiring in ~4 hours, while the app uses implicit flow (~60 days).
Switched the script to implicit flow to match, removing the client_secret
requirement and token exchange step.

Also fixed UnauthorizedInterceptor swallowing 401 errors without calling
handler.reject(), which left Dio request Futures hanging forever.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:37:23 -08:00
Tommy Chow
779572a864 replace redundant confirmation dialogs with inline actions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:58:34 -08:00
Tommy Chow
02f4cd417b audit and optimize haptic feedback across the app
Add selectionClick to settings switches, discrete slider steps, bottom
nav tabs, and expandable section headers. Fix intensity levels for cache
clear and random color (medium → light). Add lightImpact to thumbnail
long-press. Fix inconsistent search result long-press level. Replace
blanket lightImpact in updateNotification with targeted haptics at
error/warning call sites to eliminate double-firing and match severity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:46:24 -08:00
Tommy Chow
21cb79d2c2 move menu styling to theme for app-wide consistency
Add menuTheme and menuButtonTheme to ThemeData matching the existing
overlay surface style (12px radius, outline border, compact density).
Remove per-use styling from chat_tabs.dart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:30:02 -08:00
Tommy Chow
4c696745b0 add tab disconnect option via context menu
X button on secondary tabs now opens a MenuAnchor popup with
Disconnect (deactivates without removing) and Remove options.
Menu styled to match app overlay surfaces (12px radius, outline border).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:25:56 -08:00
Tommy Chow
89f8859ba3 add merged chat mode for multi-tab view
Merge only already-activated tabs; lazy tabs join when tapped. Dedup
messages by IRC id for Twitch shared chat overlap. Preserve draft and
reply state when switching tabs in merged mode. Merge button appears
only when 2+ tabs are loaded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:00:03 -08:00
Tommy Chow
ed59896c5f deduplicate SystemChrome calls and optimize PiP animation rebuilds
- extract _scheduleSystemUiModeSync to avoid redundant platform calls on
  every build, deferred via addPostFrameCallback with state tracking
- use AnimatedBuilder.child so the video subtree is not rebuilt on every
  animation tick during PiP spring-back
- move orientation reset from build() to initState() in home screen
- reformat landscape LayoutBuilder for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:13:08 -08:00
Tommy Chow
d28a3279e4 replace per-message Future.delayed with lightweight callback queue
drains delayed chat/7TV callbacks via the existing 200ms message buffer
timer instead of creating one Future.delayed per incoming event, removing
the biggest memory-pressure risk on busy channels with chat delay active

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:11:42 -08:00
Tommy Chow
9e81b2d80a make auto-synced chat delay session-only and improve video store
- split chatDelay into persisted (manual) and session-only (synced) fields
  so auto-synced latency values don't carry over between channels
- make latency tracker singleton/idempotent to prevent overlapping JS pollers
- add isCycling guard to prevent re-entrant latency cycles
- deduplicate concurrent stream info API requests
- tie JS cleanup timer to video mode only (skip in chat-only)
- update UI to reflect effective delay instead of persisted value

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:11:21 -08:00
Tommy Chow
04e5479acd add keep screen awake setting
adds a user-controlled toggle (default on) under video settings that
controls whether wakelock is enabled during channel sessions. previously
wakelock was unconditional with no way to turn it off.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:58:32 -08:00
Tommy Chow
3b3cd92026 show buy me a coffee icon on iOS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:45:24 -08:00
Tommy Chow
4ff331ec98 replace about dialog and FAQ tile with bottom-pinned author and version footer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:31:32 -08:00
Tommy Chow
d500119219 move clear recent emotes action to tab bar icon button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 20:40:33 -08:00
Tommy Chow
93c7b5b637 remove landscape top-edge gesture barrier causing black bar on iPad
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:28:48 -08:00
Tommy Chow
2e9d0bd16a extract kCardBorderRadius constant for card thumbnails
Replace 13 occurrences of BorderRadius.all(Radius.circular(8)) with a
shared kCardBorderRadius constant in constants.dart. All uses represent
the same design token: card/thumbnail corner radius across stream cards,
category cards, and their skeleton loader placeholders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:01:58 -08:00
Tommy Chow
96bce87a0e add FrostyColors ThemeExtension, align hardcoded colors with M3 roles
Introduce FrostyColors ThemeExtension with 7 semantic color fields
(highlightedMessage, emoteOnly, followersOnly, slowMode, subOnly,
uniqueMode, overlayOnSurface) and brightness-aware light/dark variants.

Replace hardcoded colors across 15 files:
- Links use colorScheme.primary instead of Colors.blue
- Mentions use colorScheme.error instead of Colors.red
- First-time chatters share highlightedMessage (Twitch purple)
- Chat mode indicators use frostyColors fields
- Overlay text uses frostyColors.overlayOnSurface

Remove Provider<FrostyThemes> pattern from main.dart, channel.dart,
video_overlay.dart, and frosty_photo_view_dialog.dart.

Replace manual onSurface.withValues(alpha: 0.6) with M3's built-in
onSurfaceVariant for secondary text in section headers, chat modes,
alerts, loading indicators, and input hints. Fix double-muted
onSurfaceVariant.withValues(alpha: 0.6) in navigation icons and
notification dismiss button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:01:44 -08:00
Tommy Chow
9119a69b17 fix 7TV emote mutations on computed map, fix search store disposer leak, fix typo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:34:49 -08:00
Tommy Chow
f9302b0d82 replace BackdropFilter with solid container on stream card badge, remove AnimatedContainer from BlurredContainer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:13:35 -08:00
Tommy Chow
467dca9b86 upgrade dependencies, fix null-aware element lint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:13:35 -08:00
Tommy Chow
a0b46eaf3d fix overlay toggle stream reload, fix spring-back animation listener
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:19:13 -08:00
Tommy Chow
a3b8ac3237 clean up and harden webview video JS infrastructure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:19:02 -08:00
Tommy Chow
93cd39c99f consolidate overlay shadow constants, simplify overlay gradients
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:18:54 -08:00
Tommy Chow
9306d9c48d fix useragent breaking login on ios 2026-01-09 02:14:52 -05:00
Tommy Chow
260348736d fix landscape bottom padding in chat bottom bar 2026-01-09 01:47:02 -05:00
Tommy Chow
f383c447f5 revert auto-fill safe area 2026-01-09 01:04:09 -05:00
Tommy Chow
5963a0ab3c fix draggable divider still active in fullscreen
fixes #486
2026-01-08 20:49:13 -05:00
Tommy Chow
9aea491966 fix chat text shadows causing frame drops 2026-01-08 19:12:34 -05:00
Tommy Chow
549a93b495 actually fix safe area filll resetting when moving to portrait 2026-01-08 17:24:30 -05:00
Tommy Chow
bde322fd09 fix safe area filll resetting when moving to portrait 2026-01-08 16:30:22 -05:00
Tommy Chow
ba7f68af1e fix google log in 2026-01-08 01:42:11 -05:00
Tommy Chow
487700f5b7 add device orientation detection for optimal landscape rotation 2026-01-08 00:06:18 -05:00
Tommy Chow
50c9bb2387 add native device orientation support, auto-fill safe area depending on orientation 2026-01-07 23:54:11 -05:00
Tommy Chow
57671967c9 fix ios landscape bottom safe area 2026-01-07 00:31:42 -05:00
Tommy Chow
82ab13b517 add swipe pip gesture support in landscape 2026-01-07 00:31:40 -05:00