mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-14 01:30:57 +01:00
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
154 lines
4.7 KiB
Dart
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}'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|