mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: remove redundant profile link

+87 -44
+43 -42
lib/core/router/app_shell.dart
··· 1 1 import 'package:flutter/material.dart'; 2 2 import 'package:flutter_bloc/flutter_bloc.dart'; 3 3 import 'package:go_router/go_router.dart'; 4 + import 'package:lazurite/features/account/presentation/account_switcher_sheet.dart'; 4 5 import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; 5 6 import 'package:lazurite/features/connectivity/connectivity_helpers.dart'; 6 7 import 'package:lazurite/features/connectivity/cubit/connectivity_cubit.dart'; ··· 37 38 38 39 final StatefulNavigationShell navigationShell; 39 40 40 - /// Global key for the shell [Scaffold]. Accessible from anywhere — even 41 + /// Global key for the shell [Scaffold]. Accessible from any screen, even 41 42 /// screens pushed onto the root navigator that are outside [AppShellScope]. 42 43 static final scaffoldKey = GlobalKey<ScaffoldState>(); 43 44 ··· 171 172 ], 172 173 ), 173 174 ), 174 - Padding( 175 - padding: const EdgeInsets.fromLTRB(12, 0, 12, 12), 176 - child: InkWell( 177 - borderRadius: BorderRadius.circular(20), 178 - onTap: () => _runAfterClose(context, () => navigationShell.goBranch(3, initialLocation: false)), 179 - child: Ink( 180 - padding: const EdgeInsets.all(12), 181 - decoration: BoxDecoration( 182 - color: theme.colorScheme.surfaceContainerHigh, 183 - borderRadius: BorderRadius.circular(20), 184 - ), 185 - child: Row( 186 - children: [ 187 - _MenuProfileAvatar(did: did, initials: initials), 188 - const SizedBox(width: 12), 189 - Expanded( 190 - child: Column( 191 - crossAxisAlignment: CrossAxisAlignment.start, 192 - children: [ 193 - Text( 194 - displayName, 195 - maxLines: 1, 196 - overflow: TextOverflow.ellipsis, 197 - style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700), 198 - ), 199 - const SizedBox(height: 2), 200 - Text( 201 - '@$handle', 202 - maxLines: 1, 203 - overflow: TextOverflow.ellipsis, 204 - style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurfaceVariant), 205 - ), 206 - ], 207 - ), 208 - ), 209 - const Icon(Icons.chevron_right), 210 - ], 211 - ), 212 - ), 213 - ), 214 - ), 175 + _buildProfileTag(context, displayName, handle, initials, did), 215 176 Expanded( 216 177 child: ListView( 217 178 padding: const EdgeInsets.symmetric(horizontal: 8), ··· 284 245 ], 285 246 ), 286 247 ), 248 + ], 249 + ), 250 + ), 251 + ), 252 + ); 253 + } 254 + 255 + Widget _profileTag(ThemeData theme, String content, bool isLabel) { 256 + final style = isLabel 257 + ? theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700) 258 + : theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurfaceVariant); 259 + return Text(content, maxLines: 1, overflow: TextOverflow.ellipsis, style: style); 260 + } 261 + 262 + Widget _buildProfileTag(BuildContext context, String displayName, String handle, String initials, String? did) { 263 + final theme = Theme.of(context); 264 + final deco = BoxDecoration(color: theme.colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(20)); 265 + return Padding( 266 + padding: const EdgeInsets.fromLTRB(12, 0, 12, 12), 267 + child: InkWell( 268 + borderRadius: BorderRadius.circular(20), 269 + onTap: () => _runAfterClose(context, () => showAccountSwitcherSheet(rootContext)), 270 + child: Ink( 271 + padding: const EdgeInsets.all(12), 272 + decoration: deco, 273 + child: Row( 274 + children: [ 275 + _MenuProfileAvatar(did: did, initials: initials), 276 + const SizedBox(width: 12), 277 + Expanded( 278 + child: Column( 279 + crossAxisAlignment: CrossAxisAlignment.start, 280 + children: [ 281 + _profileTag(theme, displayName, true), 282 + const SizedBox(height: 2), 283 + _profileTag(theme, '@$handle', false), 284 + ], 285 + ), 286 + ), 287 + const Icon(Icons.chevron_right), 287 288 ], 288 289 ), 289 290 ),
+24 -1
lib/features/account/presentation/account_switcher_sheet.dart
··· 23 23 24 24 @override 25 25 Widget build(BuildContext context) { 26 + final theme = Theme.of(context); 27 + final colorScheme = theme.colorScheme; 28 + final textTheme = theme.textTheme; 26 29 return SafeArea( 27 30 child: Column( 28 31 mainAxisSize: MainAxisSize.min, ··· 30 33 children: [ 31 34 Padding( 32 35 padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), 33 - child: Text('Accounts', style: Theme.of(context).textTheme.titleMedium), 36 + child: Text('Accounts', style: textTheme.titleMedium), 34 37 ), 35 38 const Divider(), 36 39 BlocBuilder<AccountSwitcherCubit, AccountSwitcherState>( ··· 41 44 child: Center(child: CircularProgressIndicator()), 42 45 ); 43 46 } 47 + 48 + if (state.accounts.isEmpty) return _buildEmptyState(colorScheme, textTheme); 44 49 45 50 return ListView.builder( 46 51 shrinkWrap: true, ··· 67 72 leading: const Icon(Icons.person_add_outlined), 68 73 title: const Text('Add Account'), 69 74 onTap: () => _onAddAccount(context), 75 + ), 76 + ], 77 + ), 78 + ); 79 + } 80 + 81 + Widget _buildEmptyState(ColorScheme colorScheme, TextTheme textTheme) { 82 + return Padding( 83 + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), 84 + child: Row( 85 + children: [ 86 + Icon(Icons.swap_horiz_outlined, color: colorScheme.onSurfaceVariant), 87 + const SizedBox(width: 12), 88 + Expanded( 89 + child: Text( 90 + 'No other signed-in accounts yet. Add an account to switch between profiles.', 91 + style: textTheme.bodyMedium?.copyWith(color: colorScheme.onSurfaceVariant), 92 + ), 70 93 ), 71 94 ], 72 95 ),
+17
test/core/router/app_router_test.dart
··· 246 246 expect(find.text('SETTINGS'), findsOneWidget); 247 247 }); 248 248 249 + testWidgets('drawer profile tag opens account switcher sheet', (tester) async { 250 + await tester.binding.setSurfaceSize(const Size(430, 932)); 251 + addTearDown(() => tester.binding.setSurfaceSize(null)); 252 + 253 + await tester.pumpWidget(buildSubject()); 254 + await tester.pumpAndSettle(); 255 + 256 + await tester.tap(find.byTooltip('Open menu')); 257 + await tester.pumpAndSettle(); 258 + 259 + await tester.tap(find.text('River Tam')); 260 + await tester.pumpAndSettle(); 261 + 262 + expect(find.text('Accounts'), findsOneWidget); 263 + expect(find.text('Add Account'), findsOneWidget); 264 + }); 265 + 249 266 testWidgets('tapping bottom nav tabs switches active branch', (tester) async { 250 267 await tester.binding.setSurfaceSize(const Size(430, 932)); 251 268 addTearDown(() => tester.binding.setSurfaceSize(null));
+3 -1
test/features/account/presentation/account_switcher_sheet_test.dart
··· 116 116 expect(find.byIcon(Icons.check), findsOneWidget); 117 117 }); 118 118 119 - testWidgets('shows Add Account tile', (tester) async { 119 + testWidgets('shows empty state and Add Account tile when there are no switchable accounts', (tester) async { 120 120 when(() => cubit.state).thenReturn(const AccountSwitcherState.ready(accounts: [])); 121 121 122 122 await openSheet(tester); 123 123 124 + expect(find.text('No other signed-in accounts yet. Add an account to switch between profiles.'), findsOneWidget); 125 + expect(find.byIcon(Icons.swap_horiz_outlined), findsOneWidget); 124 126 expect(find.text('Add Account'), findsOneWidget); 125 127 expect(find.byIcon(Icons.person_add_outlined), findsOneWidget); 126 128 });