diff --git a/lib/core/database/app_database.dart b/lib/core/database/app_database.dart index cf433a9..4e314ba 100644 --- a/lib/core/database/app_database.dart +++ b/lib/core/database/app_database.dart @@ -25,7 +25,7 @@ class AppDatabase extends _$AppDatabase { static const activeAccountDidSettingKey = 'active_account_did'; @override - int get schemaVersion => 12; + int get schemaVersion => 13; @override MigrationStrategy get migration => MigrationStrategy( @@ -62,9 +62,7 @@ class AppDatabase extends _$AppDatabase { await migrator.createTable(labelerCache); } if (from < 10) { - await customStatement( - "INSERT OR IGNORE INTO settings (key, value) VALUES ('ui_density', 'standard'), ('feed_architecture', 'grid')", - ); + await customStatement("INSERT OR IGNORE INTO settings (key, value) VALUES ('feed_architecture', 'grid')"); } if (from < 11) { /* @@ -75,6 +73,9 @@ class AppDatabase extends _$AppDatabase { if (from < 12) { await migrator.createTable(cachedFeedPages); } + if (from < 13) { + await customStatement("DELETE FROM settings WHERE key = 'ui_density'"); + } }, ); diff --git a/lib/core/theme/density_spacing.dart b/lib/core/theme/density_spacing.dart deleted file mode 100644 index 9bfe507..0000000 --- a/lib/core/theme/density_spacing.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; - -/// ThemeExtension providing density-scaled spacing values. -/// -/// Obtain via `Theme.of(context).extension()`. -class DensitySpacing extends ThemeExtension { - const DensitySpacing({required this.scale}); - - factory DensitySpacing.fromDensity(UiDensity density) => DensitySpacing(scale: density.scaleFactor); - - /// The multiplier applied to all spacing values (0.75 / 1.0 / 1.25). - final double scale; - - double get xs => 4.0 * scale; - double get sm => 8.0 * scale; - double get md => 16.0 * scale; - double get lg => 24.0 * scale; - double get xl => 32.0 * scale; - double get xxl => 48.0 * scale; - - @override - DensitySpacing copyWith({double? scale}) => DensitySpacing(scale: scale ?? this.scale); - - @override - DensitySpacing lerp(ThemeExtension? other, double t) { - if (other is! DensitySpacing) return this; - return DensitySpacing(scale: scale + (other.scale - scale) * t); - } - - @override - bool operator ==(Object other) => - identical(this, other) || other is DensitySpacing && runtimeType == other.runtimeType && scale == other.scale; - - @override - int get hashCode => scale.hashCode; -} diff --git a/lib/core/theme/ui_density.dart b/lib/core/theme/ui_density.dart deleted file mode 100644 index 3e1e53c..0000000 --- a/lib/core/theme/ui_density.dart +++ /dev/null @@ -1,27 +0,0 @@ -enum UiDensity { - compact, - standard, - relaxed; - - double get scaleFactor { - switch (this) { - case UiDensity.compact: - return 0.75; - case UiDensity.standard: - return 1.0; - case UiDensity.relaxed: - return 1.25; - } - } - - static UiDensity fromString(String? value) { - switch (value) { - case 'compact': - return UiDensity.compact; - case 'relaxed': - return UiDensity.relaxed; - default: - return UiDensity.standard; - } - } -} diff --git a/lib/features/auth/data/auth_repository.dart b/lib/features/auth/data/auth_repository.dart index 7b650f2..731c4c8 100644 --- a/lib/features/auth/data/auth_repository.dart +++ b/lib/features/auth/data/auth_repository.dart @@ -564,7 +564,7 @@ class AuthRepository { } String _buildCallbackPageHtml(Uri reopenUri) { - final escapedReopenUrl = HtmlEscape(HtmlEscapeMode.element).convert(reopenUri.toString()); + final escapedReopenUrl = const HtmlEscape(HtmlEscapeMode.element).convert(reopenUri.toString()); return ''' diff --git a/lib/features/settings/bloc/settings_cubit.dart b/lib/features/settings/bloc/settings_cubit.dart index 71d0460..8914168 100644 --- a/lib/features/settings/bloc/settings_cubit.dart +++ b/lib/features/settings/bloc/settings_cubit.dart @@ -2,7 +2,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lazurite/core/database/app_database.dart'; import 'package:lazurite/core/theme/app_theme.dart'; import 'package:lazurite/core/theme/feed_architecture.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; import 'package:lazurite/features/settings/bloc/settings_state.dart'; class SettingsCubit extends Cubit { @@ -11,7 +10,6 @@ class SettingsCubit extends Cubit { AppThemePalette? initialPalette, AppThemeVariant? initialVariant, bool? initialUseSystemTheme, - UiDensity? initialUiDensity, FeedArchitecture? initialFeedArchitecture, bool? initialSimulateOffline, int? initialThreadAutoCollapseDepth, @@ -20,7 +18,6 @@ class SettingsCubit extends Cubit { themePalette: initialPalette ?? AppThemePalette.oxocarbon, themeVariant: initialVariant ?? AppThemeVariant.dark, useSystemTheme: initialUseSystemTheme ?? false, - uiDensity: initialUiDensity ?? UiDensity.standard, feedArchitecture: initialFeedArchitecture ?? FeedArchitecture.grid, simulateOffline: initialSimulateOffline ?? false, threadAutoCollapseDepth: initialThreadAutoCollapseDepth, @@ -32,7 +29,6 @@ class SettingsCubit extends Cubit { static const String _keyThemePalette = 'theme_palette'; static const String _keyThemeVariant = 'theme_variant'; static const String _keyUseSystemTheme = 'use_system_theme'; - static const String _keyUiDensity = 'ui_density'; static const String _keyFeedArchitecture = 'feed_architecture'; static const String _keySimulateOffline = 'simulate_offline'; static const String _keyThreadAutoCollapseDepth = 'thread_auto_collapse_depth'; @@ -41,7 +37,6 @@ class SettingsCubit extends Cubit { final paletteStr = await database.getSetting(_keyThemePalette); final variantStr = await database.getSetting(_keyThemeVariant); final useSystemStr = await database.getSetting(_keyUseSystemTheme); - final uiDensityStr = await database.getSetting(_keyUiDensity); final feedArchStr = await database.getSetting(_keyFeedArchitecture); final simulateOfflineStr = await database.getSetting(_keySimulateOffline); final threadAutoCollapseDepthStr = await database.getSetting(_keyThreadAutoCollapseDepth); @@ -51,7 +46,6 @@ class SettingsCubit extends Cubit { themePalette: AppTheme.parsePalette(paletteStr), themeVariant: AppTheme.parseVariant(variantStr), useSystemTheme: useSystemStr == 'true', - uiDensity: UiDensity.fromString(uiDensityStr), feedArchitecture: FeedArchitecture.fromString(feedArchStr), simulateOffline: simulateOfflineStr == 'true', threadAutoCollapseDepth: int.tryParse(threadAutoCollapseDepthStr ?? ''), @@ -80,11 +74,6 @@ class SettingsCubit extends Cubit { emit(state.copyWith(useSystemTheme: value)); } - Future setUiDensity(UiDensity density) async { - await database.setSetting(_keyUiDensity, density.name); - emit(state.copyWith(uiDensity: density)); - } - Future setFeedArchitecture(FeedArchitecture architecture) async { await database.setSetting(_keyFeedArchitecture, architecture.name); emit(state.copyWith(feedArchitecture: architecture)); diff --git a/lib/features/settings/bloc/settings_state.dart b/lib/features/settings/bloc/settings_state.dart index 5428d77..22330dc 100644 --- a/lib/features/settings/bloc/settings_state.dart +++ b/lib/features/settings/bloc/settings_state.dart @@ -1,9 +1,6 @@ import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; import 'package:lazurite/core/theme/app_theme.dart'; -import 'package:lazurite/core/theme/density_spacing.dart'; import 'package:lazurite/core/theme/feed_architecture.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; const Object _threadAutoCollapseDepthUnset = Object(); @@ -12,7 +9,6 @@ class SettingsState extends Equatable { required this.themePalette, required this.themeVariant, required this.useSystemTheme, - this.uiDensity = UiDensity.standard, this.feedArchitecture = FeedArchitecture.grid, this.simulateOffline = false, this.threadAutoCollapseDepth, @@ -21,21 +17,14 @@ class SettingsState extends Equatable { final AppThemePalette themePalette; final AppThemeVariant themeVariant; final bool useSystemTheme; - final UiDensity uiDensity; final FeedArchitecture feedArchitecture; final bool simulateOffline; final int? threadAutoCollapseDepth; - ThemeData get themeData { - final base = AppTheme.getTheme(themePalette, themeVariant); - return base.copyWith(extensions: [DensitySpacing.fromDensity(uiDensity)]); - } - SettingsState copyWith({ AppThemePalette? themePalette, AppThemeVariant? themeVariant, bool? useSystemTheme, - UiDensity? uiDensity, FeedArchitecture? feedArchitecture, bool? simulateOffline, Object? threadAutoCollapseDepth = _threadAutoCollapseDepthUnset, @@ -44,7 +33,6 @@ class SettingsState extends Equatable { themePalette: themePalette ?? this.themePalette, themeVariant: themeVariant ?? this.themeVariant, useSystemTheme: useSystemTheme ?? this.useSystemTheme, - uiDensity: uiDensity ?? this.uiDensity, feedArchitecture: feedArchitecture ?? this.feedArchitecture, simulateOffline: simulateOffline ?? this.simulateOffline, threadAutoCollapseDepth: identical(threadAutoCollapseDepth, _threadAutoCollapseDepthUnset) @@ -58,7 +46,6 @@ class SettingsState extends Equatable { themePalette, themeVariant, useSystemTheme, - uiDensity, feedArchitecture, simulateOffline, threadAutoCollapseDepth, diff --git a/lib/features/settings/presentation/settings_screen.dart b/lib/features/settings/presentation/settings_screen.dart index 5ff1288..8ff1a0a 100644 --- a/lib/features/settings/presentation/settings_screen.dart +++ b/lib/features/settings/presentation/settings_screen.dart @@ -5,7 +5,6 @@ import 'package:go_router/go_router.dart'; import 'package:lazurite/core/router/app_shell.dart'; import 'package:lazurite/core/theme/app_theme.dart'; import 'package:lazurite/core/theme/feed_architecture.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; import 'package:lazurite/features/account/cubit/account_switcher_cubit.dart'; import 'package:lazurite/features/account/presentation/account_switcher_sheet.dart'; import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; @@ -84,20 +83,6 @@ class SettingsScreen extends StatelessWidget { subtitle: 'View your saved posts', onTap: () => context.push('/saved'), ), - _SettingsTile(icon: Icons.person_outline, title: 'Edit Profile', subtitle: 'Name, bio, avatar', onTap: () {}), - _SettingsTile(icon: Icons.lock_outline, title: 'Privacy', subtitle: 'Visibility settings', onTap: () {}), - const SizedBox(height: 24), - _buildSectionHeader(context, 'Notifications'), - _SettingsTile( - icon: Icons.notifications_outlined, - title: 'Push Notifications', - trailing: Switch(value: true, onChanged: (_) {}), - ), - _SettingsTile( - icon: Icons.email_outlined, - title: 'Email Notifications', - trailing: Switch(value: false, onChanged: (_) {}), - ), const SizedBox(height: 24), if (!kReleaseMode) ...[ _buildSectionHeader(context, 'Developer'), @@ -117,7 +102,6 @@ class SettingsScreen extends StatelessWidget { subtitle: 'View app log files', onTap: () => context.push('/settings/logs'), ), - _SettingsTile(icon: Icons.help_outline, title: 'Help & Support', onTap: () {}), _SettingsTile( icon: Icons.info_outline, title: 'About', @@ -241,22 +225,6 @@ class SettingsScreen extends StatelessWidget { ), child: Column( children: [ - _SettingsDropdownTile( - title: 'UI Density', - value: state.uiDensity, - options: UiDensity.values, - labelBuilder: (density) => switch (density) { - UiDensity.compact => 'Compact', - UiDensity.standard => 'Standard', - UiDensity.relaxed => 'Relaxed', - }, - onChanged: (value) { - if (value != null) { - settingsCubit.setUiDensity(value); - } - }, - ), - const Divider(height: 1), _SettingsDropdownTile( title: 'Feed Architecture', value: state.feedArchitecture, diff --git a/test/core/theme/density_spacing_test.dart b/test/core/theme/density_spacing_test.dart deleted file mode 100644 index 7fdac0a..0000000 --- a/test/core/theme/density_spacing_test.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:lazurite/core/theme/density_spacing.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; - -void main() { - group('DensitySpacing', () { - group('fromDensity', () { - test('compact uses 0.75 scale', () { - final spacing = DensitySpacing.fromDensity(UiDensity.compact); - expect(spacing.scale, 0.75); - }); - - test('standard uses 1.0 scale', () { - final spacing = DensitySpacing.fromDensity(UiDensity.standard); - expect(spacing.scale, 1.0); - }); - - test('relaxed uses 1.25 scale', () { - final spacing = DensitySpacing.fromDensity(UiDensity.relaxed); - expect(spacing.scale, 1.25); - }); - }); - - group('spacing values at standard (1.0) scale', () { - late DensitySpacing spacing; - - setUp(() { - spacing = DensitySpacing.fromDensity(UiDensity.standard); - }); - - test('xs is 4.0', () => expect(spacing.xs, 4.0)); - test('sm is 8.0', () => expect(spacing.sm, 8.0)); - test('md is 16.0', () => expect(spacing.md, 16.0)); - test('lg is 24.0', () => expect(spacing.lg, 24.0)); - test('xl is 32.0', () => expect(spacing.xl, 32.0)); - test('xxl is 48.0', () => expect(spacing.xxl, 48.0)); - }); - - group('spacing values scale correctly', () { - test('compact halves relative to relaxed', () { - final compact = DensitySpacing.fromDensity(UiDensity.compact); - final relaxed = DensitySpacing.fromDensity(UiDensity.relaxed); - expect(compact.md, lessThan(relaxed.md)); - }); - - test('all spacing values are proportional to scale', () { - const scale = 2.0; - const spacing = DensitySpacing(scale: scale); - expect(spacing.xs, 4.0 * scale); - expect(spacing.sm, 8.0 * scale); - expect(spacing.md, 16.0 * scale); - expect(spacing.lg, 24.0 * scale); - expect(spacing.xl, 32.0 * scale); - expect(spacing.xxl, 48.0 * scale); - }); - }); - - group('copyWith', () { - test('returns new instance with updated scale', () { - const original = DensitySpacing(scale: 1.0); - final copy = original.copyWith(scale: 0.5); - expect(copy.scale, 0.5); - expect(original.scale, 1.0); - }); - - test('preserves scale when not provided', () { - const original = DensitySpacing(scale: 1.25); - final copy = original.copyWith(); - expect(copy.scale, 1.25); - }); - }); - - group('lerp', () { - test('lerps scale between two instances', () { - const a = DensitySpacing(scale: 0.75); - const b = DensitySpacing(scale: 1.25); - final mid = a.lerp(b, 0.5); - expect(mid.scale, closeTo(1.0, 0.001)); - }); - - test('lerp at t=0 returns self values', () { - const a = DensitySpacing(scale: 0.75); - const b = DensitySpacing(scale: 1.25); - final result = a.lerp(b, 0.0); - expect(result.scale, 0.75); - }); - - test('lerp at t=1 returns other values', () { - const a = DensitySpacing(scale: 0.75); - const b = DensitySpacing(scale: 1.25); - final result = a.lerp(b, 1.0); - expect(result.scale, 1.25); - }); - - test('lerp with null returns self', () { - const a = DensitySpacing(scale: 1.0); - final result = a.lerp(null, 0.5); - expect(result, a); - }); - }); - - group('equality', () { - test('equal when scale is the same', () { - const a = DensitySpacing(scale: 1.0); - const b = DensitySpacing(scale: 1.0); - expect(a, equals(b)); - }); - - test('not equal when scale differs', () { - const a = DensitySpacing(scale: 1.0); - const b = DensitySpacing(scale: 1.25); - expect(a, isNot(equals(b))); - }); - - test('hashCode is equal for same scale', () { - const a = DensitySpacing(scale: 1.0); - const b = DensitySpacing(scale: 1.0); - expect(a.hashCode, b.hashCode); - }); - }); - }); -} diff --git a/test/core/theme/ui_density_test.dart b/test/core/theme/ui_density_test.dart deleted file mode 100644 index f0d4acb..0000000 --- a/test/core/theme/ui_density_test.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; - -void main() { - group('UiDensity', () { - group('scaleFactor', () { - test('compact has scale 0.75', () { - expect(UiDensity.compact.scaleFactor, 0.75); - }); - - test('standard has scale 1.0', () { - expect(UiDensity.standard.scaleFactor, 1.0); - }); - - test('relaxed has scale 1.25', () { - expect(UiDensity.relaxed.scaleFactor, 1.25); - }); - - test('compact scale is less than standard', () { - expect(UiDensity.compact.scaleFactor, lessThan(UiDensity.standard.scaleFactor)); - }); - - test('relaxed scale is greater than standard', () { - expect(UiDensity.relaxed.scaleFactor, greaterThan(UiDensity.standard.scaleFactor)); - }); - }); - - group('fromString', () { - test('parses compact', () { - expect(UiDensity.fromString('compact'), UiDensity.compact); - }); - - test('parses standard', () { - expect(UiDensity.fromString('standard'), UiDensity.standard); - }); - - test('parses relaxed', () { - expect(UiDensity.fromString('relaxed'), UiDensity.relaxed); - }); - - test('null returns standard', () { - expect(UiDensity.fromString(null), UiDensity.standard); - }); - - test('unknown value returns standard', () { - expect(UiDensity.fromString('unknown'), UiDensity.standard); - }); - - test('round-trips all values via name', () { - for (final density in UiDensity.values) { - expect(UiDensity.fromString(density.name), density, reason: 'density: $density'); - } - }); - }); - }); -} diff --git a/test/features/feed/presentation/home_feed_screen_test.dart b/test/features/feed/presentation/home_feed_screen_test.dart index 2ace6f5..da0b3bd 100644 --- a/test/features/feed/presentation/home_feed_screen_test.dart +++ b/test/features/feed/presentation/home_feed_screen_test.dart @@ -7,7 +7,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lazurite/core/theme/app_theme.dart'; import 'package:lazurite/core/theme/feed_architecture.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; import 'package:lazurite/features/auth/data/models/auth_models.dart'; import 'package:lazurite/features/connectivity/cubit/connectivity_cubit.dart'; @@ -33,7 +32,6 @@ SettingsState _settingsState(FeedArchitecture architecture) => SettingsState( themePalette: AppThemePalette.oxocarbon, themeVariant: AppThemeVariant.dark, useSystemTheme: false, - uiDensity: UiDensity.standard, feedArchitecture: architecture, ); diff --git a/test/features/profile/presentation/profile_screen_test.dart b/test/features/profile/presentation/profile_screen_test.dart index e415072..c2d4f4d 100644 --- a/test/features/profile/presentation/profile_screen_test.dart +++ b/test/features/profile/presentation/profile_screen_test.dart @@ -11,7 +11,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:lazurite/core/theme/app_theme.dart'; import 'package:lazurite/core/theme/feed_architecture.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; import 'package:lazurite/features/auth/data/models/auth_models.dart'; import 'package:lazurite/features/compose/presentation/compose_route_args.dart'; @@ -79,7 +78,6 @@ void main() { themePalette: AppThemePalette.oxocarbon, themeVariant: AppThemeVariant.dark, useSystemTheme: false, - uiDensity: UiDensity.standard, feedArchitecture: FeedArchitecture.grid, ); @@ -87,7 +85,6 @@ void main() { themePalette: AppThemePalette.oxocarbon, themeVariant: AppThemeVariant.dark, useSystemTheme: false, - uiDensity: UiDensity.standard, feedArchitecture: architecture, ); diff --git a/test/features/settings/bloc/settings_cubit_test.dart b/test/features/settings/bloc/settings_cubit_test.dart index 3cb742b..6f552c2 100644 --- a/test/features/settings/bloc/settings_cubit_test.dart +++ b/test/features/settings/bloc/settings_cubit_test.dart @@ -4,7 +4,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:lazurite/core/database/app_database.dart'; import 'package:lazurite/core/theme/app_theme.dart'; import 'package:lazurite/core/theme/feed_architecture.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; import 'package:lazurite/features/settings/bloc/settings_cubit.dart'; import 'package:lazurite/features/settings/bloc/settings_state.dart'; @@ -25,7 +24,6 @@ void main() { expect(cubit.state.themePalette, AppThemePalette.oxocarbon); expect(cubit.state.themeVariant, AppThemeVariant.dark); expect(cubit.state.useSystemTheme, false); - expect(cubit.state.uiDensity, UiDensity.standard); expect(cubit.state.feedArchitecture, FeedArchitecture.grid); expect(cubit.state.simulateOffline, false); expect(cubit.state.threadAutoCollapseDepth, isNull); @@ -37,7 +35,6 @@ void main() { initialPalette: AppThemePalette.catppuccin, initialVariant: AppThemeVariant.light, initialUseSystemTheme: true, - initialUiDensity: UiDensity.compact, initialFeedArchitecture: FeedArchitecture.linear, initialSimulateOffline: true, initialThreadAutoCollapseDepth: 3, @@ -45,7 +42,6 @@ void main() { expect(cubit.state.themePalette, AppThemePalette.catppuccin); expect(cubit.state.themeVariant, AppThemeVariant.light); expect(cubit.state.useSystemTheme, true); - expect(cubit.state.uiDensity, UiDensity.compact); expect(cubit.state.feedArchitecture, FeedArchitecture.linear); expect(cubit.state.simulateOffline, true); expect(cubit.state.threadAutoCollapseDepth, 3); @@ -58,7 +54,6 @@ void main() { await database.setSetting('theme_palette', 'nord'); await database.setSetting('theme_variant', 'light'); await database.setSetting('use_system_theme', 'true'); - await database.setSetting('ui_density', 'compact'); await database.setSetting('feed_architecture', 'linear'); await database.setSetting('simulate_offline', 'true'); await database.setSetting('thread_auto_collapse_depth', '4'); @@ -69,7 +64,6 @@ void main() { .having((s) => s.themePalette, 'themePalette', AppThemePalette.nord) .having((s) => s.themeVariant, 'themeVariant', AppThemeVariant.light) .having((s) => s.useSystemTheme, 'useSystemTheme', true) - .having((s) => s.uiDensity, 'uiDensity', UiDensity.compact) .having((s) => s.feedArchitecture, 'feedArchitecture', FeedArchitecture.linear) .having((s) => s.simulateOffline, 'simulateOffline', true) .having((s) => s.threadAutoCollapseDepth, 'threadAutoCollapseDepth', 4), @@ -85,7 +79,6 @@ void main() { .having((s) => s.themePalette, 'themePalette', AppThemePalette.oxocarbon) .having((s) => s.themeVariant, 'themeVariant', AppThemeVariant.dark) .having((s) => s.useSystemTheme, 'useSystemTheme', false) - .having((s) => s.uiDensity, 'uiDensity', UiDensity.standard) .having((s) => s.feedArchitecture, 'feedArchitecture', FeedArchitecture.grid) .having((s) => s.simulateOffline, 'simulateOffline', false) .having((s) => s.threadAutoCollapseDepth, 'threadAutoCollapseDepth', isNull), @@ -140,28 +133,6 @@ void main() { }, ); - blocTest( - 'setUiDensity updates state and persists to database', - build: () => SettingsCubit(database: database), - act: (cubit) => cubit.setUiDensity(UiDensity.compact), - expect: () => [isA().having((s) => s.uiDensity, 'uiDensity', UiDensity.compact)], - verify: (cubit) async { - final value = await database.getSetting('ui_density'); - expect(value, 'compact'); - }, - ); - - blocTest( - 'setUiDensity relaxed updates state and persists to database', - build: () => SettingsCubit(database: database), - act: (cubit) => cubit.setUiDensity(UiDensity.relaxed), - expect: () => [isA().having((s) => s.uiDensity, 'uiDensity', UiDensity.relaxed)], - verify: (cubit) async { - final value = await database.getSetting('ui_density'); - expect(value, 'relaxed'); - }, - ); - blocTest( 'setFeedArchitecture updates state and persists to database', build: () => SettingsCubit(database: database), @@ -223,17 +194,15 @@ void main() { ); blocTest( - 'loadSettings round-trips ui_density, feed_architecture, and thread auto-collapse depth', + 'loadSettings round-trips feed_architecture and thread auto-collapse depth', build: () => SettingsCubit(database: database), setUp: () async { - await database.setSetting('ui_density', 'relaxed'); await database.setSetting('feed_architecture', 'linear'); await database.setSetting('thread_auto_collapse_depth', '6'); }, act: (cubit) => cubit.loadSettings(), expect: () => [ isA() - .having((s) => s.uiDensity, 'uiDensity', UiDensity.relaxed) .having((s) => s.feedArchitecture, 'feedArchitecture', FeedArchitecture.linear) .having((s) => s.threadAutoCollapseDepth, 'threadAutoCollapseDepth', 6), ], diff --git a/test/features/settings/bloc/settings_state_test.dart b/test/features/settings/bloc/settings_state_test.dart index 998c818..6ec1d13 100644 --- a/test/features/settings/bloc/settings_state_test.dart +++ b/test/features/settings/bloc/settings_state_test.dart @@ -1,7 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:lazurite/core/theme/app_theme.dart'; import 'package:lazurite/core/theme/feed_architecture.dart'; -import 'package:lazurite/core/theme/ui_density.dart'; import 'package:lazurite/features/settings/bloc/settings_state.dart'; void main() { @@ -66,23 +65,6 @@ void main() { expect(state1, isNot(equals(state2))); }); - test('inequality when uiDensity differs', () { - const state1 = SettingsState( - themePalette: AppThemePalette.oxocarbon, - themeVariant: AppThemeVariant.dark, - useSystemTheme: false, - uiDensity: UiDensity.standard, - ); - const state2 = SettingsState( - themePalette: AppThemePalette.oxocarbon, - themeVariant: AppThemeVariant.dark, - useSystemTheme: false, - uiDensity: UiDensity.compact, - ); - - expect(state1, isNot(equals(state2))); - }); - test('inequality when feedArchitecture differs', () { const state1 = SettingsState( themePalette: AppThemePalette.oxocarbon, @@ -145,7 +127,6 @@ void main() { themePalette: AppThemePalette.nord, themeVariant: AppThemeVariant.light, useSystemTheme: true, - uiDensity: UiDensity.compact, feedArchitecture: FeedArchitecture.linear, simulateOffline: true, threadAutoCollapseDepth: 3, @@ -154,7 +135,6 @@ void main() { expect(updated.themePalette, AppThemePalette.nord); expect(updated.themeVariant, AppThemeVariant.light); expect(updated.useSystemTheme, true); - expect(updated.uiDensity, UiDensity.compact); expect(updated.feedArchitecture, FeedArchitecture.linear); expect(updated.simulateOffline, true); expect(updated.threadAutoCollapseDepth, 3); @@ -166,7 +146,6 @@ void main() { themePalette: AppThemePalette.catppuccin, themeVariant: AppThemeVariant.light, useSystemTheme: true, - uiDensity: UiDensity.relaxed, feedArchitecture: FeedArchitecture.linear, simulateOffline: true, threadAutoCollapseDepth: 4, @@ -177,7 +156,6 @@ void main() { expect(updated.themePalette, AppThemePalette.catppuccin); expect(updated.themeVariant, AppThemeVariant.light); expect(updated.useSystemTheme, true); - expect(updated.uiDensity, UiDensity.relaxed); expect(updated.feedArchitecture, FeedArchitecture.linear); expect(updated.simulateOffline, true); expect(updated.threadAutoCollapseDepth, 4); @@ -201,7 +179,6 @@ void main() { themePalette: AppThemePalette.rosePine, themeVariant: AppThemeVariant.light, useSystemTheme: true, - uiDensity: UiDensity.compact, feedArchitecture: FeedArchitecture.linear, simulateOffline: true, threadAutoCollapseDepth: 6, @@ -210,21 +187,11 @@ void main() { expect(state.props, contains(AppThemePalette.rosePine)); expect(state.props, contains(AppThemeVariant.light)); expect(state.props, contains(true)); - expect(state.props, contains(UiDensity.compact)); expect(state.props, contains(FeedArchitecture.linear)); expect(state.props, contains(true)); expect(state.props, contains(6)); }); - test('defaults uiDensity to standard', () { - const state = SettingsState( - themePalette: AppThemePalette.oxocarbon, - themeVariant: AppThemeVariant.dark, - useSystemTheme: false, - ); - expect(state.uiDensity, UiDensity.standard); - }); - test('defaults feedArchitecture to grid', () { const state = SettingsState( themePalette: AppThemePalette.oxocarbon, diff --git a/test/features/settings/presentation/settings_screen_test.dart b/test/features/settings/presentation/settings_screen_test.dart new file mode 100644 index 0000000..e8ed786 --- /dev/null +++ b/test/features/settings/presentation/settings_screen_test.dart @@ -0,0 +1,80 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lazurite/core/theme/app_theme.dart'; +import 'package:lazurite/core/theme/feed_architecture.dart'; +import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; +import 'package:lazurite/features/settings/bloc/settings_cubit.dart'; +import 'package:lazurite/features/settings/bloc/settings_state.dart'; +import 'package:lazurite/features/settings/presentation/settings_screen.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAuthBloc extends MockBloc implements AuthBloc {} + +class MockSettingsCubit extends MockCubit implements SettingsCubit {} + +void main() { + late MockAuthBloc authBloc; + late MockSettingsCubit settingsCubit; + + setUp(() { + authBloc = MockAuthBloc(); + settingsCubit = MockSettingsCubit(); + + when(() => authBloc.state).thenReturn(const AuthState.unauthenticated()); + whenListen(authBloc, const Stream.empty(), initialState: const AuthState.unauthenticated()); + + when(() => settingsCubit.state).thenReturn( + const SettingsState( + themePalette: AppThemePalette.oxocarbon, + themeVariant: AppThemeVariant.dark, + useSystemTheme: false, + feedArchitecture: FeedArchitecture.grid, + ), + ); + whenListen( + settingsCubit, + const Stream.empty(), + initialState: const SettingsState( + themePalette: AppThemePalette.oxocarbon, + themeVariant: AppThemeVariant.dark, + useSystemTheme: false, + feedArchitecture: FeedArchitecture.grid, + ), + ); + }); + + Widget buildSubject() { + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: authBloc), + BlocProvider.value(value: settingsCubit), + ], + child: const MaterialApp(home: SettingsScreen()), + ); + } + + testWidgets('shows active settings controls that are wired up', (tester) async { + await tester.pumpWidget(buildSubject()); + await tester.pumpAndSettle(); + + expect(find.text('APPEARANCE'), findsOneWidget); + expect(find.text('System'), findsOneWidget); + expect(find.text('LAYOUT'), findsOneWidget); + expect(find.text('Feed Architecture'), findsOneWidget); + expect(find.text('Thread Auto-Collapse'), findsOneWidget); + }); + + testWidgets('does not render removed placeholder settings', (tester) async { + await tester.pumpWidget(buildSubject()); + await tester.pumpAndSettle(); + + expect(find.text('UI Density'), findsNothing); + expect(find.text('Edit Profile'), findsNothing); + expect(find.text('Privacy'), findsNothing); + expect(find.text('Push Notifications'), findsNothing); + expect(find.text('Email Notifications'), findsNothing); + expect(find.text('Help & Support'), findsNothing); + }); +}