neoagent 2.3.1-beta.54 → 2.3.1-beta.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/flutter_app/lib/main.dart +2 -0
- package/flutter_app/lib/main_app_shell.dart +210 -180
- package/flutter_app/lib/main_navigation.dart +2 -7
- package/flutter_app/lib/main_recordings.dart +382 -332
- package/flutter_app/lib/main_runtime.dart +30 -1
- package/flutter_app/lib/main_settings.dart +77 -0
- package/flutter_app/lib/main_shared.dart +494 -209
- package/flutter_app/lib/main_theme.dart +98 -45
- package/flutter_app/lib/src/theme/palette.dart +14 -14
- package/flutter_app/lib/src/web_app_update_monitor.dart +17 -0
- package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
- package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -0
- package/package.json +3 -3
- package/server/http/static.js +41 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +64921 -64558
|
@@ -59,83 +59,107 @@ class _RecordingsPanelState extends State<RecordingsPanel> {
|
|
|
59
59
|
return ListView(
|
|
60
60
|
padding: _pagePadding(context),
|
|
61
61
|
children: <Widget>[
|
|
62
|
-
const
|
|
63
|
-
|
|
62
|
+
const _PageTitle(
|
|
63
|
+
title: 'Recordings',
|
|
64
|
+
subtitle:
|
|
65
|
+
'Capture sessions, monitor device state, and review transcripts with clearer status and playback controls.',
|
|
66
|
+
),
|
|
64
67
|
Card(
|
|
65
68
|
child: Padding(
|
|
66
69
|
padding: const EdgeInsets.all(22),
|
|
67
70
|
child: Column(
|
|
68
71
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
69
72
|
children: <Widget>[
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
runSpacing: 10,
|
|
73
|
-
crossAxisAlignment: WrapCrossAlignment.center,
|
|
73
|
+
Row(
|
|
74
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
74
75
|
children: <Widget>[
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
Expanded(
|
|
77
|
+
child: Column(
|
|
78
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
79
|
+
children: <Widget>[
|
|
80
|
+
Text(
|
|
81
|
+
'Capture controls',
|
|
82
|
+
style: _displayTitleStyle(24),
|
|
83
|
+
),
|
|
84
|
+
const SizedBox(height: 6),
|
|
85
|
+
Text(
|
|
86
|
+
runtime.platformLabel?.trim().isNotEmpty == true
|
|
87
|
+
? runtime.platformLabel!
|
|
88
|
+
: 'Browser screen + microphone recorder',
|
|
89
|
+
style: TextStyle(
|
|
90
|
+
color: _textSecondary,
|
|
91
|
+
fontSize: 15,
|
|
92
|
+
height: 1.45,
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
],
|
|
84
96
|
),
|
|
97
|
+
),
|
|
98
|
+
const SizedBox(width: 16),
|
|
99
|
+
_StatusPill(label: statusLabel, color: statusColor),
|
|
85
100
|
],
|
|
86
101
|
),
|
|
87
102
|
const SizedBox(height: 16),
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
AnimatedSwitcher(
|
|
104
|
+
duration: const Duration(milliseconds: 220),
|
|
105
|
+
switchInCurve: Curves.easeOutCubic,
|
|
106
|
+
switchOutCurve: Curves.easeInCubic,
|
|
107
|
+
child: isStarting
|
|
108
|
+
? _GlassSurface(
|
|
109
|
+
key: const ValueKey<String>('starting'),
|
|
110
|
+
width: double.infinity,
|
|
111
|
+
padding: const EdgeInsets.symmetric(
|
|
112
|
+
horizontal: 14,
|
|
113
|
+
vertical: 12,
|
|
114
|
+
),
|
|
115
|
+
borderRadius: BorderRadius.circular(16),
|
|
116
|
+
blurSigma: 10,
|
|
117
|
+
fillColor: _bgCard.withValues(alpha: 0.88),
|
|
118
|
+
borderColor: _borderLight,
|
|
119
|
+
child: Row(
|
|
120
|
+
children: <Widget>[
|
|
121
|
+
const SizedBox.square(
|
|
122
|
+
dimension: 16,
|
|
123
|
+
child: CircularProgressIndicator(
|
|
124
|
+
strokeWidth: 2,
|
|
125
|
+
),
|
|
126
|
+
),
|
|
127
|
+
const SizedBox(width: 12),
|
|
128
|
+
Expanded(
|
|
129
|
+
child: Text(
|
|
130
|
+
'Starting recording. This can take a few seconds while the session and permissions are prepared.',
|
|
131
|
+
style: TextStyle(
|
|
132
|
+
color: _textSecondary,
|
|
133
|
+
height: 1.4,
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
],
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
: isStopping
|
|
141
|
+
? _GlassSurface(
|
|
142
|
+
key: const ValueKey<String>('stopping'),
|
|
143
|
+
width: double.infinity,
|
|
144
|
+
padding: const EdgeInsets.symmetric(
|
|
145
|
+
horizontal: 14,
|
|
146
|
+
vertical: 12,
|
|
147
|
+
),
|
|
148
|
+
borderRadius: BorderRadius.circular(16),
|
|
149
|
+
blurSigma: 10,
|
|
150
|
+
fillColor: _bgCard.withValues(alpha: 0.88),
|
|
151
|
+
borderColor: _borderLight,
|
|
109
152
|
child: Text(
|
|
110
|
-
'
|
|
153
|
+
'Finalizing recording...',
|
|
111
154
|
style: TextStyle(
|
|
112
155
|
color: _textSecondary,
|
|
113
156
|
height: 1.4,
|
|
114
157
|
),
|
|
115
158
|
),
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
] else if (isStopping) ...<Widget>[
|
|
121
|
-
const SizedBox(height: 14),
|
|
122
|
-
Container(
|
|
123
|
-
width: double.infinity,
|
|
124
|
-
padding: const EdgeInsets.symmetric(
|
|
125
|
-
horizontal: 14,
|
|
126
|
-
vertical: 12,
|
|
127
|
-
),
|
|
128
|
-
decoration: BoxDecoration(
|
|
129
|
-
color: _bgSecondary.withValues(alpha: 0.8),
|
|
130
|
-
borderRadius: BorderRadius.circular(16),
|
|
131
|
-
border: Border.all(color: _borderLight),
|
|
132
|
-
),
|
|
133
|
-
child: Text(
|
|
134
|
-
'Finalizing recording...',
|
|
135
|
-
style: TextStyle(color: _textSecondary, height: 1.4),
|
|
136
|
-
),
|
|
137
|
-
),
|
|
138
|
-
],
|
|
159
|
+
)
|
|
160
|
+
: const SizedBox.shrink(),
|
|
161
|
+
),
|
|
162
|
+
if (isStarting || isStopping) const SizedBox(height: 14),
|
|
139
163
|
const SizedBox(height: 18),
|
|
140
164
|
Wrap(
|
|
141
165
|
spacing: 12,
|
|
@@ -240,14 +264,13 @@ class _RecordingsPanelState extends State<RecordingsPanel> {
|
|
|
240
264
|
),
|
|
241
265
|
if (runtime.supportsSystemAudio) ...<Widget>[
|
|
242
266
|
const SizedBox(height: 20),
|
|
243
|
-
|
|
267
|
+
_GlassSurface(
|
|
244
268
|
width: double.infinity,
|
|
245
269
|
padding: const EdgeInsets.all(18),
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
),
|
|
270
|
+
borderRadius: BorderRadius.circular(22),
|
|
271
|
+
blurSigma: 10,
|
|
272
|
+
fillColor: _bgCard.withValues(alpha: 0.9),
|
|
273
|
+
borderColor: _borderLight,
|
|
251
274
|
child: Column(
|
|
252
275
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
253
276
|
children: <Widget>[
|
|
@@ -370,7 +393,6 @@ class _RecordingsPanelState extends State<RecordingsPanel> {
|
|
|
370
393
|
),
|
|
371
394
|
),
|
|
372
395
|
),
|
|
373
|
-
const SizedBox(height: 20),
|
|
374
396
|
const _SectionTitle('Transcripts'),
|
|
375
397
|
const SizedBox(height: 12),
|
|
376
398
|
if (widget.controller.recordingSessions.isEmpty)
|
|
@@ -426,296 +448,312 @@ class _RecordingSessionCard extends StatelessWidget {
|
|
|
426
448
|
final runtime = controller.recordingRuntime;
|
|
427
449
|
final isLiveSession = runtime.active && runtime.sessionId == session.id;
|
|
428
450
|
final canDeleteRecording = onDeleteRecording != null && !isLiveSession;
|
|
429
|
-
return
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
451
|
+
return AnimatedContainer(
|
|
452
|
+
duration: const Duration(milliseconds: 240),
|
|
453
|
+
curve: Curves.easeOutCubic,
|
|
454
|
+
child: Card(
|
|
455
|
+
child: Padding(
|
|
456
|
+
padding: const EdgeInsets.all(18),
|
|
457
|
+
child: Column(
|
|
458
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
459
|
+
children: <Widget>[
|
|
460
|
+
Row(
|
|
461
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
462
|
+
children: <Widget>[
|
|
463
|
+
Expanded(
|
|
464
|
+
child: Column(
|
|
465
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
466
|
+
children: <Widget>[
|
|
467
|
+
Text(
|
|
468
|
+
session.title,
|
|
469
|
+
style: TextStyle(
|
|
470
|
+
fontSize: 16,
|
|
471
|
+
fontWeight: FontWeight.w700,
|
|
472
|
+
),
|
|
447
473
|
),
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
474
|
+
const SizedBox(height: 6),
|
|
475
|
+
Text(
|
|
476
|
+
'${session.startedAtLabel} • ${session.platformLabel} • ${session.durationLabel}',
|
|
477
|
+
style: TextStyle(color: _textSecondary),
|
|
478
|
+
),
|
|
479
|
+
],
|
|
480
|
+
),
|
|
455
481
|
),
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
),
|
|
474
|
-
decoration: BoxDecoration(
|
|
475
|
-
color: _bgSecondary,
|
|
482
|
+
_StatusPill(
|
|
483
|
+
label: session.statusLabel,
|
|
484
|
+
color: session.statusColor,
|
|
485
|
+
),
|
|
486
|
+
],
|
|
487
|
+
),
|
|
488
|
+
const SizedBox(height: 14),
|
|
489
|
+
Wrap(
|
|
490
|
+
spacing: 8,
|
|
491
|
+
runSpacing: 8,
|
|
492
|
+
children: session.sources
|
|
493
|
+
.map(
|
|
494
|
+
(source) => _GlassSurface(
|
|
495
|
+
padding: const EdgeInsets.symmetric(
|
|
496
|
+
horizontal: 10,
|
|
497
|
+
vertical: 7,
|
|
498
|
+
),
|
|
476
499
|
borderRadius: BorderRadius.circular(999),
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
500
|
+
blurSigma: 8,
|
|
501
|
+
fillColor: _bgCard.withValues(alpha: 0.82),
|
|
502
|
+
borderColor: _border,
|
|
503
|
+
child: Text(
|
|
504
|
+
'${source.label} • ${source.durationLabel}',
|
|
505
|
+
style: TextStyle(fontSize: 12),
|
|
506
|
+
),
|
|
482
507
|
),
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
.toList(),
|
|
486
|
-
),
|
|
487
|
-
if (session.sources.any(
|
|
488
|
-
(source) => source.mediaKind == 'audio',
|
|
489
|
-
)) ...<Widget>[
|
|
490
|
-
const SizedBox(height: 12),
|
|
491
|
-
_RecordingSourceAudioControls(
|
|
492
|
-
controller: controller,
|
|
493
|
-
session: session,
|
|
508
|
+
)
|
|
509
|
+
.toList(),
|
|
494
510
|
),
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
session
|
|
502
|
-
style: TextStyle(color: _danger),
|
|
511
|
+
if (session.sources.any(
|
|
512
|
+
(source) => source.mediaKind == 'audio',
|
|
513
|
+
)) ...<Widget>[
|
|
514
|
+
const SizedBox(height: 12),
|
|
515
|
+
_RecordingSourceAudioControls(
|
|
516
|
+
controller: controller,
|
|
517
|
+
session: session,
|
|
503
518
|
),
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
519
|
+
],
|
|
520
|
+
if (session.lastError != null &&
|
|
521
|
+
session.lastError!.trim().isNotEmpty)
|
|
522
|
+
Padding(
|
|
523
|
+
padding: const EdgeInsets.only(top: 12),
|
|
524
|
+
child: Text(
|
|
525
|
+
session.lastError!,
|
|
526
|
+
style: TextStyle(color: _danger),
|
|
527
|
+
),
|
|
513
528
|
),
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
529
|
+
if (session.structuredContent.isNotEmpty) ...<Widget>[
|
|
530
|
+
const SizedBox(height: 16),
|
|
531
|
+
_GlassSurface(
|
|
532
|
+
padding: const EdgeInsets.all(16),
|
|
533
|
+
borderRadius: BorderRadius.circular(18),
|
|
534
|
+
blurSigma: 8,
|
|
535
|
+
fillColor: _accentMuted.withValues(alpha: 0.16),
|
|
536
|
+
borderColor: _accent.withValues(alpha: 0.26),
|
|
537
|
+
child: Column(
|
|
538
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
539
|
+
children: <Widget>[
|
|
540
|
+
Row(
|
|
541
|
+
children: <Widget>[
|
|
542
|
+
Icon(Icons.auto_awesome, size: 16, color: _accent),
|
|
543
|
+
const SizedBox(width: 8),
|
|
544
|
+
Text(
|
|
545
|
+
'Insights',
|
|
546
|
+
style: TextStyle(
|
|
547
|
+
color: _accent,
|
|
548
|
+
fontWeight: FontWeight.w600,
|
|
549
|
+
),
|
|
550
|
+
),
|
|
551
|
+
],
|
|
552
|
+
),
|
|
553
|
+
if (session.structuredContent['summary'] !=
|
|
554
|
+
null) ...<Widget>[
|
|
555
|
+
const SizedBox(height: 10),
|
|
521
556
|
Text(
|
|
522
|
-
'
|
|
557
|
+
'Summary',
|
|
523
558
|
style: TextStyle(
|
|
524
|
-
|
|
525
|
-
|
|
559
|
+
fontWeight: FontWeight.w700,
|
|
560
|
+
fontSize: 13,
|
|
561
|
+
color: _textSecondary,
|
|
526
562
|
),
|
|
527
563
|
),
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const SizedBox(height: 10),
|
|
533
|
-
Text(
|
|
534
|
-
'Summary',
|
|
535
|
-
style: TextStyle(
|
|
536
|
-
fontWeight: FontWeight.w700,
|
|
537
|
-
fontSize: 13,
|
|
538
|
-
color: _textSecondary,
|
|
564
|
+
const SizedBox(height: 4),
|
|
565
|
+
Text(
|
|
566
|
+
session.structuredContent['summary'].toString(),
|
|
567
|
+
style: TextStyle(height: 1.55, fontSize: 15),
|
|
539
568
|
),
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
'Action Items',
|
|
555
|
-
style: TextStyle(
|
|
556
|
-
fontWeight: FontWeight.w700,
|
|
557
|
-
fontSize: 13,
|
|
558
|
-
color: _textSecondary,
|
|
569
|
+
],
|
|
570
|
+
if (session.structuredContent['action_items'] != null &&
|
|
571
|
+
_getStructuredList(
|
|
572
|
+
session,
|
|
573
|
+
'action_items',
|
|
574
|
+
).isNotEmpty) ...<Widget>[
|
|
575
|
+
const SizedBox(height: 10),
|
|
576
|
+
Text(
|
|
577
|
+
'Action Items',
|
|
578
|
+
style: TextStyle(
|
|
579
|
+
fontWeight: FontWeight.w700,
|
|
580
|
+
fontSize: 13,
|
|
581
|
+
color: _textSecondary,
|
|
582
|
+
),
|
|
559
583
|
),
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
584
|
+
const SizedBox(height: 4),
|
|
585
|
+
..._getStructuredList(session, 'action_items').map(
|
|
586
|
+
(item) => Padding(
|
|
587
|
+
padding: const EdgeInsets.only(bottom: 4),
|
|
588
|
+
child: Row(
|
|
589
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
590
|
+
children: [
|
|
591
|
+
Text(
|
|
592
|
+
'• ',
|
|
593
|
+
style: TextStyle(
|
|
594
|
+
fontWeight: FontWeight.w700,
|
|
595
|
+
color: _accent,
|
|
596
|
+
),
|
|
573
597
|
),
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
598
|
+
Expanded(
|
|
599
|
+
child: Text(
|
|
600
|
+
item.toString(),
|
|
601
|
+
style: TextStyle(height: 1.35),
|
|
602
|
+
),
|
|
579
603
|
),
|
|
580
|
-
|
|
581
|
-
|
|
604
|
+
],
|
|
605
|
+
),
|
|
582
606
|
),
|
|
583
607
|
),
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
)
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
608
|
+
],
|
|
609
|
+
if (session.structuredContent['events'] != null &&
|
|
610
|
+
_getStructuredList(
|
|
611
|
+
session,
|
|
612
|
+
'events',
|
|
613
|
+
).isNotEmpty) ...<Widget>[
|
|
614
|
+
const SizedBox(height: 10),
|
|
615
|
+
Text(
|
|
616
|
+
'Events Mentioned',
|
|
617
|
+
style: TextStyle(
|
|
618
|
+
fontWeight: FontWeight.w700,
|
|
619
|
+
fontSize: 13,
|
|
620
|
+
color: _textSecondary,
|
|
621
|
+
),
|
|
598
622
|
),
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
623
|
+
const SizedBox(height: 4),
|
|
624
|
+
..._getStructuredList(session, 'events').map(
|
|
625
|
+
(item) => Padding(
|
|
626
|
+
padding: const EdgeInsets.only(bottom: 4),
|
|
627
|
+
child: Row(
|
|
628
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
629
|
+
children: [
|
|
630
|
+
Text(
|
|
631
|
+
'• ',
|
|
632
|
+
style: TextStyle(
|
|
633
|
+
fontWeight: FontWeight.w700,
|
|
634
|
+
color: _accent,
|
|
635
|
+
),
|
|
612
636
|
),
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
637
|
+
Expanded(
|
|
638
|
+
child: Text(
|
|
639
|
+
item.toString(),
|
|
640
|
+
style: TextStyle(height: 1.35),
|
|
641
|
+
),
|
|
618
642
|
),
|
|
619
|
-
|
|
620
|
-
|
|
643
|
+
],
|
|
644
|
+
),
|
|
621
645
|
),
|
|
622
646
|
),
|
|
623
|
-
|
|
647
|
+
],
|
|
624
648
|
],
|
|
625
|
-
|
|
649
|
+
),
|
|
626
650
|
),
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
651
|
+
],
|
|
652
|
+
if (session.transcriptSegments.isNotEmpty) ...<Widget>[
|
|
653
|
+
const SizedBox(height: 16),
|
|
654
|
+
...session.transcriptSegments.map(
|
|
655
|
+
(segment) => Padding(
|
|
656
|
+
padding: const EdgeInsets.only(bottom: 10),
|
|
657
|
+
child: _GlassSurface(
|
|
658
|
+
padding: const EdgeInsets.fromLTRB(14, 12, 10, 12),
|
|
659
|
+
borderRadius: BorderRadius.circular(16),
|
|
660
|
+
blurSigma: 6,
|
|
661
|
+
fillColor: _bgCard.withValues(alpha: 0.9),
|
|
662
|
+
borderColor: _border,
|
|
663
|
+
child: Row(
|
|
664
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
665
|
+
children: <Widget>[
|
|
666
|
+
SizedBox(
|
|
667
|
+
width: 88,
|
|
668
|
+
child: Text(
|
|
669
|
+
segment.timestampLabel,
|
|
670
|
+
style: TextStyle(
|
|
671
|
+
color: _textSecondary,
|
|
672
|
+
fontWeight: FontWeight.w600,
|
|
673
|
+
),
|
|
674
|
+
),
|
|
675
|
+
),
|
|
676
|
+
Expanded(
|
|
677
|
+
child: Text(
|
|
678
|
+
segment.displayText,
|
|
679
|
+
style: const TextStyle(
|
|
680
|
+
height: 1.55,
|
|
681
|
+
fontSize: 15,
|
|
682
|
+
),
|
|
683
|
+
),
|
|
684
|
+
),
|
|
685
|
+
if (onDeleteSegment != null &&
|
|
686
|
+
segment.id > 0) ...<Widget>[
|
|
687
|
+
const SizedBox(width: 8),
|
|
688
|
+
IconButton(
|
|
689
|
+
onPressed: () async {
|
|
690
|
+
await onDeleteSegment!(segment);
|
|
691
|
+
},
|
|
692
|
+
icon: Icon(Icons.delete_outline),
|
|
693
|
+
tooltip: 'Delete segment',
|
|
694
|
+
visualDensity: VisualDensity.compact,
|
|
695
|
+
),
|
|
696
|
+
],
|
|
697
|
+
],
|
|
649
698
|
),
|
|
650
|
-
|
|
651
|
-
segment.id > 0) ...<Widget>[
|
|
652
|
-
const SizedBox(width: 8),
|
|
653
|
-
IconButton(
|
|
654
|
-
onPressed: () async {
|
|
655
|
-
await onDeleteSegment!(segment);
|
|
656
|
-
},
|
|
657
|
-
icon: Icon(Icons.delete_outline),
|
|
658
|
-
tooltip: 'Delete segment',
|
|
659
|
-
visualDensity: VisualDensity.compact,
|
|
660
|
-
),
|
|
661
|
-
],
|
|
662
|
-
],
|
|
699
|
+
),
|
|
663
700
|
),
|
|
664
701
|
),
|
|
665
|
-
)
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
702
|
+
] else if (session.transcriptText.isNotEmpty) ...<Widget>[
|
|
703
|
+
const SizedBox(height: 16),
|
|
704
|
+
_GlassSurface(
|
|
705
|
+
width: double.infinity,
|
|
706
|
+
padding: const EdgeInsets.all(16),
|
|
707
|
+
borderRadius: BorderRadius.circular(16),
|
|
708
|
+
blurSigma: 8,
|
|
709
|
+
fillColor: _bgCard.withValues(alpha: 0.92),
|
|
710
|
+
borderColor: _border,
|
|
711
|
+
child: SelectableText(
|
|
712
|
+
session.transcriptText,
|
|
713
|
+
style: const TextStyle(height: 1.6, fontSize: 15),
|
|
714
|
+
),
|
|
675
715
|
),
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
716
|
+
] else ...<Widget>[
|
|
717
|
+
const SizedBox(height: 16),
|
|
718
|
+
Text(
|
|
719
|
+
session.status == 'processing'
|
|
720
|
+
? 'Transcribing...'
|
|
721
|
+
: session.status == 'failed'
|
|
722
|
+
? 'Transcription failed. Check the error above and retry.'
|
|
723
|
+
: session.status == 'completed'
|
|
724
|
+
? 'No transcript text was returned. You can retry transcription.'
|
|
725
|
+
: 'Transcript is not available yet.',
|
|
726
|
+
style: TextStyle(color: _textSecondary),
|
|
679
727
|
),
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
OutlinedButton.icon(
|
|
708
|
-
onPressed: () async {
|
|
709
|
-
await onDeleteRecording!();
|
|
710
|
-
},
|
|
711
|
-
icon: Icon(Icons.delete_forever_outlined),
|
|
712
|
-
label: Text('Delete recording'),
|
|
713
|
-
style: OutlinedButton.styleFrom(foregroundColor: _danger),
|
|
714
|
-
),
|
|
715
|
-
],
|
|
716
|
-
),
|
|
728
|
+
],
|
|
729
|
+
if (onRetry != null || canDeleteRecording) ...<Widget>[
|
|
730
|
+
const SizedBox(height: 14),
|
|
731
|
+
Wrap(
|
|
732
|
+
spacing: 10,
|
|
733
|
+
runSpacing: 10,
|
|
734
|
+
children: <Widget>[
|
|
735
|
+
if (onRetry != null)
|
|
736
|
+
OutlinedButton.icon(
|
|
737
|
+
onPressed: onRetry,
|
|
738
|
+
icon: Icon(Icons.replay),
|
|
739
|
+
label: Text('Retry transcription'),
|
|
740
|
+
),
|
|
741
|
+
if (canDeleteRecording)
|
|
742
|
+
OutlinedButton.icon(
|
|
743
|
+
onPressed: () async {
|
|
744
|
+
await onDeleteRecording!();
|
|
745
|
+
},
|
|
746
|
+
icon: Icon(Icons.delete_forever_outlined),
|
|
747
|
+
label: Text('Delete recording'),
|
|
748
|
+
style: OutlinedButton.styleFrom(
|
|
749
|
+
foregroundColor: _danger,
|
|
750
|
+
),
|
|
751
|
+
),
|
|
752
|
+
],
|
|
753
|
+
),
|
|
754
|
+
],
|
|
717
755
|
],
|
|
718
|
-
|
|
756
|
+
),
|
|
719
757
|
),
|
|
720
758
|
),
|
|
721
759
|
);
|
|
@@ -857,11 +895,23 @@ class _RecordingSourceAudioControlsState
|
|
|
857
895
|
runSpacing: 8,
|
|
858
896
|
children: audioSources.map((source) {
|
|
859
897
|
final isActive = _isPlaying && _activeSourceKey == source.sourceKey;
|
|
860
|
-
return
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
898
|
+
return AnimatedScale(
|
|
899
|
+
duration: const Duration(milliseconds: 180),
|
|
900
|
+
curve: Curves.easeOutCubic,
|
|
901
|
+
scale: isActive ? 1.02 : 1,
|
|
902
|
+
child: OutlinedButton.icon(
|
|
903
|
+
onPressed: () => _toggleSource(source),
|
|
904
|
+
icon: Icon(
|
|
905
|
+
isActive ? Icons.stop_circle_outlined : Icons.play_arrow,
|
|
906
|
+
),
|
|
907
|
+
label: Text(
|
|
908
|
+
isActive ? 'Stop ${source.label}' : 'Play ${source.label}',
|
|
909
|
+
),
|
|
910
|
+
style: OutlinedButton.styleFrom(
|
|
911
|
+
backgroundColor: isActive
|
|
912
|
+
? _accentMuted.withValues(alpha: 0.2)
|
|
913
|
+
: null,
|
|
914
|
+
),
|
|
865
915
|
),
|
|
866
916
|
);
|
|
867
917
|
}).toList(),
|