@@ -15,70 +15,193 @@ class UploadStatusPage extends StatefulWidget {
1515}
1616
1717class _UploadStatusPageState extends State <UploadStatusPage > {
18+ final PageController _pageController = PageController ();
19+ final ScrollController _scrollController = ScrollController ();
20+
21+ int _page = 0 ;
22+
1823 @override
1924 Widget build (BuildContext context) {
2025 return Scaffold (
2126 appBar: AppBar (
27+ centerTitle: true ,
2228 scrolledUnderElevation: 5.0 ,
2329 title: Text (
24- appStrings.tabBar_upload ,
30+ appStrings.uploadList_title ,
2531 style: Theme .of (context).appBarTheme.titleTextStyle,
2632 ),
2733 ),
28- body: Consumer <UploadNotifier >(builder: (context, uploadNotifier, child) {
29- if (uploadNotifier.uploadList.isEmpty) {
30- return Center (
31- child: Text (appStrings.noImages),
32- );
33- }
34- return ListView .separated (
35- padding: EdgeInsets .symmetric (vertical: 8.0 ),
36- itemCount: uploadNotifier.uploadList.length,
37- itemBuilder: (context, index) {
38- UploadItem item = uploadNotifier.uploadList[index];
39- return ListTile (
40- dense: true ,
41- leading: ClipRRect (
42- borderRadius: BorderRadius .circular (5.0 ),
43- child: AspectRatio (
44- aspectRatio: 1 ,
45- child: Image .file (
46- item.file,
47- fit: BoxFit .cover,
48- ),
34+ body: PageView (
35+ controller: _pageController,
36+ onPageChanged: (page) => setState (() {
37+ _page = page;
38+ }),
39+ children: [
40+ _buildUploadList,
41+ _buildHistoryList,
42+ ],
43+ ),
44+ floatingActionButton: ScrollUpFloatingButton (
45+ controller: _scrollController,
46+ ),
47+ bottomNavigationBar: BottomNavigationBar (
48+ currentIndex: _page,
49+ onTap: (index) => _pageController.animateToPage (
50+ index,
51+ duration: const Duration (milliseconds: 300 ),
52+ curve: Curves .ease,
53+ ),
54+ items: [
55+ BottomNavigationBarItem (
56+ icon: Icon (Icons .list),
57+ label: appStrings.uploadList_uploading,
58+ ),
59+ BottomNavigationBarItem (
60+ icon: Icon (Icons .history),
61+ label: appStrings.uploadList_history,
62+ ),
63+ ],
64+ ),
65+ );
66+ }
67+
68+ Widget get _buildUploadList {
69+ return Consumer <UploadNotifier >(builder: (context, uploadNotifier, child) {
70+ if (uploadNotifier.uploadList.isEmpty) {
71+ return Center (
72+ child: Text (appStrings.noImages),
73+ );
74+ }
75+ return ListView .separated (
76+ padding: EdgeInsets .symmetric (vertical: 8.0 ),
77+ itemCount: uploadNotifier.uploadList.length,
78+ itemBuilder: (context, index) {
79+ UploadItem item = uploadNotifier.uploadList[index];
80+ return ListTile (
81+ dense: true ,
82+ leading: ClipRRect (
83+ borderRadius: BorderRadius .circular (5.0 ),
84+ child: AspectRatio (
85+ aspectRatio: 1 ,
86+ child: Image .file (
87+ item.file,
88+ fit: BoxFit .cover,
4989 ),
5090 ),
51- title: Text (
52- item.file.path.split ('/' ).last,
53- style: Theme .of (context).textTheme.bodyMedium,
54- ),
55- subtitle: StreamBuilder <double >(
56- stream: item.progress.stream,
57- initialData: 0.0 ,
58- builder: (context, snapshot) {
59- if (snapshot.hasData) {
60- return LinearProgressIndicator (
61- backgroundColor: Theme .of (context).colorScheme.secondary.withOpacity (0.3 ),
62- value: min (snapshot.data! , 1.0 ),
63- );
64- }
65- return LinearProgressIndicator ();
66- },
67- ),
68- trailing: IconButton (
69- onPressed: () {
70- item.cancelToken.cancel ();
71- uploadNotifier.itemUploadCompleted (item);
72- },
73- icon: Icon (Icons .close),
74- ),
75- );
76- },
77- separatorBuilder: (context, index) {
78- return const Divider ();
79- },
91+ ),
92+ title: Text (
93+ item.file.path.split ('/' ).last,
94+ style: Theme .of (context).textTheme.bodyMedium,
95+ ),
96+ subtitle: StreamBuilder <double >(
97+ stream: item.progress.stream,
98+ initialData: 0.0 ,
99+ builder: (context, snapshot) {
100+ if (snapshot.hasData) {
101+ return LinearProgressIndicator (
102+ backgroundColor: Theme .of (context).colorScheme.secondary.withOpacity (0.3 ),
103+ value: min (snapshot.data! , 1.0 ),
104+ );
105+ }
106+ return LinearProgressIndicator ();
107+ },
108+ ),
109+ trailing: IconButton (
110+ onPressed: () {
111+ item.cancelToken.cancel ();
112+ uploadNotifier.itemUploadCompleted (item);
113+ },
114+ icon: Icon (Icons .close),
115+ ),
116+ );
117+ },
118+ separatorBuilder: (context, index) {
119+ return const Divider ();
120+ },
121+ );
122+ });
123+ }
124+
125+ Widget get _buildHistoryList {
126+ return Consumer <UploadNotifier >(builder: (context, uploadNotifier, child) {
127+ if (uploadNotifier.uploadHistoryList.isEmpty) {
128+ return Center (
129+ child: Text (appStrings.noImages),
80130 );
81- }),
131+ }
132+ return ListView .separated (
133+ controller: _scrollController,
134+ padding: EdgeInsets .symmetric (vertical: 8.0 ),
135+ itemCount: uploadNotifier.uploadHistoryList.length,
136+ itemBuilder: (context, index) {
137+ UploadItem item = uploadNotifier.uploadHistoryList[index];
138+ return ListTile (
139+ dense: true ,
140+ leading: ClipRRect (
141+ borderRadius: BorderRadius .circular (5.0 ),
142+ child: AspectRatio (
143+ aspectRatio: 1 ,
144+ child: Image .file (
145+ item.file,
146+ fit: BoxFit .cover,
147+ ),
148+ ),
149+ ),
150+ title: Text (
151+ item.file.path.split ('/' ).last,
152+ style: Theme .of (context).textTheme.bodyMedium,
153+ ),
154+ subtitle: Builder (builder: (context) {
155+ if (item.error) {
156+ return Text (appStrings.errorHUD_label);
157+ } else if (item.cancelToken.isCancelled) {
158+ return Text (appStrings.uploadCancelled_title);
159+ }
160+ return Text (appStrings.completeHUD_label);
161+ }),
162+ );
163+ },
164+ separatorBuilder: (context, index) {
165+ return const Divider ();
166+ },
167+ );
168+ });
169+ }
170+ }
171+
172+ class ScrollUpFloatingButton extends AnimatedWidget {
173+ const ScrollUpFloatingButton ({
174+ Key ? key,
175+ required this .controller,
176+ }) : super (key: key, listenable: controller);
177+
178+ static const Duration showDuration = Duration (milliseconds: 300 );
179+ static const Duration scrollDuration = Duration (milliseconds: 700 );
180+ static const Curve showCurve = Curves .ease;
181+
182+ final ScrollController controller;
183+
184+ @override
185+ Widget build (BuildContext context) {
186+ bool isHidden = ! controller.hasClients || controller.offset < kToolbarHeight;
187+ return AnimatedScale (
188+ duration: showDuration,
189+ curve: showCurve,
190+ scale: isHidden ? 0.0 : 1.0 ,
191+ child: AnimatedOpacity (
192+ duration: showDuration,
193+ curve: showCurve,
194+ opacity: isHidden ? 0.0 : 1.0 ,
195+ child: FloatingActionButton (
196+ backgroundColor: Colors .grey.withOpacity (0.8 ),
197+ onPressed: () => controller.animateTo (
198+ 0.0 ,
199+ duration: scrollDuration,
200+ curve: showCurve,
201+ ),
202+ child: Icon (Icons .arrow_upward),
203+ ),
204+ ),
82205 );
83206 }
84207}
0 commit comments