mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:27:02 +01:00
focus_owner: split status_page_state.dart under the 250-line cap
Extracts the two AlertDialog builders and the build() body-selection logic into a new status_page_dialogs.dart part file, following the same part-of-main.dart split pattern status_body.dart already used. Pure extraction, no behavior change -- verified with dart analyze and the full flutter test suite (94/94 passing). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
f153a69ac1
commit
47e05d1dca
@ -0,0 +1,49 @@
|
||||
{
|
||||
"intent": "Phase 1 of NEXT_SESSION_PROMPT.md's 250-line-cap campaign: bring focus_owner/lib/status_page_state.dart (307 lines) under the cap.",
|
||||
"scope": [
|
||||
"focus_owner/lib/status_page_state.dart",
|
||||
"focus_owner/lib/status_page_dialogs.dart (new)",
|
||||
"focus_owner/lib/main.dart"
|
||||
],
|
||||
"changes": [
|
||||
"Extracted the two AlertDialog builders (_lockVpnDialog, _releaseDialog) out of _lockVpn/_release into standalone functions in a new status_page_dialogs.dart part file",
|
||||
"Extracted the build() method's body-selection if/else chain into a standalone _statusPageBody() helper in the same new part file",
|
||||
"Registered the new part file in main.dart alongside the existing status_page_state.dart and status_body.dart parts, following the exact pattern already established by that prior split",
|
||||
"No behavioral change: all extracted code moved verbatim (dialog content, button labels, callback wiring, hasAccounts branching) with no logic changes"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bash meta/scripts/check_file_length.sh --all",
|
||||
"result": "pass",
|
||||
"evidence": "status_page_state.dart now 248 lines, status_page_dialogs.dart 103 lines; file count over cap dropped from 11 to 10"
|
||||
},
|
||||
{
|
||||
"command": "dart analyze lib/",
|
||||
"result": "pass",
|
||||
"evidence": "No issues found!"
|
||||
},
|
||||
{
|
||||
"command": "flutter test test/status_page_test.dart test/status_page_actions_test.dart",
|
||||
"result": "pass",
|
||||
"evidence": "8/8 tests passed, including the release-confirmation dialog and lock-VPN dialog paths driven through the rendered widget tree"
|
||||
},
|
||||
{
|
||||
"command": "flutter test (full suite)",
|
||||
"result": "pass",
|
||||
"evidence": "94/94 tests passed, no regressions elsewhere in focus_owner"
|
||||
},
|
||||
{
|
||||
"command": "dart format focus_owner/lib/main.dart focus_owner/lib/status_page_state.dart focus_owner/lib/status_page_dialogs.dart",
|
||||
"result": "pass",
|
||||
"evidence": "Formatted 3 files (0 changed) -- already correctly formatted"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"focus_owner has no dedicated CI workflow or coverage gate (confirmed: no .github/workflows/*focus_owner* file), so this split relies entirely on the local flutter test run performed this session, not an automated gate",
|
||||
"No new tests were added since this is a pure verbatim extraction already covered by existing widget tests that drive the dialogs through the rendered UI -- if that coverage assumption is wrong, a future regression in these dialogs would not be caught by unit tests alone"
|
||||
],
|
||||
"rollback": [
|
||||
"git revert this commit",
|
||||
"After rollback, re-run flutter test to confirm the pre-split test count (94) still passes"
|
||||
]
|
||||
}
|
||||
@ -12,6 +12,8 @@ import 'package:focus_owner/theme.dart';
|
||||
|
||||
part 'status_page_state.dart';
|
||||
|
||||
part 'status_page_dialogs.dart';
|
||||
|
||||
part 'status_body.dart';
|
||||
|
||||
void main() => runApp(const FocusOwnerApp());
|
||||
|
||||
103
focus_owner/lib/status_page_dialogs.dart
Normal file
103
focus_owner/lib/status_page_dialogs.dart
Normal file
@ -0,0 +1,103 @@
|
||||
/// Confirmation dialogs and the body-selection helper for the StatusPage.
|
||||
///
|
||||
/// Split out of status_page_state.dart to keep it under the 250-line cap. A
|
||||
/// `part` because these builders are library-private, same as the rest of
|
||||
/// the StatusPage split.
|
||||
|
||||
part of 'main.dart';
|
||||
|
||||
/// Picks the StatusPage's body widget for the current load state.
|
||||
Widget _statusPageBody({
|
||||
required DevicePolicyStatus? status,
|
||||
required String? error,
|
||||
required FocusPolicy? focusPolicy,
|
||||
required String? policyError,
|
||||
required bool busy,
|
||||
required Future<void> Function() onRelease,
|
||||
required Future<void> Function() onRunNow,
|
||||
required Future<void> Function() onSetHome,
|
||||
required Future<void> Function() onLockVpn,
|
||||
required bool hasHome,
|
||||
required List<EnforcementRecord> log,
|
||||
required VoidCallback onOpenLog,
|
||||
}) {
|
||||
if (error != null) {
|
||||
return const _Message(text: 'Could not read state', color: kDanger);
|
||||
}
|
||||
if (status != null) {
|
||||
return _StatusBody(
|
||||
status: status,
|
||||
focusPolicy: focusPolicy,
|
||||
policyError: policyError,
|
||||
busy: busy,
|
||||
onRelease: onRelease,
|
||||
onRunNow: onRunNow,
|
||||
onSetHome: onSetHome,
|
||||
onLockVpn: onLockVpn,
|
||||
hasHome: hasHome,
|
||||
log: log,
|
||||
onOpenLog: onOpenLog,
|
||||
);
|
||||
}
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
Widget _lockVpnDialog(BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: kSurface,
|
||||
title: const Text('Lock VPN configuration?'),
|
||||
content: const Text(
|
||||
'The always-on VPN can no longer be turned off from Settings. '
|
||||
'Releasing device owner lifts this.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Lock'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _releaseDialog(BuildContext context, {required bool hasAccounts}) {
|
||||
return AlertDialog(
|
||||
backgroundColor: kSurface,
|
||||
title: const Text('Release device owner?'),
|
||||
// Deliberately concrete, and honest in both directions. "Restored only
|
||||
// by a factory reset" is true but abstract, and this dialog is read in
|
||||
// the moment the block is most inconvenient -- the price has to be
|
||||
// legible right then, not inferred. While no account exists the price
|
||||
// really is low, and overstating it there would teach the user to
|
||||
// ignore the warning that matters later.
|
||||
content: Text(
|
||||
hasAccounts
|
||||
? 'Enforcement stops. YouTube comes back.\n\n'
|
||||
'Accounts exist on this device, so device owner can NEVER '
|
||||
'be set again without a factory reset. That means '
|
||||
're-pairing mBank, Revolut and inFakt over SMS, '
|
||||
're-activating mObywatel, signing in to every account '
|
||||
'again, and losing Signal history.\n\n'
|
||||
'This is the way out if something is broken. It is a bad '
|
||||
'trade for wanting to watch a video.'
|
||||
: 'Enforcement stops and YouTube comes back.\n\n'
|
||||
'No account exists yet, so device owner can be set again '
|
||||
'straight afterwards without a wipe. This is the moment to '
|
||||
'test the release path, before signing in.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: TextButton.styleFrom(foregroundColor: kDanger),
|
||||
child: const Text('Release'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -158,24 +158,7 @@ class _StatusPageState extends State<StatusPage> {
|
||||
Future<void> _lockVpn() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: kSurface,
|
||||
title: const Text('Lock VPN configuration?'),
|
||||
content: const Text(
|
||||
'The always-on VPN can no longer be turned off from Settings. '
|
||||
'Releasing device owner lifts this.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Lock'),
|
||||
),
|
||||
],
|
||||
),
|
||||
builder: _lockVpnDialog,
|
||||
);
|
||||
if (confirmed != true) return;
|
||||
|
||||
@ -194,44 +177,10 @@ class _StatusPageState extends State<StatusPage> {
|
||||
}
|
||||
|
||||
Future<void> _release() async {
|
||||
final hasAccounts = _status?.hasAccounts ?? true;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: kSurface,
|
||||
title: const Text('Release device owner?'),
|
||||
// Deliberately concrete, and honest in both directions. "Restored only
|
||||
// by a factory reset" is true but abstract, and this dialog is read in
|
||||
// the moment the block is most inconvenient -- the price has to be
|
||||
// legible right then, not inferred. While no account exists the price
|
||||
// really is low, and overstating it there would teach the user to
|
||||
// ignore the warning that matters later.
|
||||
content: Text(
|
||||
_status?.hasAccounts ?? true
|
||||
? 'Enforcement stops. YouTube comes back.\n\n'
|
||||
'Accounts exist on this device, so device owner can NEVER '
|
||||
'be set again without a factory reset. That means '
|
||||
're-pairing mBank, Revolut and inFakt over SMS, '
|
||||
're-activating mObywatel, signing in to every account '
|
||||
'again, and losing Signal history.\n\n'
|
||||
'This is the way out if something is broken. It is a bad '
|
||||
'trade for wanting to watch a video.'
|
||||
: 'Enforcement stops and YouTube comes back.\n\n'
|
||||
'No account exists yet, so device owner can be set again '
|
||||
'straight afterwards without a wipe. This is the moment to '
|
||||
'test the release path, before signing in.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: TextButton.styleFrom(foregroundColor: kDanger),
|
||||
child: const Text('Release'),
|
||||
),
|
||||
],
|
||||
),
|
||||
builder: (context) => _releaseDialog(context, hasAccounts: hasAccounts),
|
||||
);
|
||||
if (confirmed != true) return;
|
||||
|
||||
@ -259,28 +208,20 @@ class _StatusPageState extends State<StatusPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final status = _status;
|
||||
final error = _error;
|
||||
final Widget body;
|
||||
if (error != null) {
|
||||
body = const _Message(text: 'Could not read state', color: kDanger);
|
||||
} else if (status != null) {
|
||||
body = _StatusBody(
|
||||
status: status,
|
||||
focusPolicy: _focusPolicy,
|
||||
policyError: _policyError,
|
||||
busy: _busy,
|
||||
onRelease: _release,
|
||||
onRunNow: _runNow,
|
||||
onSetHome: _setHome,
|
||||
onLockVpn: _lockVpn,
|
||||
hasHome: _hasHome,
|
||||
log: _log,
|
||||
onOpenLog: _openLog,
|
||||
);
|
||||
} else {
|
||||
body = const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
final body = _statusPageBody(
|
||||
status: _status,
|
||||
error: _error,
|
||||
focusPolicy: _focusPolicy,
|
||||
policyError: _policyError,
|
||||
busy: _busy,
|
||||
onRelease: _release,
|
||||
onRunNow: _runNow,
|
||||
onSetHome: _setHome,
|
||||
onLockVpn: _lockVpn,
|
||||
hasHome: _hasHome,
|
||||
log: _log,
|
||||
onOpenLog: _openLog,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user