2022-03-07 22:54:34 +08:00
import ' dart:async ' ;
2022-03-03 14:58:57 +08:00
import ' package:flutter/material.dart ' ;
2022-03-07 22:54:34 +08:00
import ' package:flutter_easyloading/flutter_easyloading.dart ' ;
import ' package:flutter_hbb/models/file_model.dart ' ;
import ' package:provider/provider.dart ' ;
2022-03-09 17:07:24 +08:00
import ' package:flutter_breadcrumb/flutter_breadcrumb.dart ' ;
2022-03-11 01:28:13 +08:00
import ' package:path/path.dart ' as Path ;
2022-03-03 14:58:57 +08:00
2022-03-07 22:54:34 +08:00
import ' ../common.dart ' ;
import ' ../models/model.dart ' ;
import ' ../widgets/dialog.dart ' ;
2022-03-11 01:28:13 +08:00
2022-03-07 22:54:34 +08:00
class FileManagerPage extends StatefulWidget {
FileManagerPage ( { Key ? key , required this . id } ) : super ( key: key ) ;
final String id ;
@ override
State < StatefulWidget > createState ( ) = > _FileManagerPageState ( ) ;
}
class _FileManagerPageState extends State < FileManagerPage > {
2022-03-11 01:28:13 +08:00
final model = FFI . fileModel ;
final _selectedItems = SelectedItems ( ) ;
2022-03-07 22:54:34 +08:00
Timer ? _interval ;
Timer ? _timer ;
var _reconnects = 1 ;
2022-03-11 01:28:13 +08:00
final _breadCrumbScroller = ScrollController ( ) ;
2022-03-07 22:54:34 +08:00
@ override
void initState ( ) {
super . initState ( ) ;
showLoading ( translate ( ' Connecting... ' ) ) ;
FFI . connect ( widget . id , isFileTransfer: true ) ;
2022-03-11 01:28:13 +08:00
final res = FFI . getByName ( " read_dir " , FFI . getByName ( " get_home_dir " ) ) ;
debugPrint ( " read_dir local : $ res " ) ;
model . tryUpdateDir ( res , true ) ;
2022-03-07 22:54:34 +08:00
_interval = Timer . periodic ( Duration ( milliseconds: 30 ) ,
( timer ) = > FFI . ffiModel . update ( widget . id , context , handleMsgBox ) ) ;
}
@ override
void dispose ( ) {
2022-03-11 01:28:13 +08:00
model . clear ( ) ;
2022-03-07 22:54:34 +08:00
_interval ? . cancel ( ) ;
FFI . close ( ) ;
EasyLoading . dismiss ( ) ;
super . dispose ( ) ;
}
2022-03-03 14:58:57 +08:00
@ override
2022-03-11 01:28:13 +08:00
Widget build ( BuildContext context ) = > Consumer < FileModel > ( builder: ( _context , _model , _child ) {
return WillPopScope (
onWillPop: ( ) async {
if ( model . selectMode ) {
model . toggleSelectMode ( ) ;
} else {
goBack ( ) ;
}
return false ;
} ,
2022-03-07 22:54:34 +08:00
child: Scaffold (
2022-03-09 17:07:24 +08:00
backgroundColor: MyTheme . grayBg ,
2022-03-07 22:54:34 +08:00
appBar: AppBar (
leading: Row ( children: [
IconButton ( icon: Icon ( Icons . arrow_back ) , onPressed: goBack ) ,
IconButton ( icon: Icon ( Icons . close ) , onPressed: clientClose ) ,
] ) ,
leadingWidth: 200 ,
centerTitle: true ,
2022-03-11 01:28:13 +08:00
title: Text ( translate ( model . isLocal ? " Local " : " Remote " ) ) ,
2022-03-07 22:54:34 +08:00
actions: [
IconButton (
2022-03-09 22:43:05 +08:00
icon: Icon ( Icons . change_circle ) ,
2022-03-11 01:28:13 +08:00
onPressed: ( ) = > model . togglePage ( ) ,
2022-03-09 22:43:05 +08:00
)
2022-03-07 22:54:34 +08:00
] ,
) ,
2022-03-09 17:07:24 +08:00
body: body ( ) ,
bottomSheet: bottomSheet ( ) ,
2022-03-07 22:54:34 +08:00
) ) ;
2022-03-11 01:28:13 +08:00
} ) ;
bool needShowCheckBox ( ) {
if ( ! model . selectMode ) {
return false ;
}
return ! _selectedItems . isOtherPage ( model . isLocal ) ;
2022-03-07 22:54:34 +08:00
}
2022-03-11 01:28:13 +08:00
Widget body ( ) {
final isLocal = model . isLocal ;
final fd = model . currentDir ;
2022-03-09 22:43:05 +08:00
final entries = fd . entries ;
return Column ( children: [
headTools ( ) ,
Expanded (
child: ListView . builder (
itemCount: entries . length + 1 ,
itemBuilder: ( context , index ) {
if ( index > = entries . length ) {
// 添加尾部信息 文件统计信息等
// 添加快速返回上部
// 使用 bottomSheet 提示以选择的文件数量 点击后展开查看更多
return listTail ( ) ;
}
2022-03-11 01:28:13 +08:00
final path = Path . join ( fd . path , entries [ index ] . name ) ;
2022-03-09 22:43:05 +08:00
var selected = false ;
2022-03-11 01:28:13 +08:00
if ( model . selectMode ) {
selected = _selectedItems . contains ( path ) ;
2022-03-09 22:43:05 +08:00
}
return Card (
child: ListTile (
2022-03-11 01:28:13 +08:00
leading: Icon ( entries [ index ] . isFile ? Icons . feed_outlined: Icons . folder ,
size: 40 ) ,
2022-03-09 22:43:05 +08:00
title: Text ( entries [ index ] . name ) ,
2022-03-11 01:28:13 +08:00
selected: selected ,
// subtitle: Text(entries[index].lastModified().toString()),
trailing: needShowCheckBox ( )
2022-03-09 22:43:05 +08:00
? Checkbox (
value: selected ,
onChanged: ( v ) {
if ( v = = null ) return ;
if ( v & & ! selected ) {
2022-03-11 01:28:13 +08:00
_selectedItems . add ( isLocal , path ) ;
2022-03-09 22:43:05 +08:00
} else if ( ! v & & selected ) {
_selectedItems . remove ( path ) ;
}
2022-03-11 01:28:13 +08:00
setState ( ( ) { } ) ;
2022-03-09 22:43:05 +08:00
} )
: null ,
onTap: ( ) {
2022-03-11 01:28:13 +08:00
if ( model . selectMode & & ! _selectedItems . isOtherPage ( isLocal ) ) {
if ( selected ) {
_selectedItems . remove ( path ) ;
} else {
_selectedItems . add ( isLocal , path ) ;
}
setState ( ( ) { } ) ;
return ;
}
2022-03-09 22:43:05 +08:00
if ( entries [ index ] . isDirectory ) {
2022-03-11 01:28:13 +08:00
model . openDirectory ( path ) ;
breadCrumbScrollToEnd ( ) ;
2022-03-09 22:43:05 +08:00
} else {
// Perform file-related tasks.
}
} ,
onLongPress: ( ) {
2022-03-11 01:28:13 +08:00
_selectedItems . clear ( ) ;
model . toggleSelectMode ( ) ;
if ( model . selectMode ) {
_selectedItems . add ( isLocal , path ) ;
}
setState ( ( ) { } ) ;
2022-03-09 22:43:05 +08:00
} ,
) ,
) ;
} ,
) )
] ) ;
2022-03-11 01:28:13 +08:00
}
2022-03-09 17:07:24 +08:00
2022-03-07 22:54:34 +08:00
goBack ( ) {
2022-03-11 01:28:13 +08:00
model . goToParentDirectory ( ) ;
2022-03-07 22:54:34 +08:00
}
void handleMsgBox ( Map < String , dynamic > evt , String id ) {
var type = evt [ ' type ' ] ;
var title = evt [ ' title ' ] ;
var text = evt [ ' text ' ] ;
if ( type = = ' re-input-password ' ) {
wrongPasswordDialog ( id ) ;
} else if ( type = = ' input-password ' ) {
enterPasswordDialog ( id ) ;
} else {
var hasRetry = evt [ ' hasRetry ' ] = = ' true ' ;
print ( evt ) ;
showMsgBox ( type , title , text , hasRetry ) ;
}
}
void showMsgBox ( String type , String title , String text , bool hasRetry ) {
msgBox ( type , title , text ) ;
if ( hasRetry ) {
_timer ? . cancel ( ) ;
_timer = Timer ( Duration ( seconds: _reconnects ) , ( ) {
FFI . reconnect ( ) ;
showLoading ( translate ( ' Connecting... ' ) ) ;
} ) ;
_reconnects * = 2 ;
} else {
_reconnects = 1 ;
}
2022-03-03 14:58:57 +08:00
}
2022-03-09 17:07:24 +08:00
2022-03-11 01:28:13 +08:00
breadCrumbScrollToEnd ( ) {
Future . delayed ( Duration ( milliseconds: 200 ) , ( ) {
_breadCrumbScroller . animateTo (
_breadCrumbScroller . position . maxScrollExtent ,
duration: Duration ( milliseconds: 200 ) ,
curve: Curves . fastLinearToSlowEaseIn ) ;
} ) ;
}
2022-03-09 17:07:24 +08:00
Widget headTools ( ) = > Container (
child: Row (
children: [
Expanded (
child: BreadCrumb (
items: getPathBreadCrumbItems ( ( ) = > debugPrint ( " pressed home " ) ,
( e ) = > debugPrint ( " pressed url: $ e " ) ) ,
divider: Icon ( Icons . chevron_right ) ,
2022-03-11 01:28:13 +08:00
overflow: ScrollableOverflow ( controller: _breadCrumbScroller ) ,
2022-03-09 17:07:24 +08:00
) ) ,
Row (
children: [
// IconButton(onPressed: () {}, icon: Icon(Icons.sort)),
PopupMenuButton < SortBy > (
2022-03-09 22:43:05 +08:00
icon: Icon ( Icons . sort ) ,
itemBuilder: ( context ) {
return SortBy . values
. map ( ( e ) = > PopupMenuItem (
child:
Text ( translate ( e . toString ( ) . split ( " . " ) . last ) ) ,
value: e ,
) )
. toList ( ) ;
} ,
2022-03-11 01:28:13 +08:00
onSelected: model . changeSortStyle ) ,
2022-03-09 22:43:05 +08:00
PopupMenuButton < String > (
icon: Icon ( Icons . more_vert ) ,
2022-03-09 17:07:24 +08:00
itemBuilder: ( context ) {
2022-03-09 22:43:05 +08:00
return [
PopupMenuItem (
child: Row (
2022-03-11 01:28:13 +08:00
children: [ Icon ( Icons . refresh ) , Text ( " 刷新 " ) ] ,
2022-03-09 22:43:05 +08:00
) ,
value: " refresh " ,
2022-03-11 01:28:13 +08:00
) ,
PopupMenuItem (
child: Row (
children: [ Icon ( Icons . check ) , Text ( " 多选 " ) ] ,
) ,
value: " select " ,
2022-03-09 22:43:05 +08:00
)
] ;
2022-03-09 17:07:24 +08:00
} ,
2022-03-11 01:28:13 +08:00
onSelected: ( v ) {
if ( v = = " refresh " ) {
model . refresh ( ) ;
} else if ( v = = " select " ) {
_selectedItems . clear ( ) ;
model . toggleSelectMode ( ) ;
2022-03-09 22:43:05 +08:00
}
} ) ,
2022-03-09 17:07:24 +08:00
] ,
)
] ,
) ) ;
Widget emptyPage ( ) {
return Column (
children: [
headTools ( ) ,
Expanded ( child: Center ( child: Text ( " Empty Directory " ) ) )
] ,
) ;
}
Widget listTail ( ) {
return SizedBox ( height: 100 ) ;
}
2022-03-11 01:28:13 +08:00
/// 有几种状态
/// 选择模式 localPage
/// 准备复制模式 otherPage
/// 正在复制模式 动态数字和显示速度
/// 粘贴完成模式
2022-03-09 17:07:24 +08:00
BottomSheet ? bottomSheet ( ) {
2022-03-11 01:28:13 +08:00
if ( ! model . selectMode ) return null ;
2022-03-09 17:07:24 +08:00
return BottomSheet (
backgroundColor: MyTheme . grayBg ,
enableDrag: false ,
onClosing: ( ) {
debugPrint ( " BottomSheet close " ) ;
} ,
builder: ( context ) {
2022-03-11 01:28:13 +08:00
final isOtherPage = _selectedItems . isOtherPage ( model . isLocal ) ;
2022-03-09 17:07:24 +08:00
return Container (
height: 65 ,
alignment: Alignment . centerLeft ,
decoration: BoxDecoration (
color: MyTheme . accent50 ,
borderRadius: BorderRadius . vertical ( top: Radius . circular ( 10 ) ) ) ,
child: Padding (
padding: EdgeInsets . symmetric ( horizontal: 15 ) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
2022-03-11 01:28:13 +08:00
// 做一个bottomSheet类框架 不同状态下显示不同的内容
2022-03-09 17:07:24 +08:00
Row (
children: [
2022-03-11 01:28:13 +08:00
CircularProgressIndicator ( ) ,
isOtherPage ? Icon ( Icons . input ) : Icon ( Icons . check ) ,
SizedBox ( width: 16 ) ,
Column (
mainAxisAlignment: MainAxisAlignment . center ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text ( isOtherPage ? ' 粘贴到这里? ' : ' 已选择 ' , style: TextStyle ( fontSize: 18 ) ) ,
Text ( " ${ _selectedItems . length } 个文件 [ ${ model . isLocal ? ' 本地 ' : ' 远程 ' } ] " , style: TextStyle ( fontSize: 14 , color: MyTheme . grayBg ) )
] ,
)
2022-03-09 17:07:24 +08:00
] ,
) ,
Row (
children: [
2022-03-11 01:28:13 +08:00
( _selectedItems . length > 0 & & isOtherPage ) ? IconButton (
2022-03-09 17:07:24 +08:00
icon: Icon ( Icons . paste ) ,
2022-03-11 01:28:13 +08:00
onPressed: ( ) {
2022-03-09 22:43:05 +08:00
debugPrint ( " paste " ) ;
2022-03-11 01:28:13 +08:00
// TODO
model . sendFiles (
_selectedItems . items . first ,
model . currentRemoteDir . path +
' / ' +
_selectedItems . items . first . split ( ' / ' ) . last ,
false ,
false ) ;
// unused set callback
// _fileModel.set
2022-03-09 22:43:05 +08:00
} ,
2022-03-11 01:28:13 +08:00
) : IconButton (
2022-03-09 17:07:24 +08:00
icon: Icon ( Icons . delete_forever ) ,
onPressed: ( ) { } ,
) ,
IconButton (
icon: Icon ( Icons . cancel_outlined ) ,
onPressed: ( ) {
2022-03-11 01:28:13 +08:00
model . toggleSelectMode ( ) ;
2022-03-09 17:07:24 +08:00
} ,
) ,
] ,
)
] ,
) ,
) ,
) ;
} ) ;
}
List < BreadCrumbItem > getPathBreadCrumbItems (
void Function ( ) onHome , void Function ( String ) onPressed ) {
2022-03-11 01:28:13 +08:00
final path = model . currentDir . path ;
final list = path . trim ( ) . split ( ' / ' ) ; // TODO use Path
2022-03-09 17:07:24 +08:00
list . remove ( " " ) ;
final breadCrumbList = [
BreadCrumbItem (
content: IconButton (
icon: Icon ( Icons . home_filled ) ,
onPressed: onHome ,
) )
] ;
breadCrumbList . addAll ( list . map ( ( e ) = > BreadCrumbItem (
content: TextButton (
child: Text ( e ) ,
style:
ButtonStyle ( minimumSize: MaterialStateProperty . all ( Size ( 0 , 0 ) ) ) ,
onPressed: ( ) = > onPressed ( e ) ) ) ) ) ;
return breadCrumbList ;
}
2022-03-11 01:28:13 +08:00
}
class SelectedItems {
bool ? _isLocal ;
final List < String > _items = [ ] ;
List < String > get items = > _items ;
int get length = > _items . length ;
// bool get isEmpty => _items.length == 0;
add ( bool isLocal , String path ) {
if ( _isLocal = = null ) {
_isLocal = isLocal ;
}
if ( _isLocal ! = null & & _isLocal ! = isLocal ) {
return ;
}
if ( ! _items . contains ( path ) ) {
_items . add ( path ) ;
}
}
2022-03-09 17:07:24 +08:00
2022-03-11 01:28:13 +08:00
bool contains ( String path ) {
return _items . contains ( path ) ;
}
remove ( String path ) {
_items . remove ( path ) ;
if ( _items . length = = 0 ) {
_isLocal = null ;
}
}
bool isOtherPage ( bool currentIsLocal ) {
if ( _isLocal = = null ) {
return false ;
} else {
return _isLocal ! = currentIsLocal ;
}
}
clear ( ) {
_items . clear ( ) ;
_isLocal = null ;
}
2022-03-03 14:58:57 +08:00
}