mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Polish frontend: design tokens, theming, AppBar titles, empty/loading states
Frontend usability and aesthetics pass across web and mobile: - Add shared layout tokens (theme/dimens.dart: Insets + Breakpoints) and replace magic-number spacing and per-file breakpoint consts. - Route both themes through buildAppTheme (theme/theme_builder.dart): explicit useMaterial3 and a shared branded textTheme (Barlow Condensed for display/headline/title styles); logo now reads its style from the theme. - Move screen titles into the shared AppBar (route-derived) and drop the redundant in-body headers; upgrade Settings buttons to M3 FilledButton. - Add reusable EmptyState and skeleton loaders; Devices empty state links to scanner status, notifications get filter-aware messages. - Add a first-run welcome intro and Save-disabled helper text in Settings. - Make device Filter/Sort adaptive: bottom sheet on phones, dialog on wide. - Collapse the device-list filter chips and Sort/Filter buttons into one row; rename the home Scanners card title and align its style. Tests: add EmptyState/skeleton widget tests and Settings first-run cases; update tests for the FilledButton swap and relocated titles. 89 passing, dart analyze clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5f6f5ad679
commit
9367c359d7
@@ -5,10 +5,13 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../model/notification.dart' as oott_model;
|
||||
import '../navigation.dart';
|
||||
import '../theme/dimens.dart';
|
||||
import '../utils/friendly_date_formatter.dart';
|
||||
import '../utils/oott_api.dart';
|
||||
import '../utils/ui_snackbars.dart';
|
||||
import '../widgets/empty_state.dart';
|
||||
import '../widgets/pagination_bar.dart';
|
||||
import '../widgets/skeleton.dart';
|
||||
import 'notification_card.dart';
|
||||
|
||||
const _pageSize = 5;
|
||||
@@ -190,6 +193,12 @@ class _NotificationsListState extends State<NotificationsList>
|
||||
return false;
|
||||
}
|
||||
|
||||
String _emptyMessage() => switch (_filter) {
|
||||
_NotificationFilter.newOnly => 'No new notifications',
|
||||
_NotificationFilter.oldOnly => 'No old notifications',
|
||||
_NotificationFilter.all => 'No notifications yet',
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@@ -229,9 +238,12 @@ class _NotificationsListState extends State<NotificationsList>
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Insets.sm,
|
||||
vertical: Insets.xs,
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
spacing: Insets.sm,
|
||||
children: _NotificationFilter.values
|
||||
.map(
|
||||
(f) => ChoiceChip(
|
||||
@@ -252,11 +264,7 @@ class _NotificationsListState extends State<NotificationsList>
|
||||
|
||||
List<Widget> _buildNotificationSlivers(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return [
|
||||
const SliverFillRemaining(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
];
|
||||
return [const SliverToBoxAdapter(child: ListSkeleton(rows: 4))];
|
||||
}
|
||||
if (_error != null) {
|
||||
return [
|
||||
@@ -273,16 +281,9 @@ class _NotificationsListState extends State<NotificationsList>
|
||||
if (_items.isEmpty) {
|
||||
return [
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 16, bottom: 12),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'No items found',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: EmptyState(
|
||||
icon: Icons.notifications_off_outlined,
|
||||
message: _emptyMessage(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user