mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 15:33:57 +01:00
Final batch of the 250-line sweep. `check_file_length.sh --all` exits 0: every file in the repo is at or under 250 lines. github_mirror_screen.dart 391 -> 205 (+ actions, client_id_dialog parts) foodbank_service.dart 314 -> 209 (+ foodbank_service_manual part) settings_screen.dart 286 -> 237 (+ settings_kcal_goal part) firebase_backend.dart 282 -> 100 (+ firebase_client.dart) Three of the four are `part` files rather than separate libraries, and that is forced rather than stylistic: the moved code drives private state (`_busy`, `_status`, the text controllers) and calls private helpers (`_readBank`, `_normalize`), none of which are visible across a library boundary in Dart. Widening them to public just to split a file would leak internals the rest of the app has no business calling. Within those parts, mixins rather than extensions, for a concrete reason: an extension is not a subclass of `State`, so calling `setState` from one is an `invalid_use_of_protected_member`. Fields the State must own (the controllers it creates and disposes) stay declared there and are reached through abstract getters. `firebase_client.dart` is a real library split -- opening an authenticated client has no private coupling to the keystore-backed account record, only to its public API. The one exception, dropping the account marker without setting the opt-out flag, became `forgetAccountMarker()` rather than exposing the secure store. Verified: flutter test 555 (unchanged), flutter analyze clean, 707 Python tests / 100% branch coverage, check_patch_targets 0, pre-commit clean, check_file_length.sh --all exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RA9ynjqXPnexRQnR5oAuCC
84 lines
2.9 KiB
Dart
84 lines
2.9 KiB
Dart
// The "paste your OAuth client id" dialog shown before the device flow.
|
|
//
|
|
// Split out of `github_mirror_screen.dart` for the repo's 250-line cap. A
|
|
// `part` so the class can stay library-private: it is an implementation
|
|
// detail of that screen and nothing else may construct it.
|
|
|
|
part of 'github_mirror_screen.dart';
|
|
|
|
/// Dialog shown when "Connect GitHub" is tapped with no OAuth App client id
|
|
/// configured yet. Explains what it is, how to get one, and lets the user
|
|
/// paste it in directly — rather than leaving them to discover a buried
|
|
/// field on their own. Pops the trimmed client id, or null if cancelled.
|
|
class _ClientIdSetupDialog extends StatefulWidget {
|
|
const _ClientIdSetupDialog();
|
|
|
|
@override
|
|
State<_ClientIdSetupDialog> createState() => _ClientIdSetupDialogState();
|
|
}
|
|
|
|
class _ClientIdSetupDialogState extends State<_ClientIdSetupDialog> {
|
|
final _controller = TextEditingController();
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AlertDialog(
|
|
title: const Text('One-time GitHub setup needed'),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Diet Guard signs in via a GitHub OAuth App (no password '
|
|
'typed into this app). You only have to set this up once:',
|
|
),
|
|
const SizedBox(height: 12),
|
|
const Text(
|
|
'1. On any device, open '
|
|
'github.com/settings/developers → "New OAuth App".\n'
|
|
'2. Name/Homepage/Callback URL can be anything (device flow '
|
|
"doesn't use the callback) — e.g. "
|
|
'"Diet Guard" and your GitHub profile URL.\n'
|
|
'3. Check "Enable Device Flow", then click "Register '
|
|
'application".\n'
|
|
"4. Copy the Client ID shown on the app's page and paste it "
|
|
'below.',
|
|
),
|
|
const SizedBox(height: 12),
|
|
const Text(
|
|
'When you connect below, log in with the GitHub account that '
|
|
'has write access to kuhyx/syncs.',
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextField(
|
|
controller: _controller,
|
|
autofocus: true,
|
|
decoration: const InputDecoration(labelText: 'Client ID'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('Cancel'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () {
|
|
final id = _controller.text.trim();
|
|
if (id.isNotEmpty) Navigator.of(context).pop(id);
|
|
},
|
|
child: const Text('Continue'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|