@@ -6,7 +6,6 @@ import 'package:flutter_it/flutter_it.dart';
66import 'package:gap/gap.dart' ;
77import 'package:logging/logging.dart' ;
88import 'package:printing/printing.dart' ;
9- import 'package:school_data_hub_client/school_data_hub_client.dart' ;
109import 'package:school_data_hub_flutter/app_utils/pdf_viewer_page.dart' ;
1110import 'package:school_data_hub_flutter/common/theme/app_colors.dart' ;
1211import 'package:school_data_hub_flutter/common/theme/styles.dart' ;
@@ -16,6 +15,7 @@ import 'package:school_data_hub_flutter/features/user/data/staff_excel_import_pa
1615import 'package:school_data_hub_flutter/features/user/domain/batch_create_result.dart' ;
1716import 'package:school_data_hub_flutter/features/user/domain/user_manager.dart' ;
1817import 'package:school_data_hub_flutter/features/user/presentation/batch_import_users/staff_credentials_pdf_service.dart' ;
18+ import 'package:wakelock_plus/wakelock_plus.dart' ;
1919
2020final _log = Logger ('BatchImportUsersPage' );
2121
@@ -32,11 +32,12 @@ class _BatchImportUsersPageState extends State<BatchImportUsersPage> {
3232 bool _isCreating = false ;
3333 int _progressCreated = 0 ;
3434 int _progressErrors = 0 ;
35- StreamSubscription <BatchCreateUserEvent >? _streamSubscription ;
35+ StreamSubscription <BatchCreateResult >? _chunkSubscription ;
3636
3737 @override
3838 void dispose () {
39- _streamSubscription? .cancel ();
39+ _chunkSubscription? .cancel ();
40+ WakelockPlus .disable ();
4041 super .dispose ();
4142 }
4243
@@ -63,89 +64,90 @@ class _BatchImportUsersPageState extends State<BatchImportUsersPage> {
6364 _progressCreated = 0 ;
6465 _progressErrors = 0 ;
6566 });
67+
68+ await WakelockPlus .enable ();
69+ _log.info ('[BatchImport] Wakelock enabled' );
70+
6671 final userManager = di <UserManager >();
67- final credentials = < StaffCredentialEntry > [];
68- final errors = < BatchCreateError > [];
72+ final allCredentials = < StaffCredentialEntry > [];
73+ final allErrors = < BatchCreateError > [];
6974
7075 try {
71- _log.info ('[BatchImport] Getting stream from UserManager' );
72- final stream = userManager.batchCreateUsersStreamFromImportRows (rows);
73- _log.info ('[BatchImport] Subscribing to batchCreateUsersStream' );
74- _streamSubscription = stream.listen (
75- (event) {
76+ final stream = userManager.batchCreateUsersInChunks (rows);
77+ _log.info ('[BatchImport] Subscribing to batchCreateUsersInChunks' );
78+ _chunkSubscription = stream.listen (
79+ (chunkResult) {
7680 if (! mounted) return ;
77- if (event.credential != null ) {
78- final c = event.credential! ;
79- credentials.add (
80- StaffCredentialEntry (
81- userName: c.userName,
82- fullName: c.fullName,
83- email: c.email,
84- password: c.password,
85- ),
86- );
87- setState (() => _progressCreated = credentials.length);
88- _log.info (
89- '[BatchImport] Event: created ${credentials .length } — ${c .userName }' ,
90- );
91- } else if (event.error != null ) {
92- final e = event.error! ;
93- errors.add (
94- BatchCreateError (
95- rowIndex: e.rowIndex,
96- userNameOrKurzel: e.userNameOrKurzel,
97- message: e.message,
98- ),
99- );
100- setState (() => _progressErrors = errors.length);
101- _log.info (
102- '[BatchImport] Event: error ${errors .length } — row ${e .rowIndex } ${e .userNameOrKurzel }: ${e .message }' ,
103- );
104- }
81+ allCredentials.addAll (chunkResult.credentials);
82+ allErrors.addAll (chunkResult.errors);
83+ setState (() {
84+ _progressCreated = allCredentials.length;
85+ _progressErrors = allErrors.length;
86+ });
87+ _log.info (
88+ '[BatchImport] Chunk done — created=${chunkResult .successCount }, '
89+ 'errors=${chunkResult .failureCount }, '
90+ 'total created=$_progressCreated , total errors=$_progressErrors ' ,
91+ );
10592 },
10693 onError: (Object e, StackTrace ? st) {
107- _log.severe ('[BatchImport] Stream onError' , e, st);
94+ _log.severe ('[BatchImport] Chunk stream onError' , e, st);
10895 if (mounted) {
10996 setState (() => _isCreating = false );
110- ScaffoldMessenger .of (
111- context ,
112- ). showSnackBar ( SnackBar (content : Text ( 'Fehler: $ e ' ))) ;
97+ ScaffoldMessenger .of (context). showSnackBar (
98+ SnackBar (content : Text ( 'Fehler: $ e ' )) ,
99+ );
113100 }
101+ WakelockPlus .disable ();
114102 },
115- onDone: () async {
103+ onDone: () {
116104 _log.info (
117- '[BatchImport] Stream onDone — credentials=${credentials .length } errors=${errors .length }' ,
105+ '[BatchImport] All chunks done — '
106+ 'credentials=${allCredentials .length } errors=${allErrors .length }' ,
118107 );
119- if (! mounted) return ;
120- _log.info ('[BatchImport] Refreshing user list' );
121- await userManager.fetchUsersCommand.runAsync ();
108+ WakelockPlus .disable ();
122109 if (! mounted) return ;
123110 setState (() {
124111 _batchResult = BatchCreateResult (
125- credentials: credentials ,
126- errors: errors ,
112+ credentials: allCredentials ,
113+ errors: allErrors ,
127114 );
128115 _isCreating = false ;
129116 _progressCreated = 0 ;
130117 _progressErrors = 0 ;
131118 });
132- _log.info (
133- '[BatchImport] Batch complete. Success: ${credentials .length }, failures: ${errors .length }' ,
134- );
135119 },
136120 cancelOnError: false ,
137121 );
138122 } catch (e, st) {
139123 _log.severe ('[BatchImport] _createUsers catch' , e, st);
124+ await WakelockPlus .disable ();
140125 if (mounted) {
141126 setState (() => _isCreating = false );
142- ScaffoldMessenger .of (
143- context ,
144- ). showSnackBar ( SnackBar (content : Text ( 'Fehler: $ e ' ))) ;
127+ ScaffoldMessenger .of (context). showSnackBar (
128+ SnackBar (content : Text ( 'Fehler: $ e ' )) ,
129+ );
145130 }
146131 }
147132 }
148133
134+ void _abortCreate () {
135+ _log.info ('[BatchImport] User aborted batch create' );
136+ _chunkSubscription? .cancel ();
137+ _chunkSubscription = null ;
138+ WakelockPlus .disable ();
139+ if (mounted) {
140+ setState (() {
141+ _isCreating = false ;
142+ _progressCreated = 0 ;
143+ _progressErrors = 0 ;
144+ });
145+ ScaffoldMessenger .of (context).showSnackBar (
146+ const SnackBar (content: Text ('Import abgebrochen.' )),
147+ );
148+ }
149+ }
150+
149151 Future <void > _printCredentials () async {
150152 final credentials = _batchResult? .credentials ?? [];
151153 if (credentials.isEmpty) return ;
@@ -262,21 +264,33 @@ class _BatchImportUsersPageState extends State<BatchImportUsersPage> {
262264 style: AppStyles .subtitle,
263265 ),
264266 const Gap (8 ),
265- ElevatedButton .icon (
266- style: AppStyles .actionButtonStyle,
267- onPressed: _isCreating ? null : _createUsers,
268- icon: _isCreating
269- ? const SizedBox (
270- width: 20 ,
271- height: 20 ,
272- child: CircularProgressIndicator (strokeWidth: 2 ),
273- )
274- : const Icon (Icons .person_add),
275- label: Text (
276- _isCreating
277- ? 'Wird erstellt… ($_progressCreated / ${_parseResult !.rows .length }, $_progressErrors Fehler)'
278- : 'Benutzer anlegen' ,
279- ),
267+ Row (
268+ children: [
269+ ElevatedButton .icon (
270+ style: AppStyles .actionButtonStyle,
271+ onPressed: _isCreating ? null : _createUsers,
272+ icon: _isCreating
273+ ? const SizedBox (
274+ width: 20 ,
275+ height: 20 ,
276+ child: CircularProgressIndicator (strokeWidth: 2 ),
277+ )
278+ : const Icon (Icons .person_add),
279+ label: Text (
280+ _isCreating
281+ ? 'Wird erstellt… ($_progressCreated / ${_parseResult !.rows .length }, $_progressErrors Fehler)'
282+ : 'Benutzer anlegen' ,
283+ ),
284+ ),
285+ if (_isCreating) ...[
286+ const Gap (12 ),
287+ OutlinedButton .icon (
288+ onPressed: _abortCreate,
289+ icon: const Icon (Icons .cancel_outlined),
290+ label: const Text ('Abbrechen' ),
291+ ),
292+ ],
293+ ],
280294 ),
281295 ],
282296 ],
0 commit comments