@@ -22,10 +22,29 @@ class FeedbackBloc extends Bloc<FeedbackEvent, FeedbackState> { | |||
if (event is FeedbackInitEvent) { | |||
yield* _mapInitToState(event); | |||
} | |||
/// 提交反馈 | |||
if (event is FeedbackSubmitEvent) { | |||
yield* _mapSaveToState(event); | |||
} | |||
} | |||
/// 提交反馈 | |||
Stream<FeedbackState> _mapSaveToState(FeedbackSubmitEvent event) async* { | |||
var result = await repository.saveFeedback(event?.model); | |||
if (!EmptyUtil.isEmpty(result)) { | |||
yield FeedbackSaveSuccessState(); | |||
} else { | |||
yield FeedbackSaveErrorState(); | |||
} | |||
} | |||
/// 初始化 | |||
Stream<FeedbackState> _mapInitToState(FeedbackInitEvent event) async* { | |||
var cache = await repository.fetchCachedData(event.model); | |||
if (!EmptyUtil.isEmpty(cache)) { | |||
yield FeedbackLoadedState(model: cache); | |||
} | |||
var result = await repository.fetchData(event.model); | |||
if (!EmptyUtil.isEmpty(result)) { | |||
yield FeedbackLoadedState(model: result); | |||
@@ -10,4 +10,8 @@ class FeedbackInitEvent extends FeedbackEvent{ | |||
} | |||
/// 提交反馈 | |||
class FeedbackSubmitEvent extends FeedbackEvent{} | |||
class FeedbackSubmitEvent extends FeedbackEvent{ | |||
final Map<String, dynamic> model; | |||
FeedbackSubmitEvent({this.model}); | |||
} |
@@ -0,0 +1,73 @@ | |||
import 'dart:async'; | |||
import 'package:bloc/bloc.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/bloc/feedback_record_repository.dart'; | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
import 'feedback_record_event.dart'; | |||
import 'feedback_record_state.dart'; | |||
class FeedbackRecordBloc extends Bloc<FeedbackRecordEvent, FeedbackRecordState> { | |||
// FeedbackRecordBloc() : super(FeedbackRecordInitial()); | |||
@override | |||
FeedbackRecordState get initialState => FeedbackRecordInitial(); | |||
FeedbackRecordRepository repository; | |||
FeedbackRecordBloc(this.repository); | |||
@override | |||
Stream<FeedbackRecordState> mapEventToState( | |||
FeedbackRecordEvent event, | |||
) async* { | |||
/// 初始化 | |||
if (event is FeedbackRecordInitEvent) { | |||
yield* _mapInitEventToState(event); | |||
} | |||
/// 刷新 | |||
if (event is FeedbackRecordRefreshEvent) { | |||
yield* _mapRefreshEventToState(event); | |||
} | |||
/// 加载更多 | |||
if (event is FeedbackRecordLoadEvent) { | |||
yield* _mapLoadEventToState(event); | |||
} | |||
} | |||
/// 初始化 | |||
Stream<FeedbackRecordState> _mapInitEventToState(FeedbackRecordInitEvent event) async* { | |||
var styleCache = await repository.fetchCacheStyle(); | |||
var styleNet = await repository.fetchInitStyle(); | |||
var data = await repository.fetchInitData(); | |||
if (!EmptyUtil.isEmpty(data) && (!EmptyUtil.isEmpty(styleCache) || !EmptyUtil.isEmpty(styleNet))) { | |||
yield FeedbackRecordLoadedState(dataModel: data, styleModel: !EmptyUtil.isEmpty(styleNet) ? styleNet : styleCache); | |||
} else { | |||
yield FeedbackRecordInitErrorState(); | |||
} | |||
} | |||
/// 刷新 | |||
Stream<FeedbackRecordState> _mapRefreshEventToState(FeedbackRecordRefreshEvent event) async* { | |||
var style = repository.styleModel; | |||
var result = await repository.fetchRefreshData(); | |||
if (!EmptyUtil.isEmpty(result) && !EmptyUtil.isEmpty(style)) { | |||
yield FeedbackRecordRefreshSuccessState(); | |||
yield FeedbackRecordLoadedState(dataModel: result, styleModel: style); | |||
} else { | |||
yield FeedbackRecordRefreshErrorState(); | |||
} | |||
} | |||
/// 加载更多 | |||
Stream<FeedbackRecordState> _mapLoadEventToState(FeedbackRecordLoadEvent event) async* { | |||
var style = repository.styleModel; | |||
var result = await repository.fetchLoadData(); | |||
if (!EmptyUtil.isEmpty(result) && !EmptyUtil.isEmpty(style)) { | |||
yield FeedbackRecordLoadSuccessState(); | |||
yield FeedbackRecordLoadedState(dataModel: result, styleModel: style); | |||
} else { | |||
yield FeedbackRecordLoadErrorState(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
import 'package:meta/meta.dart'; | |||
@immutable | |||
abstract class FeedbackRecordEvent {} | |||
/// 初始化 | |||
class FeedbackRecordInitEvent extends FeedbackRecordEvent {} | |||
/// 下拉刷新 | |||
class FeedbackRecordRefreshEvent extends FeedbackRecordEvent {} | |||
/// 加载更多 | |||
class FeedbackRecordLoadEvent extends FeedbackRecordEvent {} |
@@ -0,0 +1,117 @@ | |||
import 'dart:convert'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_data_model.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_style_model.dart'; | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
class FeedbackRecordRepository { | |||
// final int max = 5; | |||
int _currentPage = 1; | |||
bool _hasMore = true; | |||
final Map<String, dynamic> data; | |||
FeedbackRecordRepository(this.data) { | |||
_dataModel = FeedbackRecordDataModel(); | |||
_dataModel.data = []; | |||
} | |||
/// 样式数据 | |||
FeedbackRecordStyleModel _styleModel; | |||
/// 数据 | |||
FeedbackRecordDataModel _dataModel; | |||
FeedbackRecordStyleModel get styleModel => _styleModel; | |||
FeedbackRecordDataModel get dataModel => _dataModel; | |||
/// 初始化数据 | |||
Future<dynamic> fetchInitData() async { | |||
_hasMore = true; | |||
_currentPage = 1; | |||
return _fetchBaseRequest(); | |||
} | |||
/// 样式初始化 | |||
Future<dynamic> fetchInitStyle() async { | |||
try { | |||
String skipIdentifier = | |||
!EmptyUtil.isEmpty(data) && data.containsKey('skip_identifier') && !EmptyUtil.isEmpty(data['skip_identifier']) ? data['skip_identifier'] : 'pub.flutter.feedback_list'; | |||
var result = await NetUtil.post('/api/v1/mod/$skipIdentifier', method: NetMethod.GET, cache: true); | |||
if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { | |||
return _handleStyle(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); | |||
} | |||
} catch (e, s) { | |||
Logger.error(e, s); | |||
} | |||
return null; | |||
} | |||
/// 获取缓存样式 | |||
Future<dynamic> fetchCacheStyle() async { | |||
try { | |||
String skipIdentifier = | |||
!EmptyUtil.isEmpty(data) && data.containsKey('skip_identifier') && !EmptyUtil.isEmpty(data['skip_identifier']) ? data['skip_identifier'] : 'pub.flutter.feedback_list'; | |||
var result = await NetUtil.getRequestCachedData('/api/v1/mod/$skipIdentifier'); | |||
if (!EmptyUtil.isEmpty(result)) { | |||
return _handleStyle(result); | |||
} | |||
} catch (e, s) { | |||
Logger.error(e, s); | |||
} | |||
return null; | |||
} | |||
/// 处理样式 | |||
dynamic _handleStyle(Map<String, dynamic> data) { | |||
try { | |||
var modListData = data['mod_list'][0]['data']; | |||
if (!EmptyUtil.isEmpty(modListData)) { | |||
FeedbackRecordStyleModel model = FeedbackRecordStyleModel.fromJson(modListData is String ? jsonDecode(modListData) : modListData); | |||
if (!EmptyUtil.isEmpty(model)) { | |||
_styleModel = model; | |||
return _styleModel; | |||
} | |||
} | |||
} catch (e, s) { | |||
Logger.error(e, s); | |||
} | |||
return null; | |||
} | |||
/// 刷新 | |||
Future<dynamic> fetchRefreshData() async { | |||
_hasMore = true; | |||
_currentPage = 1; | |||
return _fetchBaseRequest(); | |||
} | |||
/// 加载更多 | |||
Future<dynamic> fetchLoadData() async { | |||
if (_hasMore) { | |||
return _fetchBaseRequest(); | |||
} | |||
return null; | |||
} | |||
/// 基础加载方法 | |||
Future<dynamic> _fetchBaseRequest() async { | |||
try { | |||
var result = await NetUtil.post('/api/v1/feedback/list', method: NetMethod.GET, queryParameters: {'page': _currentPage.toString()}); | |||
if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { | |||
FeedbackRecordDataModel model = FeedbackRecordDataModel.fromJson(result); | |||
if (!EmptyUtil.isEmpty(model) && !EmptyUtil.isEmpty(model?.data)) { | |||
_dataModel.data.addAll(model.data); | |||
_hasMore = true; | |||
++_currentPage; | |||
return _dataModel; | |||
} | |||
} | |||
_hasMore = false; | |||
} catch (e, s) { | |||
Logger.error(e, s); | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
import 'package:meta/meta.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_data_model.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_style_model.dart'; | |||
@immutable | |||
abstract class FeedbackRecordState {} | |||
class FeedbackRecordInitial extends FeedbackRecordState {} | |||
/// 数据加载成功 | |||
class FeedbackRecordLoadedState extends FeedbackRecordState { | |||
FeedbackRecordDataModel dataModel; | |||
FeedbackRecordStyleModel styleModel; | |||
FeedbackRecordLoadedState({this.dataModel, this.styleModel}); | |||
} | |||
/// 初始化失败 | |||
class FeedbackRecordInitErrorState extends FeedbackRecordState{} | |||
/// 刷新成功与失败 | |||
class FeedbackRecordRefreshSuccessState extends FeedbackRecordState{} | |||
class FeedbackRecordRefreshErrorState extends FeedbackRecordState{} | |||
/// 加载更多成功与失败 | |||
class FeedbackRecordLoadSuccessState extends FeedbackRecordState{} | |||
class FeedbackRecordLoadErrorState extends FeedbackRecordState{} |
@@ -1,21 +1,42 @@ | |||
import 'dart:convert'; | |||
import 'dart:io'; | |||
import 'package:path/path.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_model.dart'; | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
class FeedBackRepository { | |||
/// 获取缓存样式数据 | |||
Future<FeedbackModel> fetchCachedData(final Map<String, dynamic> data) async { | |||
try { | |||
String skipIdentifier = | |||
!EmptyUtil.isEmpty(data) && data.containsKey('skip_identifier') && !EmptyUtil.isEmpty(data['skip_identifier']) ? data['skip_identifier'] : 'pub.flutter.feedback'; | |||
var result = await NetUtil.getRequestCachedData('/api/v1/mod/$skipIdentifier'); | |||
if (!EmptyUtil.isEmpty(result)) { | |||
var modListData = result['mod_list'][0]['data']; | |||
if (!EmptyUtil.isEmpty(modListData)) { | |||
FeedbackModel model = FeedbackModel.fromJson(jsonDecode(modListData)); | |||
model.feedbackTypes.last.isSelect = true; | |||
return model; | |||
} | |||
} | |||
} catch (e, s) { | |||
Logger.error(e, s); | |||
} | |||
return null; | |||
} | |||
/// 获取网络样式数据 | |||
Future<FeedbackModel> fetchData(final Map<String, dynamic> data) async { | |||
try { | |||
String skip_identifier = !EmptyUtil.isEmpty(data) && data.containsKey('skip_identifier') && !EmptyUtil.isEmpty(data['skip_identifier']) ? data['skip_identifier'] : 'pub.flutter.feedback'; | |||
String skipIdentifier = | |||
!EmptyUtil.isEmpty(data) && data.containsKey('skip_identifier') && !EmptyUtil.isEmpty(data['skip_identifier']) ? data['skip_identifier'] : 'pub.flutter.feedback'; | |||
var result = await NetUtil.post('/api/v1/mod/$skip_identifier', method: NetMethod.GET, cache: true); | |||
var result = await NetUtil.post('/api/v1/mod/$skipIdentifier', method: NetMethod.GET, cache: true); | |||
if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { | |||
var modListData = result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]['mod_list'][0]['data']; | |||
if (!EmptyUtil.isEmpty(modListData)) { | |||
FeedbackModel model = FeedbackModel.fromJson(jsonDecode(modListData)); | |||
model.feedbackTypes.last.isSelect = true; | |||
return model; | |||
} | |||
} | |||
@@ -25,6 +46,71 @@ class FeedBackRepository { | |||
return null; | |||
} | |||
/// 获取网络数据 | |||
/// 提交反馈 | |||
Future<dynamic> saveFeedback(final Map<String, dynamic> data) async { | |||
try { | |||
List<File> files = data['files']; | |||
List<String> imageList = []; | |||
if(!EmptyUtil.isEmpty(files)) { | |||
// files.forEach((element) async { | |||
// String fileName = await updateFile(element); | |||
// if (!EmptyUtil.isEmpty(fileName)) { | |||
// imageList.add(fileName); | |||
// } | |||
// }); | |||
for(int i = 0 ; i < files.length; i++){ | |||
String imgName = await updateFile(files[i]); | |||
if(!EmptyUtil.isEmpty(imgName)){ | |||
imageList.add(imgName); | |||
} | |||
} | |||
if (imageList.length != files.length) { | |||
return null; | |||
} | |||
} | |||
var result = await NetUtil.post('/api/v1/feedback/save', method: NetMethod.POST, params: { | |||
'type': data['type']?.toString(), | |||
'title': data['title']?.toString(), | |||
'content': data['content']?.toString(), | |||
'image_list': imageList, | |||
}); | |||
if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { | |||
return result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]; | |||
} | |||
} catch (e, s) { | |||
Logger.error(e, s); | |||
} | |||
return null; | |||
} | |||
/// 上传图片 | |||
Future<String> updateFile(final File imageFile) async { | |||
try { | |||
List<int> originBytes = await imageFile.readAsBytes(); | |||
var result1 = await NetUtil.post('/api/v1/img/upload', params: {'dir': 'feedback', 'file_size': originBytes.length, 'file_name': basename(imageFile.path)}, method: NetMethod.PUT); | |||
if (NetUtil.isSuccess(result1) && !EmptyUtil.isEmpty(result1[GlobalConfig.HTTP_RESPONSE_KEY_DATA]) && !EmptyUtil.isEmpty(result1[GlobalConfig.HTTP_RESPONSE_KEY_DATA]['token'])) { | |||
String token = result1[GlobalConfig.HTTP_RESPONSE_KEY_DATA]['token']; | |||
String host = result1[GlobalConfig.HTTP_RESPONSE_KEY_DATA]['host']; | |||
String key = result1[GlobalConfig.HTTP_RESPONSE_KEY_DATA]['key']; | |||
String method = result1[GlobalConfig.HTTP_RESPONSE_KEY_DATA]['method']; | |||
var result2 = await NetUtil.uploadFile(host, imageFile, params: {'key': key, 'token': token}, method: method); | |||
Map<String, dynamic> json = jsonDecode(result2.toString()); | |||
if (NetUtil.isSuccess(json) && !EmptyUtil.isEmpty(json[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { | |||
String filename = json['data']['fname'] ?? ''; | |||
print(filename); | |||
// filename += '?${DateTime.now().millisecondsSinceEpoch.toString()}'; | |||
// print(filename); | |||
// ⚠️ 这里返回key是因为fname是带有域名的,所以直接用key即可 | |||
return !EmptyUtil.isEmpty(filename) ? key : null; | |||
} | |||
} | |||
} catch (e, s) { | |||
Logger.error(e, s); | |||
} | |||
return null; | |||
} | |||
} |
@@ -13,3 +13,9 @@ class FeedbackLoadedState extends FeedbackState{ | |||
} | |||
class FeedbackErrorState extends FeedbackState{} | |||
/// 保存反馈成功 | |||
class FeedbackSaveSuccessState extends FeedbackState{} | |||
/// 保存反馈失败 | |||
class FeedbackSaveErrorState extends FeedbackState{} |
@@ -1,4 +1,5 @@ | |||
import 'dart:io'; | |||
import 'dart:math'; | |||
import 'package:flutter/cupertino.dart'; | |||
import 'package:flutter/material.dart'; | |||
@@ -55,6 +56,9 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
TextEditingController _title; | |||
// 是否上传中 | |||
bool isUpLoading = false; | |||
@override | |||
void initState() { | |||
_feedback = TextEditingController(); | |||
@@ -69,7 +73,6 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
super.dispose(); | |||
} | |||
/// 选择图片 | |||
void _onAddImage() async { | |||
if (_images.length >= 4) { | |||
@@ -107,9 +110,13 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
if (file == null) return; | |||
setState(() { | |||
_images.add(File(file.path)); | |||
}); | |||
if (_images.length <= 4) { | |||
setState(() { | |||
_images.add(File(file.path)); | |||
}); | |||
} else { | |||
Fluttertoast.showToast(msg: '最多只能选择4张'); | |||
} | |||
// File resultFile = await EncodeUtil.compressImage(file, 800); | |||
} | |||
@@ -120,6 +127,19 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
return BlocConsumer<FeedbackBloc, FeedbackState>( | |||
listener: (context, state) {}, | |||
buildWhen: (prev, current) { | |||
/// 保存成功 | |||
if (current is FeedbackSaveSuccessState) { | |||
Fluttertoast.showToast(msg: '反馈成功~'); | |||
Navigator.pop(context); | |||
return false; | |||
} | |||
if (current is FeedbackSaveErrorState) { | |||
Fluttertoast.showToast(msg: '反馈失败~'); | |||
setState(() { | |||
isUpLoading = false; | |||
}); | |||
return false; | |||
} | |||
return true; | |||
}, | |||
builder: (context, state) { | |||
@@ -169,14 +189,7 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
Center( | |||
child: GestureDetector( | |||
behavior: HitTestBehavior.opaque, | |||
onTap: () { | |||
Navigator.push( | |||
context, | |||
CupertinoPageRoute( | |||
builder: (_) => FeedbackRecordPage(), | |||
), | |||
); | |||
}, | |||
onTap: () => RouterUtil.route(model, model.toJson(), context), | |||
child: Container( | |||
width: 120, | |||
height: 30, | |||
@@ -273,6 +286,7 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
), | |||
), | |||
const SizedBox(height: 2), | |||
/// 异常选项 | |||
Padding( | |||
padding: EdgeInsets.only(top: 4, bottom: 4), | |||
@@ -281,16 +295,15 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
child: Row( | |||
children: model.feedbackTypes | |||
.map((e) => Expanded( | |||
child: GestureDetector( | |||
child: GestureDetector( | |||
behavior: HitTestBehavior.opaque, | |||
onTap: (){ | |||
onTap: () { | |||
setState(() { | |||
model.feedbackTypes.forEach((element) { | |||
element.isSelect = false; | |||
}); | |||
e.isSelect = true; | |||
}); | |||
}, | |||
child: Container( | |||
margin: EdgeInsets.only(left: 2, right: 2), | |||
@@ -311,9 +324,8 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
e.name ?? '', | |||
style: TextStyle( | |||
fontSize: 12, | |||
color: HexColor.fromHex(e.isSelect | |||
? model?.feedbackTypeSelectedTextColor | |||
: model?.feedbackTypeNoSelectedTextColor) //e.isSelect ? Colors.redAccent : Color(0xff999999), | |||
color: HexColor.fromHex( | |||
e.isSelect ? model?.feedbackTypeSelectedTextColor : model?.feedbackTypeNoSelectedTextColor) //e.isSelect ? Colors.redAccent : Color(0xff999999), | |||
), | |||
), | |||
), | |||
@@ -401,8 +413,9 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
child: FeedbackImageWidget( | |||
file: file, | |||
onDelete: () { | |||
_images.remove(file); | |||
setState(() {}); | |||
setState(() { | |||
_images.remove(file); | |||
}); | |||
}, | |||
), | |||
)); | |||
@@ -425,6 +438,8 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
} | |||
return Column( | |||
mainAxisAlignment: MainAxisAlignment.center, | |||
crossAxisAlignment: CrossAxisAlignment.start, | |||
children: <Widget>[ | |||
Row( | |||
children: <Widget>[ | |||
@@ -448,9 +463,18 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
), | |||
], | |||
), | |||
Row( | |||
// Row( | |||
// children: images, | |||
// ) | |||
Wrap( | |||
crossAxisAlignment: WrapCrossAlignment.center, | |||
textDirection: TextDirection.ltr, | |||
spacing: 0, | |||
runSpacing: 0, | |||
runAlignment: WrapAlignment.center, | |||
alignment: WrapAlignment.start, | |||
children: images, | |||
) | |||
), | |||
], | |||
); | |||
} | |||
@@ -461,9 +485,27 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
if (!_submitable) { | |||
return; | |||
} | |||
if(isUpLoading){ | |||
// Fluttertoast.showToast(msg: '提交中...'); | |||
return; | |||
} | |||
setState(() { | |||
isUpLoading = true; | |||
}); | |||
Logger.debug('提交:${_feedback.text.toString()}'); | |||
Fluttertoast.showToast(msg: '提交成功~'); | |||
Navigator.pop(context); | |||
String selectId; | |||
model.feedbackTypes.forEach((element) { | |||
if (element.isSelect) { | |||
selectId = element.typeId; | |||
} | |||
}); | |||
BlocProvider.of<FeedbackBloc>(context) | |||
.add(FeedbackSubmitEvent(model: {'title': _title?.text?.toString(), 'content': _feedback?.text?.toString(), 'type': selectId, 'files': _images})); | |||
// Fluttertoast.showToast(msg: '提交成功~'); | |||
// Navigator.pop(context); | |||
}, | |||
child: Container( | |||
margin: EdgeInsets.only(top: 24, bottom: 4), | |||
@@ -474,7 +516,7 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
), | |||
child: Center( | |||
child: Text( | |||
model?.feedbackPostBtnText ?? '提交意见', | |||
isUpLoading ? '提交意见...' : model?.feedbackPostBtnText ?? '提交意见', | |||
style: TextStyle( | |||
fontSize: 14, | |||
color: HexColor.fromHex(model?.feedbackPostBtnTextColor ?? '#FFFFFF'), | |||
@@ -484,6 +526,4 @@ class __FeedbackPageContainerState extends State<_FeedbackPageContainer> { | |||
), | |||
); | |||
} | |||
} |
@@ -1,36 +1,167 @@ | |||
import 'package:flutter/cupertino.dart'; | |||
import 'package:flutter/material.dart'; | |||
import 'package:pull_to_refresh/pull_to_refresh.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/bloc/feedback_record_bloc.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/bloc/feedback_record_repository.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_data_model.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_style_model.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/widgets/feedback_record_item.dart'; | |||
import 'package:zhiying_base_widget/widgets/empty/empty_widget.dart'; | |||
import 'package:flutter_bloc/flutter_bloc.dart'; | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
import 'bloc/feedback_record_event.dart'; | |||
import 'bloc/feedback_record_state.dart'; | |||
class FeedbackRecordPage extends StatelessWidget { | |||
final Map<String, dynamic> data; | |||
FeedbackRecordPage(this.data); | |||
class FeedbackRecordPage extends StatefulWidget { | |||
@override | |||
_FeedbackRecordPageState createState() => _FeedbackRecordPageState(); | |||
Widget build(BuildContext context) { | |||
return BlocProvider<FeedbackRecordBloc>( | |||
create: (_) => FeedbackRecordBloc(FeedbackRecordRepository(data))..add(FeedbackRecordInitEvent()), | |||
child: _FeedbackRecordPage(), | |||
); | |||
} | |||
} | |||
class _FeedbackRecordPageState extends State<FeedbackRecordPage> { | |||
class _FeedbackRecordPage extends StatefulWidget { | |||
@override | |||
__FeedbackRecordPageState createState() => __FeedbackRecordPageState(); | |||
} | |||
class __FeedbackRecordPageState extends State<_FeedbackRecordPage> { | |||
RefreshController _controller; | |||
/// 点击详情 | |||
/// 刷新 | |||
void _refresh() { | |||
BlocProvider.of<FeedbackRecordBloc>(context).add(FeedbackRecordRefreshEvent()); | |||
} | |||
/// 加载更多 | |||
void _load() { | |||
BlocProvider.of<FeedbackRecordBloc>(context).add(FeedbackRecordLoadEvent()); | |||
} | |||
@override | |||
void initState() { | |||
_controller = RefreshController(); | |||
super.initState(); | |||
} | |||
@override | |||
void dispose() { | |||
_controller?.dispose(); | |||
super.dispose(); | |||
} | |||
@override | |||
Widget build(BuildContext context) { | |||
return BlocConsumer<FeedbackRecordBloc, FeedbackRecordState>( | |||
listener: (context, state) {}, | |||
buildWhen: (prev, current) { | |||
if (current is FeedbackRecordRefreshSuccessState) { | |||
_controller?.refreshCompleted(resetFooterState: true); | |||
return false; | |||
} | |||
if (current is FeedbackRecordRefreshErrorState) { | |||
_controller?.refreshFailed(); | |||
return false; | |||
} | |||
if (current is FeedbackRecordLoadSuccessState) { | |||
_controller?.loadComplete(); | |||
return false; | |||
} | |||
if (current is FeedbackRecordLoadErrorState) { | |||
_controller?.loadNoData(); | |||
return false; | |||
} | |||
return true; | |||
}, | |||
builder: (context, state) { | |||
/// 数据视图 | |||
if (state is FeedbackRecordLoadedState) { | |||
return _createDataWidget(state?.styleModel, state?.dataModel); | |||
} | |||
/// 初始化失败,空视图 | |||
if (state is FeedbackRecordInitErrorState) { | |||
return _createEmptyWidget(); | |||
} | |||
/// 加载中,骨架图 | |||
return _createSkeletonWidget(); | |||
}, | |||
); | |||
} | |||
/// 有数据 | |||
Widget _createDataWidget(FeedbackRecordStyleModel styleModel, FeedbackRecordDataModel dataModel) { | |||
int length = dataModel?.data?.length ?? 0; | |||
if(length == 0 ) return _createEmptyWidget(); | |||
return Scaffold( | |||
appBar: _createNav(styleModel), | |||
body: GestureDetector( | |||
onTap: () { | |||
FocusScope.of(context).requestFocus(FocusNode()); | |||
}, | |||
child: SafeArea( | |||
child: SmartRefresher( | |||
controller: _controller, | |||
enablePullUp: true, | |||
enablePullDown: true, | |||
onRefresh: _refresh, | |||
onLoading: _load, | |||
child: ListView.builder( | |||
itemCount: length, | |||
itemBuilder: (context, index) { | |||
return FeedbackRecordItem(styleModel, dataModel.data[index]); | |||
}), | |||
), | |||
), | |||
), | |||
); | |||
} | |||
/// 空数据 | |||
Widget _createEmptyWidget() { | |||
return Scaffold( | |||
appBar: _createNav(), | |||
appBar: _createNav(null), | |||
body: GestureDetector( | |||
onTap: () { | |||
FocusScope.of(context).requestFocus(FocusNode()); | |||
}, | |||
child: SafeArea( | |||
child: Center(child: EmptyWidget()), | |||
// child: ListView.builder( | |||
// itemCount: 10, | |||
// itemBuilder: (context, index) { | |||
// return FeedbackRecordItem(); | |||
// }), | |||
child: SmartRefresher( | |||
controller: _controller, | |||
enablePullDown: true, | |||
enablePullUp: false, | |||
onRefresh: _refresh, | |||
child: Center(child: EmptyWidget()), | |||
), | |||
), | |||
), | |||
); | |||
} | |||
// 导航栏 | |||
Widget _createNav() { | |||
/// 骨架图 | |||
Widget _createSkeletonWidget() { | |||
return Scaffold( | |||
appBar: _createNav(null), | |||
body: Container(), | |||
); | |||
} | |||
/// 导航栏 | |||
Widget _createNav(FeedbackRecordStyleModel styleModel) { | |||
return CupertinoNavigationBar( | |||
border: Border( | |||
bottom: BorderSide( | |||
@@ -38,7 +169,7 @@ class _FeedbackRecordPageState extends State<FeedbackRecordPage> { | |||
style: BorderStyle.none, | |||
), | |||
), | |||
backgroundColor: Colors.white, | |||
backgroundColor: HexColor.fromHex(styleModel?.appBarBgColor ?? '#FFFFFF'), | |||
leading: Navigator.canPop(context) | |||
? GestureDetector( | |||
child: Container( | |||
@@ -56,10 +187,10 @@ class _FeedbackRecordPageState extends State<FeedbackRecordPage> { | |||
) | |||
: Container(), | |||
middle: Text( | |||
'反馈列表', | |||
styleModel?.appBarName ?? '反馈列表', | |||
style: TextStyle( | |||
fontSize: 15, | |||
color: Color(0xff333333), | |||
color: HexColor.fromHex(styleModel?.appBarNameColor ?? '#FF333333'), | |||
), | |||
), | |||
); | |||
@@ -1,4 +1,6 @@ | |||
class FeedbackModel { | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
class FeedbackModel extends SkipModel{ | |||
String appBarName; | |||
String appBarNameColor; | |||
String appBarBgColor; | |||
@@ -73,6 +75,7 @@ class FeedbackModel { | |||
this.skipIdentifier}); | |||
FeedbackModel.fromJson(Map<String, dynamic> json) { | |||
super.fromJson(json); | |||
appBarName = json['app_bar_name']; | |||
appBarNameColor = json['app_bar_name_color']; | |||
appBarBgColor = json['app_bar_bg_color']; | |||
@@ -121,7 +124,7 @@ class FeedbackModel { | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
final Map<String, dynamic> data = super.toJson(); | |||
data['app_bar_name'] = this.appBarName; | |||
data['app_bar_name_color'] = this.appBarNameColor; | |||
data['app_bar_bg_color'] = this.appBarBgColor; | |||
@@ -0,0 +1,60 @@ | |||
/// 反馈列表数据 | |||
class FeedbackRecordDataModel { | |||
List<FeedbackRecordDataItemModel> data; | |||
FeedbackRecordDataModel({this.data}); | |||
FeedbackRecordDataModel.fromJson(Map<String, dynamic> json) { | |||
if (json['data'] != null) { | |||
data = new List<FeedbackRecordDataItemModel>(); | |||
json['data'].forEach((v) { | |||
data.add(new FeedbackRecordDataItemModel.fromJson(v)); | |||
}); | |||
} | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
if (this.data != null) { | |||
data['data'] = this.data.map((v) => v.toJson()).toList(); | |||
} | |||
return data; | |||
} | |||
} | |||
class FeedbackRecordDataItemModel { | |||
String id; | |||
String type; | |||
String typeText; | |||
String state; | |||
String title; | |||
String content; | |||
List<String> imageList; | |||
String createTime; | |||
FeedbackRecordDataItemModel({this.id, this.type, this.typeText, this.state, this.title, this.content, this.imageList, this.createTime}); | |||
FeedbackRecordDataItemModel.fromJson(Map<String, dynamic> json) { | |||
id = json['id']; | |||
type = json['type']; | |||
typeText = json['type_text']; | |||
state = json['state']; | |||
title = json['title']; | |||
content = json['content']; | |||
imageList = json['image_list'].cast<String>(); | |||
createTime = json['create_time']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
data['id'] = this.id; | |||
data['type'] = this.type; | |||
data['type_text'] = this.typeText; | |||
data['state'] = this.state; | |||
data['title'] = this.title; | |||
data['content'] = this.content; | |||
data['image_list'] = this.imageList; | |||
data['create_time'] = this.createTime; | |||
return data; | |||
} | |||
} |
@@ -0,0 +1,140 @@ | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
/// 反馈列表样式 | |||
class FeedbackRecordStyleModel extends SkipModel{ | |||
String appBarName; | |||
String appBarNameColor; | |||
String appBarBgColor; | |||
String bgColor; | |||
ItemStyle itemStyle; | |||
List<FeedbackTypes> feedbackTypes; | |||
List<StateImages> stateImages; | |||
String requiredLogin; | |||
String requiredTaobaoAuth; | |||
String skipIdentifier; | |||
FeedbackRecordStyleModel( | |||
{this.appBarName, | |||
this.appBarNameColor, | |||
this.appBarBgColor, | |||
this.bgColor, | |||
this.itemStyle, | |||
this.feedbackTypes, | |||
this.stateImages, | |||
this.requiredLogin, | |||
this.requiredTaobaoAuth, | |||
this.skipIdentifier}); | |||
FeedbackRecordStyleModel.fromJson(Map<String, dynamic> json) { | |||
super.fromJson(json); | |||
appBarName = json['app_bar_name']; | |||
appBarNameColor = json['app_bar_name_color']; | |||
appBarBgColor = json['app_bar_bg_color']; | |||
bgColor = json['bg_color']; | |||
itemStyle = json['item_style'] != null ? new ItemStyle.fromJson(json['item_style']) : null; | |||
if (json['feedback_types'] != null) { | |||
feedbackTypes = new List<FeedbackTypes>(); | |||
json['feedback_types'].forEach((v) { | |||
feedbackTypes.add(new FeedbackTypes.fromJson(v)); | |||
}); | |||
} | |||
if (json['state_images'] != null) { | |||
stateImages = new List<StateImages>(); | |||
json['state_images'].forEach((v) { | |||
stateImages.add(new StateImages.fromJson(v)); | |||
}); | |||
} | |||
requiredLogin = json['required_login']; | |||
requiredTaobaoAuth = json['required_taobao_auth']; | |||
skipIdentifier = json['skip_identifier']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = super.toJson(); | |||
data['app_bar_name'] = this.appBarName; | |||
data['app_bar_name_color'] = this.appBarNameColor; | |||
data['app_bar_bg_color'] = this.appBarBgColor; | |||
data['bg_color'] = this.bgColor; | |||
if (this.itemStyle != null) { | |||
data['item_style'] = this.itemStyle.toJson(); | |||
} | |||
if (this.feedbackTypes != null) { | |||
data['feedback_types'] = this.feedbackTypes.map((v) => v.toJson()).toList(); | |||
} | |||
if (this.stateImages != null) { | |||
data['state_images'] = this.stateImages.map((v) => v.toJson()).toList(); | |||
} | |||
data['required_login'] = this.requiredLogin; | |||
data['required_taobao_auth'] = this.requiredTaobaoAuth; | |||
data['skip_identifier'] = this.skipIdentifier; | |||
return data; | |||
} | |||
} | |||
class ItemStyle { | |||
String bgColor; | |||
String typeTextColor; | |||
String typeBgColor; | |||
String titleTextColor; | |||
String contentTextColor; | |||
String timeTextColor; | |||
ItemStyle({this.bgColor, this.typeTextColor, this.typeBgColor, this.titleTextColor, this.contentTextColor, this.timeTextColor}); | |||
ItemStyle.fromJson(Map<String, dynamic> json) { | |||
bgColor = json['bg_color']; | |||
typeTextColor = json['type_text_color']; | |||
typeBgColor = json['type_bg_color']; | |||
titleTextColor = json['title_text_color']; | |||
contentTextColor = json['content_text_color']; | |||
timeTextColor = json['time_text_color']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
data['bg_color'] = this.bgColor; | |||
data['type_text_color'] = this.typeTextColor; | |||
data['type_bg_color'] = this.typeBgColor; | |||
data['title_text_color'] = this.titleTextColor; | |||
data['content_text_color'] = this.contentTextColor; | |||
data['time_text_color'] = this.timeTextColor; | |||
return data; | |||
} | |||
} | |||
class FeedbackTypes { | |||
String typeId; | |||
String name; | |||
FeedbackTypes({this.typeId, this.name}); | |||
FeedbackTypes.fromJson(Map<String, dynamic> json) { | |||
typeId = json['type_id']; | |||
name = json['name']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
data['type_id'] = this.typeId; | |||
data['name'] = this.name; | |||
return data; | |||
} | |||
} | |||
class StateImages { | |||
String stateId; | |||
String img; | |||
StateImages({this.stateId, this.img}); | |||
StateImages.fromJson(Map<String, dynamic> json) { | |||
stateId = json['state_id']; | |||
img = json['img']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
data['state_id'] = this.stateId; | |||
data['img'] = this.img; | |||
return data; | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
import 'dart:io'; | |||
class FeedbackSaveModel { | |||
String type; | |||
String title; | |||
String content; | |||
List<File> files; | |||
FeedbackSaveModel({this.type, this.title, this.content, this.files}); | |||
} |
@@ -1,6 +1,14 @@ | |||
import 'package:flutter/material.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_data_model.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_record_style_model.dart'; | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
class FeedbackRecordItem extends StatelessWidget { | |||
FeedbackRecordStyleModel styleModel; | |||
FeedbackRecordDataItemModel dataModel; | |||
FeedbackRecordItem(this.styleModel, this.dataModel); | |||
@override | |||
Widget build(BuildContext context) { | |||
return Container( | |||
@@ -8,7 +16,7 @@ class FeedbackRecordItem extends StatelessWidget { | |||
padding: EdgeInsets.all(15), | |||
width: double.infinity, | |||
decoration: BoxDecoration( | |||
color: Colors.white, | |||
color: HexColor.fromHex(styleModel?.itemStyle?.bgColor ?? '#FFFFFF'), | |||
borderRadius: BorderRadius.circular(7.5), | |||
), | |||
child: Row( | |||
@@ -31,40 +39,53 @@ class FeedbackRecordItem extends StatelessWidget { | |||
width: 60, | |||
height: 60, | |||
decoration: BoxDecoration( | |||
color: Colors.green, | |||
// color: Colors.green, | |||
borderRadius: BorderRadius.circular(30), | |||
), | |||
child: CachedNetworkImage( | |||
imageUrl: getStateImageUrl(), | |||
), | |||
) | |||
], | |||
), | |||
); | |||
} | |||
String getStateImageUrl(){ | |||
String result = ''; | |||
int length = styleModel?.stateImages?.length ?? 0; | |||
if(length > 0){ | |||
styleModel.stateImages.forEach((element) { | |||
if(element.stateId == dataModel.state){ | |||
result = element.img; | |||
} | |||
}); | |||
} | |||
return result; | |||
} | |||
Widget _createTitle() { | |||
List<InlineSpan> list = List(); | |||
list.add(WidgetSpan( | |||
child: Container( | |||
padding: EdgeInsets.only(left: 2, right: 2, top: 3, bottom: 3), | |||
padding: EdgeInsets.only(left: 4.5, right: 4.5, top: 3, bottom: 3), | |||
margin: EdgeInsets.only(right: 4), | |||
child: Text( | |||
'功能异常', | |||
dataModel?.typeText ?? '其它', | |||
style: TextStyle( | |||
fontSize: 9, | |||
height: 1, | |||
// color: HexColor.fromHex(style.providerNameColor), | |||
color: Colors.white, | |||
color: HexColor.fromHex(styleModel?.itemStyle?.typeTextColor ?? '#FFFFFF'), | |||
), | |||
), | |||
decoration: BoxDecoration( | |||
color: Colors.redAccent, borderRadius: BorderRadius.circular(2.5)), | |||
decoration: BoxDecoration(color: HexColor.fromHex(styleModel?.itemStyle?.typeBgColor ?? '#FF4242'), borderRadius: BorderRadius.circular(2.5)), | |||
), | |||
)); | |||
list.add(TextSpan( | |||
text: '关于关闭提现功能的反馈', | |||
style: TextStyle( | |||
fontSize: 14, color: Color(0xff333333), fontWeight: FontWeight.bold), | |||
text: dataModel?.title ?? '', | |||
style: TextStyle(fontSize: 14, color: HexColor.fromHex(styleModel?.itemStyle?.titleTextColor ?? '#333333'), fontWeight: FontWeight.bold), | |||
)); | |||
return RichText( | |||
maxLines: 2, | |||
@@ -76,32 +97,44 @@ class FeedbackRecordItem extends StatelessWidget { | |||
Widget _createDesc() { | |||
return Container( | |||
child: Text( | |||
'我今天想使用我的提现功能的时候,发现官方已经把这个功能给暂时关闭了,所以我很伤心,不开心不开心不开心,所以我很伤心,不开心不开心不开心', | |||
style: TextStyle(fontSize: 11, color: Color(0xff999999)), | |||
dataModel?.content ?? '', | |||
style: TextStyle(fontSize: 11, color: HexColor.fromHex(styleModel?.itemStyle?.contentTextColor ?? '#999999')), | |||
), | |||
); | |||
} | |||
Widget _createImage() { | |||
return Container( | |||
margin: EdgeInsets.only(top: 4, bottom: 4), | |||
child: Wrap( | |||
children: <Widget>[ | |||
List<Widget> images = []; | |||
int length = dataModel?.imageList?.length ?? 0; | |||
if (length > 0) { | |||
for (int i = 0; i < length; i++) { | |||
images.add( | |||
Container( | |||
width: 60, | |||
height: 60, | |||
color: Colors.redAccent, | |||
child: CachedNetworkImage( | |||
imageUrl: dataModel?.imageList[i] ?? '', | |||
), | |||
), | |||
], | |||
), | |||
); | |||
); | |||
} | |||
return Container( | |||
margin: EdgeInsets.only(top: 4, bottom: 4), | |||
child: Wrap( | |||
children: images, | |||
), | |||
); | |||
} else { | |||
return Container(); | |||
} | |||
} | |||
Widget _createTime() { | |||
return Container( | |||
child: Text( | |||
'2020-06-23 01:00:06', | |||
style: TextStyle(fontSize: 11, color: Color(0xffd8d8d8)), | |||
dataModel?.createTime ?? '', | |||
style: TextStyle(fontSize: 11, color: HexColor.fromHex(styleModel?.itemStyle?.timeTextColor ?? '#D8D8D8')), | |||
), | |||
); | |||
} | |||
@@ -6,6 +6,7 @@ import 'package:sharesdk_plugin/sharesdk_register.dart'; | |||
import 'package:zhiying_base_widget/pages/about_us_page/about_us_page.dart'; | |||
import 'package:zhiying_base_widget/pages/bil_detail_page/bil_detail_page.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/feedback_page.dart'; | |||
import 'package:zhiying_base_widget/pages/feedback_page/feedback_record_page.dart'; | |||
import 'package:zhiying_base_widget/pages/goods_details_page/goods_details_page.dart'; | |||
import 'package:zhiying_base_widget/pages/home_page/home_page.dart'; | |||
import 'package:zhiying_base_widget/pages/hot_ranking_page/hot_ranking_page.dart'; | |||
@@ -58,6 +59,7 @@ import 'package:zhiying_base_widget/widgets/wallet_bil_detail/wallet_bil_detail. | |||
import 'package:zhiying_comm/util/defalut_widget_creater.dart'; | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
import 'pages/custom_page/custom_page.dart'; | |||
import 'pages/favorites_page/favorites_page.dart'; | |||
import 'pages/message_notice_page/message_notice_page.dart'; | |||
import 'pages/privacy_settings_page/privacy_settings_page.dart'; | |||
@@ -139,6 +141,7 @@ class BaseWidgetRegister { | |||
PageFactory.regist( | |||
'search_result_item', (model) => SearchResultItemPage(model)); | |||
PageFactory.regist('pub.flutter.feedback', (model) => FeedbackPage(model)); | |||
PageFactory.regist('pub.flutter.feedback_list', (model) => FeedbackRecordPage(model)); | |||
PageFactory.regist( | |||
'pub.flutter.wechat_teacher', (model) => WechatTeacherPage()); | |||
PageFactory.regist('pub.flutter.cash_out', (model) => WithdrawPage(model)); | |||
@@ -199,7 +202,7 @@ class BaseWidgetRegister { | |||
'pub.flutter.my_wallet_detail', (model) => BilDetailPage(model)); | |||
/// 通用模块 | |||
PageFactory.regist('pub.flutter.custom', (model) => EmptyPage()); | |||
PageFactory.regist('pub.flutter.custom', (model) => CustomPage(model)); | |||
} | |||
// 注册控件 | |||