utils/sync_settings_ui/lib/src/sync_settings_sections.dart
Krzysztof kuhy Rudnicki db3d8f827f Let a host explain an absent Google button instead of hiding it
lyricanki's Linux desktop cannot use google_sign_in (no Linux
implementation) and its loopback replacement is dark until a Desktop OAuth
client exists. Hiding the button there makes the screen silently offer less
on one platform, which reads as a bug.

googleUnavailableReason renders a *disabled* button plus the reason. That is
not the "control that can never succeed" trap: the trap is a live-looking
button that reports a fake cancellation, and a null onPressed cannot be
tapped at all. Null stays the default, so every existing host is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
2026-08-22 19:03:24 +02:00

154 lines
4.7 KiB
Dart

part of 'sync_settings_screen.dart';
/// The two sections the screen composes.
///
/// Split from sync_settings_screen.dart for the 250-line cap;
/// a `part` rather than an import because both sections are
/// library-private and could not be imported at all.
class _FirebaseSection extends StatelessWidget {
const _FirebaseSection({
required this.connected,
required this.busy,
required this.googleAvailable,
required this.googleUnavailableReason,
required this.emailController,
required this.passwordController,
required this.onConnectPassword,
required this.onConnectGoogle,
required this.onDisconnect,
});
final bool connected;
final bool busy;
final bool googleAvailable;
final String? googleUnavailableReason;
final TextEditingController emailController;
final TextEditingController passwordController;
final VoidCallback onConnectPassword;
final VoidCallback onConnectGoogle;
final VoidCallback onDisconnect;
@override
Widget build(BuildContext context) {
if (connected) {
return Row(
children: [
const Icon(Icons.cloud_done, size: 20),
const SizedBox(width: 8),
Expanded(child: Text(emailController.text)),
TextButton(
onPressed: busy ? null : onDisconnect,
child: const Text('Disconnect'),
),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (googleAvailable) ...[
Align(
alignment: Alignment.centerLeft,
child: FilledButton.icon(
onPressed: busy ? null : onConnectGoogle,
icon: const Icon(Icons.account_circle),
label: const Text('Sign in with Google'),
),
),
const SizedBox(height: 12),
] else if (googleUnavailableReason != null) ...[
// Disabled, not hidden. A null onPressed cannot be tapped, so this
// is not the "control that can never succeed" trap -- that one
// looks live and reports a fake cancellation. This one says why,
// which beats a screen that silently offers less on one platform.
Align(
alignment: Alignment.centerLeft,
child: FilledButton.icon(
onPressed: null,
icon: const Icon(Icons.account_circle),
label: const Text('Sign in with Google'),
),
),
const SizedBox(height: 4),
Text(
googleUnavailableReason!,
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 12),
],
ExpansionTile(
initiallyExpanded: !googleAvailable,
title: const Text('Use the account password instead'),
tilePadding: EdgeInsets.zero,
childrenPadding: const EdgeInsets.only(bottom: 8),
children: [
TextField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
autocorrect: false,
decoration: const InputDecoration(
labelText: 'Sync account email',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: passwordController,
obscureText: true,
autocorrect: false,
decoration: const InputDecoration(
labelText: 'Sync account password',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerLeft,
child: FilledButton.icon(
onPressed: busy ? null : onConnectPassword,
icon: const Icon(Icons.cloud_done),
label: const Text('Connect Firebase'),
),
),
],
),
],
);
}
}
class _BackupSection extends StatelessWidget {
const _BackupSection({
required this.backup,
required this.onExport,
required this.onImport,
});
final BackupSlot backup;
final VoidCallback onExport;
final VoidCallback onImport;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: onExport,
icon: const Icon(Icons.upload),
label: Text('Export ${backup.label}'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: onImport,
icon: const Icon(Icons.download),
label: Text('Import ${backup.label}'),
),
),
],
);
}
}