Settings rows carried their own platform-conditional typography and density, so on desktop and TV they rendered a 16px title, 14px subtitle and 80px row while every other row in the app — the Focusable*ListTile defaults plus ThemeData.listTileTheme's `dense: true` — renders 13/12 in 61px. Drop the overrides instead of re-tuning them: the tile defaults already encode the app's row style, and the explicit title styles were redundant under a dense ListTile (they also masked the disabled/selected title color). settingsOptionTitleStyle now only serves group children that are not ListTiles, and matches the dense title unconditionally. SettingsGroup hands its children that same compact density, so the plain ListTiles used as non-interactive info rows stop standing 11px taller than their interactive siblings.
111 lines
3.6 KiB
Dart
111 lines
3.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../../i18n/strings.g.dart';
|
|
import '../../models/catalog/catalog_item.dart';
|
|
import '../../providers/seerr_account_provider.dart';
|
|
import '../../widgets/app_icon.dart';
|
|
import '../../widgets/catalog_source_logo.dart';
|
|
import '../../widgets/focused_scroll_scaffold.dart';
|
|
import '../../widgets/focusable_list_tile.dart';
|
|
import '../../widgets/settings_section.dart';
|
|
import 'seerr_connect_screen.dart';
|
|
import 'seerr_settings_screen.dart';
|
|
import 'tracker_service_info.dart';
|
|
|
|
/// Unified hub for all connected services: the watch-progress trackers
|
|
/// (Trakt, MyAnimeList, AniList, Simkl) and the Seerr request server. Each
|
|
/// row opens its service-specific settings screen.
|
|
class ServicesSettingsScreen extends StatelessWidget {
|
|
const ServicesSettingsScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return FocusedScrollScaffold(
|
|
title: Text(t.services.title),
|
|
slivers: [
|
|
SliverList(
|
|
delegate: SliverChildListDelegate([
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
|
child: Text(
|
|
t.services.hubSubtitle,
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
|
),
|
|
),
|
|
SettingsGroup(children: [for (final info in TrackerServiceInfo.all) _TrackerHubRow(info), _seerr()]),
|
|
const SizedBox(height: 24),
|
|
]),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _seerr() => Consumer<SeerrAccountProvider>(
|
|
builder: (context, account, _) => _ServiceHubRow(
|
|
leading: const CatalogSourceLogo(CatalogSourceId.seerr, size: 24),
|
|
title: t.services.names.seerr,
|
|
username: account.isConnected ? account.displayName : null,
|
|
onTap: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => account.isConnected ? const SeerrSettingsScreen() : const SeerrConnectScreen(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Hub row for a watch tracker. Owns the `watch` on that service's account
|
|
/// provider so only this row rebuilds when the connection state changes.
|
|
class _TrackerHubRow extends StatelessWidget {
|
|
final TrackerServiceInfo info;
|
|
|
|
const _TrackerHubRow(this.info);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final connected = info.isConnected(context);
|
|
return _ServiceHubRow(
|
|
leading: CatalogSourceLogo(info.logoSource, size: 24),
|
|
title: info.displayName,
|
|
username: connected ? info.username(context) : null,
|
|
onTap: () {
|
|
if (connected) {
|
|
Navigator.push(context, MaterialPageRoute(builder: (_) => info.buildSettingsScreen()));
|
|
} else {
|
|
info.startConnection(context);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ServiceHubRow extends StatelessWidget {
|
|
final Widget leading;
|
|
final String title;
|
|
|
|
/// Non-null when connected. When null, the subtitle shows "Not connected".
|
|
final String? username;
|
|
|
|
final VoidCallback onTap;
|
|
|
|
const _ServiceHubRow({required this.leading, required this.title, required this.username, required this.onTap});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return FocusableListTile(
|
|
leading: leading,
|
|
title: Text(title),
|
|
subtitle: Text(username != null ? t.services.connectedAs(username: username!) : t.services.notConnected),
|
|
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
|
onTap: onTap,
|
|
);
|
|
}
|
|
}
|