1818import 'package:flutter/material.dart' ;
1919import 'package:get/get.dart' ;
2020
21+ import '../animated_switcher.dart' ;
2122import '../widget_button.dart' ;
2223import '/domain/model/attachment.dart' ;
2324import '/l10n/l10n.dart' ;
2425import '/themes.dart' ;
25- import '/ui/worker/audio.dart' ;
2626import '/util/audio_utils.dart' ;
27+ import 'controller.dart' ;
2728
2829/// Audio player with controls.
2930class AudioPlayer extends StatefulWidget {
@@ -48,108 +49,108 @@ class AudioPlayer extends StatefulWidget {
4849}
4950
5051class _AudioPlayerState extends State <AudioPlayer > {
51- final AudioWorker _worker = Get .find ();
52-
5352 bool _hovered = false ;
54- bool _wasPlaying = false ;
55-
56- double _getSliderValue (Duration position, Duration duration) {
57- final posMs = position.inMilliseconds.toDouble ();
58- final durMs = duration.inMilliseconds.toDouble ();
59- if (durMs <= 0 ) return 0.0 ;
60- return posMs.clamp (0.0 , durMs);
61- }
6253
6354 @override
6455 Widget build (BuildContext context) {
6556 final style = Theme .of (context).style;
6657
67- return Obx (() {
68- final bool isActive = _worker.activeAudioId.value == widget.id.val;
69- final bool isPlaying = _worker.isPlaying.value && isActive;
70- final bool isLoading = _worker.isLoading.value && isActive;
71- final position = _worker.position.value;
72- final duration = _worker.duration.value;
73-
74- return Padding (
75- padding: const EdgeInsets .all (8.0 ),
76- child: SizedBox (
77- height: 48 ,
78- child: Row (
79- children: [
80- MouseRegion (
81- onEnter: (_) => setState (() => _hovered = true ),
82- onExit: (_) => setState (() => _hovered = false ),
83- child: WidgetButton (
84- key: Key ('PlayerButton${widget .id }' ),
85- onPressed: () {
86- if (isActive && isPlaying) {
87- _worker.pause ();
88- } else {
89- _worker.play (widget.id.val, widget.source);
90- }
91- },
92- child: AnimatedContainer (
93- duration: const Duration (milliseconds: 200 ),
94- height: 48 ,
95- width: 48 ,
96- decoration: BoxDecoration (
97- shape: BoxShape .circle,
98- color: _hovered
99- ? style.colors.backgroundAuxiliaryLighter
100- : null ,
101- border: Border .all (width: 2 , color: style.colors.primary),
58+ return GetBuilder <AudioPlayerController >(
59+ init: AudioPlayerController (
60+ Get .find (),
61+ id: widget.id,
62+ source: widget.source,
63+ ),
64+ tag: widget.id.val,
65+ builder: (c) {
66+ return Padding (
67+ padding: const EdgeInsets .all (8.0 ),
68+ child: SizedBox (
69+ height: 48 ,
70+ child: Row (
71+ children: [
72+ MouseRegion (
73+ onEnter: (_) => setState (() => _hovered = true ),
74+ onExit: (_) => setState (() => _hovered = false ),
75+ child: WidgetButton (
76+ key: Key ('PlayerButton${widget .id }' ),
77+ onPressed: () {
78+ c.togglePlay ();
79+ },
80+ child: AnimatedContainer (
81+ duration: const Duration (milliseconds: 200 ),
82+ height: 48 ,
83+ width: 48 ,
84+ decoration: BoxDecoration (
85+ shape: BoxShape .circle,
86+ color: _hovered
87+ ? style.colors.backgroundAuxiliaryLighter
88+ : null ,
89+ border: Border .all (
90+ width: 2 ,
91+ color: style.colors.primary,
92+ ),
93+ ),
94+ child: Obx (
95+ () => SafeAnimatedSwitcher (
96+ duration: const Duration (milliseconds: 200 ),
97+ child: c.isLoading
98+ ? Padding (
99+ key: const ValueKey ('loader' ),
100+ padding: const EdgeInsets .all (8.0 ),
101+ child: const CircularProgressIndicator (),
102+ )
103+ : Center (
104+ key: ValueKey ('icon_${c .isPlaying }' ),
105+ child: Icon (
106+ c.isPlaying
107+ ? Icons .pause_rounded
108+ : Icons .play_arrow_rounded,
109+ size: 36 ,
110+ color: const Color (0xFF1F3C5D ),
111+ ),
112+ ),
113+ ),
114+ ),
102115 ),
103- child: isLoading
104- ? Padding (
105- padding: const EdgeInsets .all (8.0 ),
106- child: const CircularProgressIndicator (),
107- )
108- : Center (
109- child: Icon (
110- isPlaying
111- ? Icons .pause_rounded
112- : Icons .play_arrow_rounded,
113- size: 36 ,
114- color: const Color (0xFF1F3C5D ),
115- ),
116- ),
117116 ),
118117 ),
119- ),
120- const SizedBox (width: 16 ),
121- Expanded (
122- child: Column (
123- crossAxisAlignment: CrossAxisAlignment .start,
124- mainAxisAlignment: MainAxisAlignment .start,
125- children: [
126- Text (
127- widget.filename,
128- style: style.fonts.small.regular.onBackground,
129- maxLines: 1 ,
130- overflow: TextOverflow .ellipsis,
131- ),
118+ const SizedBox (width: 16 ),
119+ Expanded (
120+ child: Column (
121+ crossAxisAlignment: CrossAxisAlignment .start,
122+ mainAxisAlignment: MainAxisAlignment .start,
123+ children: [
124+ Text (
125+ widget.filename,
126+ style: style.fonts.small.regular.onBackground,
127+ maxLines: 1 ,
128+ overflow: TextOverflow .ellipsis,
129+ ),
132130
133- AnimatedSwitcher (
134- duration: const Duration (milliseconds: 300 ),
135- child: isActive
136- ? KeyedSubtree (
137- key: const ValueKey ('timeline' ),
138- child: _buildTimeline (position, duration, style),
139- )
140- : const SizedBox .shrink (key: ValueKey ('empty' )),
141- ),
142- ],
131+ Obx (
132+ () => AnimatedSwitcher (
133+ duration: const Duration (milliseconds: 300 ),
134+ child: c.isActive
135+ ? KeyedSubtree (
136+ key: const ValueKey ('timeline' ),
137+ child: _buildTimeline (c, style),
138+ )
139+ : const SizedBox .shrink (key: ValueKey ('empty' )),
140+ ),
141+ ),
142+ ],
143+ ),
143144 ),
144- ) ,
145- ] ,
145+ ] ,
146+ ) ,
146147 ),
147- ),
148- );
149- } );
148+ );
149+ },
150+ );
150151 }
151152
152- Widget _buildTimeline (Duration position, Duration duration , Style style) {
153+ Widget _buildTimeline (AudioPlayerController c , Style style) {
153154 return Column (
154155 children: [
155156 SliderTheme (
@@ -164,32 +165,25 @@ class _AudioPlayerState extends State<AudioPlayer> {
164165 height: 17 ,
165166 child: Slider (
166167 key: Key ('AudioSlider${widget .id }' ),
167- onChangeStart: (_) {
168- _wasPlaying = _worker.isPlaying.value;
169- _worker.pause ();
170- },
171- onChangeEnd: (_) {
172- if (_wasPlaying) {
173- _worker.play (widget.id.val, widget.source);
174- }
175- },
176- value: _getSliderValue (position, duration),
177- max: duration.inMilliseconds.toDouble () > 0
178- ? duration.inMilliseconds.toDouble ()
168+ onChangeStart: (_) => c.onSliderChangeStart (),
169+ onChangeEnd: (v) => c.onSliderChangeEnd (),
170+ value: c.getSliderValue (),
171+ max: c.duration.inMilliseconds.toDouble () > 0
172+ ? c.duration.inMilliseconds.toDouble ()
179173 : 1.0 ,
180- onChanged: (v) => _worker. seek ( Duration (milliseconds: v.toInt () )),
174+ onChanged: (v) => c.position = Duration (milliseconds: v.toInt ()),
181175 ),
182176 ),
183177 ),
184178 Row (
185179 children: [
186180 Text (
187- position.hhMmSs (),
181+ c. position.hhMmSs (),
188182 style: style.fonts.smaller.regular.secondary,
189183 ),
190184 Text (' / ' , style: style.fonts.smaller.regular.secondary),
191185 Text (
192- duration.hhMmSs (),
186+ c. duration.hhMmSs (),
193187 style: style.fonts.smaller.regular.secondary,
194188 ),
195189 ],
0 commit comments