utils/sync_settings_ui/lib/src/sync_settings_sections.dart
Krzysztof kuhy Rudnicki 3084080d74
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
github_device_auth tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
sync_settings_ui tests / test (push) Waiting to run
sync_settings_ui 0.7.0, gda 0.4.0: text-free
Glyph + spoken label replaces every app-written string on the shared
Sync settings screen and the GitHub device-code dialog (kuhy's
textgate rule). Old sentences are semantics labels; tests find them
there. The device user code stays visible (GitHub's data).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 01:27:57 +01:00

176 lines
5.3 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)),
IconButton(
onPressed: busy ? null : onDisconnect,
icon: const Icon(Icons.link_off, semanticLabel: 'Disconnect'),
),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (googleAvailable) ...[
Align(
alignment: Alignment.centerLeft,
child: FilledButton(
onPressed: busy ? null : onConnectGoogle,
child: const Icon(
Icons.account_circle,
semanticLabel: '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.
// The why is a block glyph beside it, the reason spoken only.
Row(
children: [
const FilledButton(
onPressed: null,
child: Icon(
Icons.account_circle,
semanticLabel: 'Sign in with Google',
),
),
const SizedBox(width: 8),
Icon(
Icons.block,
color: Theme.of(context).colorScheme.error,
semanticLabel: googleUnavailableReason,
),
],
),
const SizedBox(height: 12),
],
ExpansionTile(
initiallyExpanded: !googleAvailable,
title: const Align(
alignment: Alignment.centerLeft,
child: Icon(
Icons.password,
semanticLabel: '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(
prefixIcon: Icon(
Icons.alternate_email,
semanticLabel: 'Sync account email',
),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: passwordController,
obscureText: true,
autocorrect: false,
decoration: const InputDecoration(
prefixIcon: Icon(
Icons.lock_outline,
semanticLabel: 'Sync account password',
),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerLeft,
child: FilledButton(
onPressed: busy ? null : onConnectPassword,
child: const Icon(
Icons.cloud_done,
semanticLabel: '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(
onPressed: onExport,
child: Icon(Icons.upload, semanticLabel: 'Export ${backup.label}'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: onImport,
child: Icon(
Icons.download,
semanticLabel: 'Import ${backup.label}',
),
),
),
],
);
}
}