add web session indicator to profile card, clean up cookie extraction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tommy Chow 2026-03-08 18:40:43 -07:00
parent f27de6dc3d
commit d048875490
4 changed files with 54 additions and 10 deletions

View file

@ -11,7 +11,6 @@ import 'package:frosty/models/stream.dart';
import 'package:frosty/screens/channel/video/video_player_interface.dart'; import 'package:frosty/screens/channel/video/video_player_interface.dart';
import 'package:frosty/screens/settings/stores/auth_store.dart'; import 'package:frosty/screens/settings/stores/auth_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart'; import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/services/cookie_extractor.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -102,10 +101,8 @@ abstract class NativeVideoStoreBase with Store implements VideoPlayerInterface {
@action @action
Future<void> _initPlayer() async { Future<void> _initPlayer() async {
try { try {
// Prefer web cookie token (works with web Client-ID) for ad-free playback. // Use web cookie token (works with web Client-ID) for ad-free playback.
// Fall back to direct cookie extraction if AuthStore hasn't resolved it yet. final authToken = authStore.gqlToken;
var authToken = authStore.gqlToken;
authToken ??= await CookieExtractor.extractTwitchAuthToken();
late final PlaybackAccessToken token; late final PlaybackAccessToken token;
try { try {
token = await twitchGqlApi.getPlaybackAccessToken( token = await twitchGqlApi.getPlaybackAccessToken(

View file

@ -39,13 +39,63 @@ class ProfileCard extends StatelessWidget {
); );
} }
if (authStore.isLoggedIn && authStore.user.details != null) { if (authStore.isLoggedIn && authStore.user.details != null) {
final hasToken = authStore.gqlToken != null;
return ListTile( return ListTile(
leading: ProfilePicture( leading: ProfilePicture(
userLogin: authStore.user.details!.login, userLogin: authStore.user.details!.login,
radius: 12, radius: 12,
), ),
title: Text(authStore.user.details!.displayName), title: Text(authStore.user.details!.displayName),
trailing: const Icon(Icons.chevron_right_rounded), trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
tooltip: hasToken
? 'Web session linked'
: 'Web session not linked',
icon: Icon(
hasToken
? Icons.check_circle_outline_rounded
: Icons.link_off_rounded,
size: 20,
color: hasToken ? Colors.green : null,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Web session'),
content: Text(
hasToken
? 'Your Twitch web session is linked. When using the native player, ads will be avoided on channels where you have a subscription or Twitch Turbo.'
: 'Your Twitch web session is not linked. Log in again to avoid ads when using the native player on channels where you have a subscription or Twitch Turbo.',
),
actions: [
if (!hasToken)
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginWebView(),
),
);
},
child: const Text('Log in'),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
},
),
const Icon(Icons.chevron_right_rounded),
],
),
onTap: () => _showAccountOptionsModalBottomSheet(context), onTap: () => _showAccountOptionsModalBottomSheet(context),
); );
} }

View file

@ -275,9 +275,6 @@ abstract class AuthBase with Store {
} }
} }
// Try extracting a fresh GQL token from the WebView cookie store.
if (_isLoggedIn) await _extractGqlToken();
FirebaseCrashlytics.instance.setCustomKey('is_logged_in', _isLoggedIn); FirebaseCrashlytics.instance.setCustomKey('is_logged_in', _isLoggedIn);
_error = null; _error = null;
} catch (e) { } catch (e) {

View file

@ -28,7 +28,7 @@ class VideoSettings extends StatelessWidget {
SettingsListSwitch( SettingsListSwitch(
title: 'Native player (experimental)', title: 'Native player (experimental)',
subtitle: const Text( subtitle: const Text(
'Uses a native video player with improved picture-in-picture.', 'Native video player with auto picture-in-picture and quality selection.',
), ),
value: settingsStore.useNativePlayer, value: settingsStore.useNativePlayer,
onChanged: (newValue) => onChanged: (newValue) =>