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

Configure Feed

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

feat: add dev tools toggle to settings

+183 -8
+8 -8
doc/roadmap.txt
··· 34 34 - [x] Create /devtools route group (always available, not kDebugMode gated) 35 35 - [x] DevToolsHomePage: show DID, PDS host, quick actions 36 36 - [x] "Copy my DID" action 37 - - [ ] Add Settings → "Developer Tools" toggle (works in release builds) 38 - - [ ] Link debug overlay to full DevTools (/devtools routes) 37 + - [x] Add Settings → "Developer Tools" toggle (works in release builds) 38 + - [x] Link debug overlay to full DevTools (/devtools routes) 39 39 40 40 Phase 2: Repository Browser (Read-Only) 41 41 - [ ] Implement DevtoolsRepository wrapping XrpcClient 42 - - [ ] com.atproto.repo.describeRepo: list collections for a DID 42 + - [ ] com.atproto.repo.describeRepo: list collections for a DID 43 + - [ ] com.atproto.repo.listRecords: cursor-based pagination 43 44 - [ ] CollectionsPage: search, pin/unpin, tap to view records 44 - - [ ] com.atproto.repo.listRecords: cursor-based pagination 45 45 - [ ] RecordsPage: infinite scroll, filters (rkey prefix, date, blob presence) 46 - - [ ] Rich preview cards for known record types (post, profile, follow) 47 - - [ ] Fallback JSON tree for unknown types 46 + - [ ] Rich preview cards for known record types (post, profile, follow) 47 + - [ ] Fallback JSON tree for unknown types 48 48 49 49 Phase 3: Record Inspector 50 50 - [ ] com.atproto.repo.getRecord: fetch single record ··· 66 66 - [ ] Security test: tokens never logged or displayed 67 67 68 68 Deliverables: 69 - - [/] Internal debug overlay (kDebugMode only) with system info, network logs (logs pending) 70 - - [ ] User-facing DevTools (production) for repository inspection 69 + - [x] Internal debug overlay (kDebugMode only) with system info, network logs 70 + - [/] User-facing DevTools (production) for repository inspection (home page complete) 71 71 - [ ] Collections browser with search and pinning 72 72 - [ ] Records list with rich previews and pagination 73 73 - [ ] Record inspector with JSON tree and metadata
+26
lib/src/features/debug/presentation/debug_drawer.dart
··· 1 1 import 'package:flutter/material.dart'; 2 2 import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 + import 'package:go_router/go_router.dart'; 3 4 4 5 import '../application/debug_overlay_controller.dart'; 5 6 import 'atproto_session_tab.dart'; ··· 36 37 children: [SystemInfoTab(), AtprotoSessionTab(), NetworkInspectorTab()], 37 38 ), 38 39 ), 40 + _buildFooter(context, ref, theme), 39 41 ], 40 42 ), 41 43 ), ··· 83 85 labelColor: theme.colorScheme.primary, 84 86 unselectedLabelColor: theme.colorScheme.onSurfaceVariant, 85 87 indicatorColor: theme.colorScheme.primary, 88 + ); 89 + } 90 + 91 + Widget _buildFooter(BuildContext context, WidgetRef ref, ThemeData theme) { 92 + return Container( 93 + padding: const EdgeInsets.all(16), 94 + decoration: BoxDecoration( 95 + border: Border(top: BorderSide(color: theme.colorScheme.outline.withValues(alpha: 0.2))), 96 + ), 97 + child: FilledButton.tonal( 98 + onPressed: () { 99 + ref.read(debugOverlayControllerProvider.notifier).hide(); 100 + context.push('/devtools'); 101 + }, 102 + child: const Row( 103 + mainAxisAlignment: MainAxisAlignment.center, 104 + mainAxisSize: MainAxisSize.min, 105 + children: [ 106 + Icon(Icons.developer_mode, size: 20), 107 + SizedBox(width: 8), 108 + Flexible(child: Text('Open Full DevTools', overflow: TextOverflow.ellipsis)), 109 + ], 110 + ), 111 + ), 86 112 ); 87 113 } 88 114 }
+44
lib/src/features/developer_tools/application/dev_settings_providers.dart
··· 1 + import 'package:lazurite/src/app/providers.dart'; 2 + import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 + 4 + part 'dev_settings_providers.g.dart'; 5 + 6 + /// Provides access to developer tools settings. 7 + /// 8 + /// Manages whether developer tools are enabled in production builds. 9 + /// In kDebugMode, developer tools are always accessible regardless of this setting. 10 + @riverpod 11 + class DevToolsEnabled extends _$DevToolsEnabled { 12 + static const String _key = 'dev_tools_enabled'; 13 + 14 + @override 15 + Future<bool> build() async { 16 + final db = ref.watch(appDatabaseProvider); 17 + final value = await db.devToolsDao.getSetting(_key); 18 + return value == 'true'; 19 + } 20 + 21 + /// Enables developer tools in production builds. 22 + Future<void> enable() async { 23 + final db = ref.read(appDatabaseProvider); 24 + await db.devToolsDao.setSetting(_key, 'true', 'boolean'); 25 + ref.invalidateSelf(); 26 + } 27 + 28 + /// Disables developer tools in production builds. 29 + Future<void> disable() async { 30 + final db = ref.read(appDatabaseProvider); 31 + await db.devToolsDao.setSetting(_key, 'false', 'boolean'); 32 + ref.invalidateSelf(); 33 + } 34 + 35 + /// Toggles developer tools setting. 36 + Future<void> toggle() async { 37 + final current = await future; 38 + if (current) { 39 + await disable(); 40 + } else { 41 + await enable(); 42 + } 43 + } 44 + }
+70
lib/src/features/developer_tools/application/dev_settings_providers.g.dart
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'dev_settings_providers.dart'; 4 + 5 + // ************************************************************************** 6 + // RiverpodGenerator 7 + // ************************************************************************** 8 + 9 + // GENERATED CODE - DO NOT MODIFY BY HAND 10 + // ignore_for_file: type=lint, type=warning 11 + /// Provides access to developer tools settings. 12 + /// 13 + /// Manages whether developer tools are enabled in production builds. 14 + /// In kDebugMode, developer tools are always accessible regardless of this setting. 15 + 16 + @ProviderFor(DevToolsEnabled) 17 + final devToolsEnabledProvider = DevToolsEnabledProvider._(); 18 + 19 + /// Provides access to developer tools settings. 20 + /// 21 + /// Manages whether developer tools are enabled in production builds. 22 + /// In kDebugMode, developer tools are always accessible regardless of this setting. 23 + final class DevToolsEnabledProvider extends $AsyncNotifierProvider<DevToolsEnabled, bool> { 24 + /// Provides access to developer tools settings. 25 + /// 26 + /// Manages whether developer tools are enabled in production builds. 27 + /// In kDebugMode, developer tools are always accessible regardless of this setting. 28 + DevToolsEnabledProvider._() 29 + : super( 30 + from: null, 31 + argument: null, 32 + retry: null, 33 + name: r'devToolsEnabledProvider', 34 + isAutoDispose: true, 35 + dependencies: null, 36 + $allTransitiveDependencies: null, 37 + ); 38 + 39 + @override 40 + String debugGetCreateSourceHash() => _$devToolsEnabledHash(); 41 + 42 + @$internal 43 + @override 44 + DevToolsEnabled create() => DevToolsEnabled(); 45 + } 46 + 47 + String _$devToolsEnabledHash() => r'2a5ff517a0ce9c0bbe8e41b3b64b9563fbec4712'; 48 + 49 + /// Provides access to developer tools settings. 50 + /// 51 + /// Manages whether developer tools are enabled in production builds. 52 + /// In kDebugMode, developer tools are always accessible regardless of this setting. 53 + 54 + abstract class _$DevToolsEnabled extends $AsyncNotifier<bool> { 55 + FutureOr<bool> build(); 56 + @$mustCallSuper 57 + @override 58 + void runBuild() { 59 + final ref = this.ref as $Ref<AsyncValue<bool>, bool>; 60 + final element = 61 + ref.element 62 + as $ClassProviderElement< 63 + AnyNotifier<AsyncValue<bool>, bool>, 64 + AsyncValue<bool>, 65 + Object?, 66 + Object? 67 + >; 68 + element.handleCreate(ref, build); 69 + } 70 + }
+8
lib/src/features/settings/presentation/screens/settings_screen.dart
··· 97 97 }, 98 98 ), 99 99 SettingsTile( 100 + leading: const Icon(Icons.developer_mode), 101 + title: 'Developer Tools', 102 + subtitle: 'Repository inspector and debug tools', 103 + onTap: () { 104 + context.push('/devtools'); 105 + }, 106 + ), 107 + SettingsTile( 100 108 leading: const Icon(Icons.info_outline), 101 109 title: 'About', 102 110 subtitle: 'App version and information',
+8
test/src/features/debug/presentation/debug_drawer_test.dart
··· 206 206 expect(find.text('FPS'), findsOneWidget); 207 207 }); 208 208 }); 209 + 210 + testWidgets('displays Open Full DevTools button', (tester) async { 211 + await tester.pumpWidget(createSubject()); 212 + await tester.pump(); 213 + 214 + expect(find.text('Open Full DevTools'), findsOneWidget); 215 + expect(find.byIcon(Icons.developer_mode), findsOneWidget); 216 + }); 209 217 }); 210 218 } 211 219
+19
test/src/features/settings/presentation/screens/settings_screen_test.dart
··· 60 60 builder: (ctx, state) => 61 61 const Scaffold(body: Center(child: Text('Feed Management'))), 62 62 ), 63 + GoRoute( 64 + path: '/devtools', 65 + builder: (ctx, state) => 66 + const Scaffold(body: Center(child: Text('Developer Tools Screen'))), 67 + ), 63 68 ], 64 69 ); 65 70 return MaterialApp.router(routerConfig: router); ··· 114 119 expect(find.text('APP'), findsOneWidget); 115 120 expect(find.text('Theme'), findsOneWidget); 116 121 expect(find.text('Accessibility'), findsOneWidget); 122 + expect(find.text('Developer Tools'), findsOneWidget); 117 123 expect(find.text('About'), findsOneWidget); 118 124 }); 119 125 ··· 210 216 await tester.pumpAndSettle(); 211 217 212 218 expect(logoutCalled, isTrue); 219 + }); 220 + 221 + testWidgets('navigates to developer tools when Developer Tools is tapped', (tester) async { 222 + await tester.pumpWidget( 223 + buildTestApp([authProvider.overrideWith(() => _TestAuthNotifier.unauthenticated())]), 224 + ); 225 + 226 + await tester.pumpAndSettle(); 227 + 228 + await tester.tap(find.text('Developer Tools')); 229 + await tester.pumpAndSettle(); 230 + 231 + expect(find.text('Developer Tools Screen'), findsOneWidget); 213 232 }); 214 233 }); 215 234 }