mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 17:47:24 +01:00
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
This commit is contained in:
parent
79130a6dd7
commit
db3d8f827f
@ -163,6 +163,7 @@ class _SyncSettingsScreenState extends State<SyncSettingsScreen> {
|
||||
connected: _firebaseConnected,
|
||||
busy: _busy,
|
||||
googleAvailable: _googleAvailable,
|
||||
googleUnavailableReason: widget.googleUnavailableReason,
|
||||
emailController: _emailController,
|
||||
passwordController: _passwordController,
|
||||
onConnectPassword: _connectFirebase,
|
||||
|
||||
@ -10,6 +10,7 @@ class _FirebaseSection extends StatelessWidget {
|
||||
required this.connected,
|
||||
required this.busy,
|
||||
required this.googleAvailable,
|
||||
required this.googleUnavailableReason,
|
||||
required this.emailController,
|
||||
required this.passwordController,
|
||||
required this.onConnectPassword,
|
||||
@ -20,6 +21,7 @@ class _FirebaseSection extends StatelessWidget {
|
||||
final bool connected;
|
||||
final bool busy;
|
||||
final bool googleAvailable;
|
||||
final String? googleUnavailableReason;
|
||||
final TextEditingController emailController;
|
||||
final TextEditingController passwordController;
|
||||
final VoidCallback onConnectPassword;
|
||||
@ -54,6 +56,25 @@ class _FirebaseSection extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
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,
|
||||
|
||||
@ -19,6 +19,7 @@ class SyncSettingsScreen extends StatefulWidget {
|
||||
required this.firebaseFactory,
|
||||
this.googleFirebaseFactory,
|
||||
this.googleAvailable,
|
||||
this.googleUnavailableReason,
|
||||
this.backup,
|
||||
super.key,
|
||||
});
|
||||
@ -59,6 +60,17 @@ class SyncSettingsScreen extends StatefulWidget {
|
||||
/// unsupported even though a factory is wired.
|
||||
final bool? googleAvailable;
|
||||
|
||||
/// Why Google sign-in is unavailable here, shown under a *disabled* button.
|
||||
///
|
||||
/// Null hides the button entirely, which stays the default. Supplying a
|
||||
/// reason trades a silently-absent control for one that explains itself --
|
||||
/// worth it where a platform genuinely cannot support the flow, and where
|
||||
/// its absence would otherwise read as a bug.
|
||||
///
|
||||
/// Not the same as a button that can never succeed: this one cannot be
|
||||
/// tapped at all, so it can never report a misleading "cancelled".
|
||||
final String? googleUnavailableReason;
|
||||
|
||||
/// Optional local export/import section. Null omits the "Backup" section
|
||||
/// entirely (diet_guard, wake_alarm have no local backup format).
|
||||
final BackupSlot? backup;
|
||||
|
||||
@ -5,7 +5,7 @@ description: >-
|
||||
injected closures -- no keystore or platform plugin access lives in this
|
||||
package itself.
|
||||
publish_to: 'none'
|
||||
version: 0.2.0
|
||||
version: 0.3.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
|
||||
@ -141,5 +141,55 @@ void main() {
|
||||
expect(find.text('Sign in with Google'), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('a reason renders a disabled button that explains itself', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
SyncSettingsScreen(
|
||||
accountLoader: () async => null,
|
||||
accountSaver: (_) async {},
|
||||
accountClearer: () async {},
|
||||
sessionProbe: () async => false,
|
||||
firebaseFactory: () async => null,
|
||||
googleFirebaseFactory: () async => _fakeClient(),
|
||||
googleAvailable: false,
|
||||
googleUnavailableReason: 'Not available on this platform.',
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign in with Google'), findsOneWidget);
|
||||
expect(find.text('Not available on this platform.'), findsOneWidget);
|
||||
|
||||
// Disabled, so it cannot report a misleading "cancelled".
|
||||
final button = tester.widget<FilledButton>(
|
||||
find.widgetWithText(FilledButton, 'Sign in with Google'),
|
||||
);
|
||||
expect(button.onPressed, isNull);
|
||||
});
|
||||
|
||||
testWidgets('no reason keeps the button hidden, as before', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
SyncSettingsScreen(
|
||||
accountLoader: () async => null,
|
||||
accountSaver: (_) async {},
|
||||
accountClearer: () async {},
|
||||
sessionProbe: () async => false,
|
||||
firebaseFactory: () async => null,
|
||||
googleFirebaseFactory: () async => _fakeClient(),
|
||||
googleAvailable: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign in with Google'), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user