Commit 662f4759 authored by 朱翠翠's avatar 朱翠翠

Merge branch 'test' of http://git.wanmeizhensuo.com/linshengyu/gm_flutter into zcc/flutter

parents b49cdade ec0284ff
......@@ -23,6 +23,10 @@ class LevelOneItem extends StatelessWidget {
return PlanItem(context);
} else if (cards.cardType == "hospital") {
return HospitalItem(context);
} else if (cards.cardType == "doctor") {
return DoctorItem(context);
} else if (cards.cardType == "diary") {
return DiaryItem(context);
}
}
......@@ -283,4 +287,173 @@ class LevelOneItem extends StatelessWidget {
],
);
}
Widget DiaryItem(BuildContext context) {
if (cards.plan == null && isDebug) {
throw new Exception();
}
return Container(
margin: EdgeInsets.only(left: 20, right: 20),
width: double.maxFinite,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 25,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 25,
height: 25,
child: ClipOval(
child: CachedNetworkImage(
imageUrl: cards.diary.user.portrait,
fit: BoxFit.cover,
),
),
),
Container(
margin: EdgeInsets.only(left: 8),
child: baseText(
cards.diary.user.userName, 13, Color(0xff999999)),
),
cards.diary.userLevel!=null&&cards.diary.userLevel.levelIcon!=null
? Container(
width: 31,
height: 12,
child: CachedNetworkImage(
imageUrl: cards.diary.userLevel.levelIcon,
),
)
: Container(
width: 0,
)
],
),
),
(cards.diary.images == null ||
cards.diary.images.isEmpty ||
cards.diary.images.length < 2)
? Container(
height: 0,
)
: Row(
children: <Widget>[
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(
cards.diary.images[0].imageHalf))),
alignment: Alignment.bottomLeft,
child: Container(
width: 65.5,
height: 18,
decoration: BoxDecoration(
color: Color(0x4c000000),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(4),
topRight: Radius.circular(2),
)),
alignment: Alignment.center,
child: baseText(
cards.diary.images[0].desc, 11, Colors.white),
),
),
),
Container(
width: 9,
),
Expanded(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(
cards.diary.images[1].imageHalf))),
alignment: Alignment.bottomLeft,
child: Container(
width: 65.5,
height: 18,
decoration: BoxDecoration(
color: Color(0x4c000000),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(4),
topRight: Radius.circular(2),
)),
alignment: Alignment.center,
child: baseText(
cards.diary.images[1].desc, 11, Colors.white),
),
),
)
],
),
Container(
margin: EdgeInsets.only(top: 12.5,bottom: 12),
child:Text(
cards.diary.content,
textScaleFactor: 1.0,
maxLines: 5,
overflow: TextOverflow.ellipsis,
style: TextStyle(
decoration: TextDecoration.none,
fontSize: 15,
color: Color(0xff464646),
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400),
) ,
)
,
baseText("项目${cards.diary.title}", 13, Color(0xff999999)),
Container(
margin: EdgeInsets.only(top: 12,bottom: 16),
height: 14,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 14,
height: 14,
child: Image.asset("assets/eye.png"),
),
Container(
margin: EdgeInsets.only(left: 5,right: 20),
child: baseText("${cards.diary.viewNum}", 12, Color(0xff999999)),
),
Container(
width: 14,
height: 14,
child: Image.asset("assets/arguement.png"),
),
Container(
margin: EdgeInsets.only(left: 5,right: 20),
child: baseText("${cards.diary.replyNum}", 12, Color(0xff999999)),
),
Container(
width: 14,
height: 14,
child: Image.asset("assets/heart.png"),
),
Container(
margin: EdgeInsets.only(left: 5,right: 20),
child: baseText("${cards.diary.voteNum}", 12, Color(0xff999999)),
)
],
),
),
Container(
width: double.maxFinite,
height: 0.5,
color: Color(0xffE5E5E5),
)
],
),
);
}
}
/*
* @author lsy
* @date 2020/7/4
**/
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gm_flutter/ClueModel/page/levelOne/LevelOneListModel.dart';
import 'package:gm_flutter/ClueModel/server/entity/LevelOneFeedList.dart';
import 'package:gm_flutter/commonModel/base/BaseComponent.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'LevelOneItem.dart';
class LevelOneList extends StatefulWidget {
double topHeight;
int planId;
String tabName;
LevelOneList(this.planId,this.tabName,this.topHeight);
@override
State<StatefulWidget> createState() => LevelOneListState();
}
class LevelOneListState extends State<LevelOneList> {
LevelOneListModel _model = LevelOneListModel();
RefreshController refreshController = RefreshController();
@override
void initState() {
super.initState();
_model.plan_id=widget.planId;
_model.tab_type=widget.tabName;
_model.refreshView(true);
}
@override
void dispose() {
refreshController.dispose();
_model.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return baseStateView(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height, _model.stateLive, pages(), () {
_model.refreshView(true);
},paddingTop: widget.topHeight);
}
Widget pages() {
return baseRefreshView(refreshController, () {}, null, null,
customScrollView: CustomScrollView(
// physics: NeverScrollableScrollPhysics(),
physics: ClampingScrollPhysics(),
// shrinkWrap: true,
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
StreamBuilder<List<Cards>>(
stream: _model.cardsLive.stream,
initialData: _model.cardsLive.data ?? [],
builder: (c, data) {
if (data.data.isEmpty && _model.page > 1) {
refreshController.loadNoData();
} else {
refreshController.loadComplete();
}
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return LevelOneItem(_model.data[index]);
},
childCount: _model.data.length,
),
);
},
),
StreamBuilder<List<Cards>>(
stream: _model.cardsLive.stream,
initialData: _model.data ?? [],
builder: (c, data) {
double height = MediaQuery.of(context).size.height -
40 -
widget.topHeight -
100 * _model.data.length;
return SliverToBoxAdapter(
child: Container(
height: height < 0 ? 0 : height,
),
);
},
),
],
), onLoading: () {
_model.loadMore();
}, pullDown: false, pullUp: true);
}
}
/*
* @author lsy
* @date 2020/7/4
**/
import 'package:flutter_common/commonModel/live/BaseModel.dart';
import 'package:flutter_common/commonModel/live/LiveData.dart';
import 'package:flutter_common/commonModel/toast/NativeToast.dart';
import 'package:gm_flutter/ClueModel/server/api/ClueApi.serv.dart';
import 'package:gm_flutter/ClueModel/server/entity/LevelOneFeedList.dart';
import 'package:gm_flutter/commonModel/bean/Pair.dart';
import 'package:gm_flutter/commonModel/net/DioUtil.dart';
import 'package:gm_flutter/commonModel/rx/RxDispose.dart';
import 'package:gm_flutter/commonModel/util/PrintUtil.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
class LevelOneListModel extends BaseModel {
int plan_id;
String tab_type;
int page = 1;
LiveData<int> stateLive = LiveData();
LiveData<List<Cards>> cardsLive = LiveData();
List<Cards> data = [];
RxDispose rxDispose = RxDispose();
void refreshView(bool clear, {RefreshController refreshListener}) {
if (clear) {
data.clear();
page = 1;
}
ClueApiImpl.getInstance()
.getLevelOneList(
DioUtil.getInstance().getDio(), plan_id, tab_type, page)
.listen((event) {
if (event.error == 0) {
if ((event.data.cards == null || event.data.cards.isEmpty) &&
page == 1) {
stateLive.notifyView(EMPTY);
} else {
data.addAll(event.data.cards);
cardsLive.notifyView(data);
stateLive.notifyView(ENDLOADING);
}
} else {
NativeToast.showNativeToast(event.message);
stateLive.notifyView(FAIL);
}
})
.addToDispose(rxDispose)
.onError((err) {
PrintUtil.printBug(err);
stateLive.notifyView(FAIL);
});
}
@override
void dispose() {
rxDispose.dispose();
stateLive.dispost();
cardsLive.dispost();
}
void loadMore() {
page++;
refreshView(false);
}
}
......@@ -5,8 +5,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_common/commonModel/live/BaseModel.dart';
import 'package:flutter_common/commonModel/live/LiveData.dart';
import 'package:flutter_common/commonModel/toast/NativeToast.dart';
import 'package:gm_flutter/ClueModel/server/api/ClueApi.serv.dart';
import 'package:gm_flutter/ClueModel/server/entity/LevelOneFeedList.dart';
import 'package:gm_flutter/ClueModel/server/entity/PlanOverViewBean.dart';
import 'package:gm_flutter/commonModel/GMBase.dart';
import 'package:gm_flutter/commonModel/bean/Pair.dart';
import 'package:gm_flutter/commonModel/rx/RxDispose.dart';
......@@ -15,64 +17,45 @@ import 'package:pull_to_refresh/pull_to_refresh.dart';
class LevelOneModel extends BaseModel {
LiveData<double> appBarLive = LiveData();
LiveData<List<String>> rectLive = LiveData();
LiveData<List<String>> explainLive = LiveData();
LiveData<bool> showTab = LiveData();
LiveData<int> topIndexLive = new LiveData();
LiveData<double> topScrollLive = new LiveData();
LiveData<bool> loadingLive = LiveData();
LiveData<int> stateLive = LiveData();
LiveData<double> textLive = LiveData();
RxDispose rxDispose = RxDispose();
LiveData<Pair<int, List<Cards>>> cardsLive = LiveData();
Map<int, List<Cards>> data = new Map();
List list = ["plan", "hospital", "doctor", "diary"];
List<Tabs> tabsList = [];
List pageList = [1, 1, 1, 1];
List pageHeightList = [118, 149, 118, 118];
int plan_id = 0;
PlanOverData planoverItem;
void refreshView(bool clear, {RefreshController refreshListener}) {
Future.delayed(Duration(seconds: 1), () {
Cards cards = Cards(
cardType: "plan",
plan: Plan(planName: "ww", minPrice: "50", maxPrice: "500"));
List<Cards> a = [
cards,
cards,
cards,
cards,
cards,
cards,
cards,
cards,
cards,
cards,
cards,
cards,
];
List<Cards> b = [cards];
print("INDEX ${currentIndex}");
if (currentIndex == 1) {
data[currentIndex] = b;
} else {
data[currentIndex] = a;
}
cardsLive.notifyView(Pair(ENDLOADING, data));
});
int index = currentIndex;
if (clear) {
data.clear();
pageList = [1, 1, 1, 1];
}
void init(VoidCallback callback) {
ClueApiImpl.getInstance()
.getLevelOneList(
DioUtil.getInstance().getDio(), 123, list[index], pageList[index])
.listen((event) {})
.getPlanOverView(DioUtil.getInstance().getDio(), plan_id)
.listen((event) {
if (event.error == 0) {
planoverItem=event.data;
tabsList = event.data.tabs;
stateLive.notifyView(ENDLOADING);
callback();
} else {
NativeToast.showNativeToast(event.message);
stateLive.notifyView(FAIL);
}
})
.addToDispose(rxDispose)
.onError((err) {
PrintUtil.printBug(err);
});
stateLive.notifyView(FAIL);
});
}
int currentIndex = 0;
@override
......@@ -80,26 +63,14 @@ class LevelOneModel extends BaseModel {
showTab.dispost();
cardsLive.dispost();
appBarLive.dispost();
rectLive.dispost();
topIndexLive.dispost();
topScrollLive.dispost();
loadingLive.dispost();
stateLive.dispost();
textLive.dispost();
}
void selectPage(int index) {
if (currentIndex == index) {
return;
}
currentIndex = index;
if (data[currentIndex] == null) {
cardsLive.notifyView(Pair(LOADING, null));
refreshView(true);
} else {
cardsLive.notifyView(Pair(ENDLOADING, data[currentIndex]));
}
}
void loadMore() {
void selectTab(int index) {
}
}
......@@ -2,7 +2,9 @@
* @author lsy
* @date 2020/6/29
**/
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
......@@ -10,7 +12,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_common/commonModel/live/LiveData.dart';
import 'package:gm_flutter/ClueModel/page/levelOne/LevelOneItem.dart';
import 'package:gm_flutter/ClueModel/page/levelOne/LevelOneList.dart';
import 'package:gm_flutter/ClueModel/page/levelOne/LevelOneModel.dart';
import 'package:gm_flutter/ClueModel/page/levelTwo/LevelTwoPage.dart';
import 'package:gm_flutter/ClueModel/server/entity/LevelOneFeedList.dart';
import 'package:gm_flutter/ClueModel/util/PosUtil.dart';
import 'package:gm_flutter/ClueModel/view/FiveStarView.dart';
......@@ -18,6 +22,7 @@ import 'package:gm_flutter/commonModel/base/BaseComponent.dart';
import 'package:gm_flutter/commonModel/base/BaseState.dart';
import 'package:gm_flutter/commonModel/bean/Pair.dart';
import 'package:gm_flutter/commonModel/util/DartUtil.dart';
import 'package:gm_flutter/commonModel/view/baseRefreshIndicator.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'LevelOneBar.dart';
......@@ -27,8 +32,8 @@ class LevelOnePage extends StatefulWidget {
State<StatefulWidget> createState() => LevelOneState();
}
class LevelOneState extends BaseState<LevelOnePage>
with TickerProviderStateMixin {
class LevelOneState extends BaseState<LevelOnePage> {
int planId = 10;
LevelOneModel _model = new LevelOneModel();
RefreshController _refreshController = RefreshController();
PageController pageController = new PageController();
......@@ -41,26 +46,16 @@ class LevelOneState extends BaseState<LevelOnePage>
@override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 3), () {
_model.loadingLive.notifyView(false);
_model.refreshView(true);
_model.init(() {
setState(() {});
});
pageController.addListener(() {
if (screenWidth != null) {
_model.topScrollLive.notifyView(
pageController.offset / screenWidth * (screenWidth / 4));
}
});
oneList.add(head());
oneList.add(good());
oneList.add(rect());
oneList.add(explain());
oneList.add(Container(
height: 5,
color: Color(0xffF7F6FA),
));
oneList.add(feed());
oneList.add(pages());
// pageController.addListener(() {
// if (screenWidth != null) {
// _model.topScrollLive.notifyView(
// pageController.offset / screenWidth * (screenWidth / 4));
// }
// });
}
void _onScroll(double offset) {
......@@ -104,178 +99,141 @@ class LevelOneState extends BaseState<LevelOnePage>
@override
Widget buildItem(BuildContext context) {
oneList.clear();
oneList.add(good());
oneList.add(rect());
oneList.add(explain());
oneList.add(Container(
height: 5,
color: Color(0xffF7F6FA),
));
screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: StreamBuilder<bool>(
stream: _model.loadingLive.stream,
initialData: true,
builder: (c, data) {
if (data.data) {
return loadingItem();
} else {
return home();
}
},
)));
child: baseStateView(
MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height,
_model.stateLive,
Container(
child: NestedScrollViewRefreshIndicator(
onRefresh: () async {
Completer completer = new Completer();
_model.init(() {
setState(() {});
completer.complete();
});
return completer.future;
},
child: newHomeWarp()),
), () {
_model.init(() {
setState(() {});
});
})));
}
Widget home() {
return Column(
Widget newHomeWarp() {
return Stack(
children: <Widget>[
Expanded(
child: Stack(
children: <Widget>[
MediaQuery.removePadding(
removeTop: true,
context: context,
child: NotificationListener(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollUpdateNotification &&
scrollNotification.metrics.axisDirection.index == 2) {
_onScroll(scrollNotification.metrics.pixels);
}
return false;
},
child: baseRefreshView(
_refreshController,
() {
//TODO
// _refreshController.refreshCompleted();
},
null,
null,
customScrollView: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return oneList[index];
},
childCount: oneList.length,
),
),
],
),
pullUp: true,
onLoading: () {
_model.loadMore();
},
),
)),
StreamBuilder<double>(
stream: _model.appBarLive.stream,
initialData: _model.appBarLive.data ?? 0.0,
builder: (c, data) {
return Opacity(
opacity: data.data,
child: Container(
height: 86,
decoration: BoxDecoration(color: Colors.white),
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 20),
child: Text('文案'),
),
),
),
);
},
),
Positioned(
top: 86,
child: StreamBuilder<bool>(
stream: _model.showTab.stream,
initialData: _model.showTab.data ?? false,
builder: (c, data) {
return Visibility(
visible: data.data,
child: Container(
width: MediaQuery.of(context).size.width,
color: Colors.white,
child: MessageBarView(
topIndexLive: _model.topIndexLive,
topScrollLive: _model.topScrollLive,
pageController: pageController,
),
));
},
),
),
baseSliverBack(() {
Navigator.of(context).pop();
})
],
),
),
Container(
height: 0.5,
width: double.maxFinite,
color: Color(0xffE5E5E5),
),
Container(
width: double.maxFinite,
height: 55,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 18),
width: 30,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 22,
height: 22,
child: Image.asset("assets/vs_black.png"),
),
Container(
margin: EdgeInsets.only(top: 3),
child: baseText("去比较", 10, Color(0xff282828)),
)
],
),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {},
child: Container(
margin: EdgeInsets.only(left: 15),
width: 135,
height: 40,
decoration: BoxDecoration(
color: Color(0xff51CDC7),
borderRadius: BorderRadius.circular(20)),
alignment: Alignment.center,
child: baseText("咨询", 14, Colors.white, bold: true),
)),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {},
child: Container(
margin: EdgeInsets.only(left: 15),
width: 135,
height: 40,
decoration: BoxDecoration(
color: Color(0xffF96079),
borderRadius: BorderRadius.circular(20)),
alignment: Alignment.center,
child: baseText("获取底价", 14, Colors.white, bold: true),
)),
Expanded(
child: Container(),
)
],
),
)
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: NotificationListener(
onNotification: (scrollNotification) {
if (scrollNotification is KeepAliveNotification ||
scrollNotification is OverscrollIndicatorNotification) {
return false;
}
if (scrollNotification is ScrollUpdateNotification &&
scrollNotification.metrics.axisDirection.index == 2) {}
if (scrollNotification.depth == 0) {
if (scrollNotification.metrics.pixels > 80) {
_model.textLive.notifyView(
(scrollNotification.metrics.pixels - 80) / 40 > 1.0
? 1.0
: (scrollNotification.metrics.pixels - 80) / 40);
} else {
_model.textLive.notifyView(0.0);
}
} else if (scrollNotification.depth == 2) {
if (scrollNotification.metrics.pixels < 50 &&
scrollNotification.metrics.pixels > 0) {
_model.textLive.notifyView(1.0);
}
}
return false;
},
child: newHome(),
)),
baseSliverTitle(
"title", MediaQuery.of(context).size.width, _model.textLive),
baseSliverBack(() {
Navigator.of(context).pop();
}),
],
);
}
Widget newHome() {
List<Widget> list = [];
var d = MediaQueryData.fromWindow(window).padding.top;
for (int i = 0; i < _model.tabsList.length; i++) {
list.add(
LevelOneList(planId, _model.tabsList[i].tabType, kToolbarHeight + d)
.toActive());
}
final double statusBarHeight = MediaQuery.of(context).padding.top;
final double pinnedHeaderHeight = statusBarHeight + kToolbarHeight;
return DefaultTabController(
length: _model.tabsList.length,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: baseSliverAppBar(
// _model.imageUrl
"https://pic.igengmei.com/2018/09/11/1513/b7e825a4e4c1-w")),
SliverList(
delegate:
SliverChildBuilderDelegate((BuildContext c, int i) {
if (i == 0) {
return Container(
height: pinnedHeaderHeight,
);
}
return oneList[i - 1];
}, childCount: oneList.length + 1)),
SliverPersistentHeader(
pinned: true,
delegate: StickyTabBarDelegate(
child: Container(
height: 40,
color: Colors.white,
child: baseTabBar(null, getTabs(), (index) {
_model.selectTab(index);
}, scroll: false),
)),
),
];
},
body: TabBarView(children: list)));
}
List<Widget> getTabs() {
List<Widget> list = [];
for (int i = 0; i < _model.tabsList.length; i++) {
list.add(baseTabBarItem(_model.tabsList[i].name,
leftPadding: i == 0 ? 24 : 28,
rightPadding: i == _model.tabsList.length - 1 ? 24 : 28));
}
return list;
}
Widget head() {
return Container(
key: keyTop,
......@@ -292,8 +250,13 @@ class LevelOneState extends BaseState<LevelOnePage>
}
Widget good() {
if (_model.planoverItem == null) {
return Container(
height: 54,
);
}
return Container(
width: double.maxFinite,
width: MediaQuery.of(context).size.width,
height: 54,
margin: EdgeInsets.only(top: 18, bottom: 12),
child: Stack(
......@@ -301,12 +264,14 @@ class LevelOneState extends BaseState<LevelOnePage>
Positioned(
top: 6,
left: 15,
child: baseText("TODO", 18, Color(0xff282828), bold: true),
child: baseText(_model.planoverItem.name, 18, Color(0xff282828),
bold: true),
),
Positioned(
bottom: 8,
left: 15,
child: baseText("TODO", 12, Color(0xff999999)),
child: baseText(
_model.planoverItem.planDescription, 12, Color(0xff999999)),
),
Positioned(
right: 0,
......@@ -331,16 +296,18 @@ class LevelOneState extends BaseState<LevelOnePage>
baseText("好评率", 11, Color(0xff282828)),
Container(
margin: EdgeInsets.only(left: 4),
child: baseText("99", 20, Color(0xffFF5963)),
child: baseText("${_model.planoverItem.positiveRate}", 20,
Color(0xffFF5963)),
),
baseText("%", 11, Color(0xffFF5963)),
// baseText("%", 11, Color(0xffFF5963)),
],
),
),
Positioned(
right: 15,
bottom: 8,
child: baseText("销量110", 11, Color(0xff666666)),
child: baseText(
"销量${_model.planoverItem.salesCount}", 11, Color(0xff666666)),
)
],
),
......@@ -348,96 +315,93 @@ class LevelOneState extends BaseState<LevelOnePage>
}
Widget rect() {
return StreamBuilder<List<String>>(
stream: _model.rectLive.stream,
initialData: ["w", "w", "q", "w"],
builder: (c, data) {
List<Widget> list = List();
for (int i = 0; i < data.data.length; i += 2) {
list.add(Expanded(
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
baseText(data.data[i], 14, Color(0xff282828), bold: true),
baseText(data.data[i + 1], 11, Color(0xff999999)),
],
),
),
));
if (i < data.data.length - 2) {
list.add(Container(
width: 0.5,
height: 18,
color: Color(0xFFE5E5E5),
));
}
}
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Color(0xffF9F8FB),
),
width: double.maxFinite,
height: 62,
margin: EdgeInsets.only(left: 15, right: 15, bottom: 20),
child: Row(
if (_model.planoverItem == null) {
return Container(
height: 62,
);
}
List<Widget> list = List();
int i = 0;
_model.planoverItem.overviewAttrs.forEach((element) {
list.add(Expanded(
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: list,
children: <Widget>[
baseText(element.attrName, 14, Color(0xff282828), bold: true),
baseText(element.attrValue, 11, Color(0xff999999)),
],
),
);
},
),
));
if (i != _model.planoverItem?.overviewAttrs.length - 1) {
list.add(Container(
width: 0.5,
height: 18,
color: Color(0xFFE5E5E5),
));
}
i++;
});
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Color(0xffF9F8FB),
),
width: double.maxFinite,
height: 62,
margin: EdgeInsets.only(left: 15, right: 15, bottom: 20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: list,
),
);
}
Widget explain() {
return StreamBuilder<List<String>>(
stream: _model.explainLive.stream,
initialData: ["www", "www", "www", "??"],
builder: (c, data) {
List<Widget> list = [];
List<Widget> list = [];
list.add(Container(
height: 31,
child: Row(
children: <Widget>[
baseText("项目说明", 15, Color(0xff282828)),
Expanded(
child: Container(),
),
GestureDetector(
onTap: () {
//TODO
},
behavior: HitTestBehavior.opaque,
child: baseText("了解更多", 12, Color(0xff3FB5AF)),
)
],
),
));
if (_model.planoverItem != null) {
_model.planoverItem.explanationAttrs.forEach((element) {
list.add(Container(
height: 31,
height: 28,
child: Row(
children: <Widget>[
baseText("项目说明", 15, Color(0xff282828)),
Expanded(
child: Container(),
),
GestureDetector(
onTap: () {
//TODO
},
behavior: HitTestBehavior.opaque,
child: baseText("了解更多", 12, Color(0xff3FB5AF)),
baseText(element.attrName, 13, Color(0xff999999)),
Container(
margin: EdgeInsets.only(left: 12),
child: baseText(element.attrValue, 13, Color(0xff666666)),
)
],
),
));
for (int i = 0; i < data.data.length; i += 2) {
list.add(Container(
height: 28,
child: Row(
children: <Widget>[
baseText(data.data[i], 13, Color(0xff999999)),
Container(
margin: EdgeInsets.only(left: 12),
child: baseText(data.data[i + 1], 13, Color(0xff666666)),
)
],
),
));
}
return Container(
margin: EdgeInsets.only(left: 15, right: 15, bottom: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: list,
),
);
},
});
}
return Container(
margin: EdgeInsets.only(left: 15, right: 15, bottom: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: list,
),
);
}
......@@ -451,65 +415,159 @@ class LevelOneState extends BaseState<LevelOnePage>
));
}
Widget pages() {
return StreamBuilder<Pair<int, List<Cards>>>(
stream: _model.cardsLive.stream,
initialData: _model.cardsLive.data ?? Pair(LOADING, null),
builder: (c, data) {
double height = MediaQuery.of(context).size.height - 86 - 55.5 - 49;
if (_model.data[_model.currentIndex] != null &&
_model.data[_model.currentIndex].length > 0) {
height = max(
_model.data[_model.currentIndex].length *
_model.pageHeightList[_model.currentIndex] *
1.0,
height);
}
return Container(
width: double.maxFinite,
height: height,
child: PageView.builder(
itemBuilder: (c, pageIndex) {
if (data.data.first == FAIL) {
return errorItem(MediaQuery.of(context).size.width, height, () {
_model.refreshView(true);
});
}
if (data.data.first == LOADING ||
_model.data[pageIndex] == null) {
return loadingItem();
}
if (data.data.second.length == 0) {
if (_model.pageList[pageIndex] == 1) {
return emptyItem(MediaQuery.of(context).size.width, height);
} else {
_refreshController.loadNoData();
}
} else {
_refreshController.loadComplete();
}
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (c, index) {
return LevelOneItem(_model.data[pageIndex][index]);
},
itemCount: _model.data[pageIndex].length,
);
},
allowImplicitScrolling: false,
dragStartBehavior: DragStartBehavior.down,
controller: pageController,
itemCount: 4,
onPageChanged: (index) {
_refreshController.resetNoData();
setState(() {
});
_model.topIndexLive.notifyView(index);
_model.selectPage(index);
},
),
);
},
);
}
// Widget home() {
// return Column(
// children: <Widget>[
// Expanded(
// child: Stack(
// children: <Widget>[
// MediaQuery.removePadding(
// removeTop: true,
// context: context,
// child: NotificationListener(
// onNotification: (scrollNotification) {
// if (scrollNotification is ScrollUpdateNotification &&
// scrollNotification.metrics.axisDirection.index == 2) {
// _onScroll(scrollNotification.metrics.pixels);
// }
// return false;
// },
// child: baseRefreshView(
// _refreshController,
// () {
// //TODO
//// _refreshController.refreshCompleted();
// },
// null,
// null,
// customScrollView: CustomScrollView(
// slivers: <Widget>[
// SliverList(
// delegate: SliverChildBuilderDelegate(
// (BuildContext context, int index) {
// return oneList[index];
// },
// childCount: oneList.length,
// ),
// ),
// ],
// ),
// pullUp: true,
// onLoading: () {
// _model.loadMore();
// },
// ),
// )),
// StreamBuilder<double>(
// stream: _model.appBarLive.stream,
// initialData: _model.appBarLive.data ?? 0.0,
// builder: (c, data) {
// return Opacity(
// opacity: data.data,
// child: Container(
// height: 86,
// decoration: BoxDecoration(color: Colors.white),
// child: Center(
// child: Padding(
// padding: EdgeInsets.only(top: 20),
// child: Text('文案'),
// ),
// ),
// ),
// );
// },
// ),
// baseSliverTitle("title", MediaQuery.of(context).size.width,
// _model.appBarLive),
// Positioned(
// top: 86,
// child: StreamBuilder<bool>(
// stream: _model.showTab.stream,
// initialData: _model.showTab.data ?? false,
// builder: (c, data) {
// return Visibility(
// visible: data.data,
// child: Container(
// width: MediaQuery.of(context).size.width,
// color: Colors.white,
// child: MessageBarView(
// topIndexLive: _model.topIndexLive,
// topScrollLive: _model.topScrollLive,
// pageController: pageController,
// ),
// ));
// },
// ),
// ),
// baseSliverBack(() {
// Navigator.of(context).pop();
// })
// ],
// ),
// ),
// Container(
// height: 0.5,
// width: double.maxFinite,
// color: Color(0xffE5E5E5),
// ),
// Container(
// width: double.maxFinite,
// height: 55,
// child: Row(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Container(
// margin: EdgeInsets.only(left: 18),
// width: 30,
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// Container(
// width: 22,
// height: 22,
// child: Image.asset("assets/vs_black.png"),
// ),
// Container(
// margin: EdgeInsets.only(top: 3),
// child: baseText("去比较", 10, Color(0xff282828)),
// )
// ],
// ),
// ),
// GestureDetector(
// behavior: HitTestBehavior.opaque,
// onTap: () {},
// child: Container(
// margin: EdgeInsets.only(left: 15),
// width: 135,
// height: 40,
// decoration: BoxDecoration(
// color: Color(0xff51CDC7),
// borderRadius: BorderRadius.circular(20)),
// alignment: Alignment.center,
// child: baseText("咨询", 14, Colors.white, bold: true),
// )),
// GestureDetector(
// behavior: HitTestBehavior.opaque,
// onTap: () {},
// child: Container(
// margin: EdgeInsets.only(left: 15),
// width: 135,
// height: 40,
// decoration: BoxDecoration(
// color: Color(0xffF96079),
// borderRadius: BorderRadius.circular(20)),
// alignment: Alignment.center,
// child: baseText("获取底价", 14, Colors.white, bold: true),
// )),
// Expanded(
// child: Container(),
// )
// ],
// ),
// )
// ],
// );
// }
}
......@@ -8,7 +8,7 @@ class PlanOverViewBean {
Null extra;
Null errorExtra;
UserType userType;
Data data;
PlanOverData data;
PlanOverViewBean(
{this.error,
......@@ -26,7 +26,7 @@ class PlanOverViewBean {
userType = json['user_type'] != null
? new UserType.fromJson(json['user_type'])
: null;
data = json['data'] != null ? new Data.fromJson(json['data']) : null;
data = json['data'] != null ? new PlanOverData.fromJson(json['data']) : null;
}
Map<String, dynamic> toJson() {
......@@ -56,7 +56,7 @@ class UserType {
}
}
class Data {
class PlanOverData {
Banner banner;
String name;
String positiveRate;
......@@ -67,7 +67,7 @@ class Data {
List<ExplanationAttrs> explanationAttrs;
List<Tabs> tabs;
Data(
PlanOverData(
{this.banner,
this.name,
this.positiveRate,
......@@ -78,7 +78,7 @@ class Data {
this.explanationAttrs,
this.tabs});
Data.fromJson(Map<String, dynamic> json) {
PlanOverData.fromJson(Map<String, dynamic> json) {
banner =
json['banner'] != null ? new Banner.fromJson(json['banner']) : null;
name = json['name'];
......
......@@ -143,42 +143,49 @@ Widget loadingItem({bool needBackground = false}) {
Widget netErrorItem() {}
Widget errorItem(double width, double height, VoidCallback retry,
{String errorText, String retryText}) {
{String errorText, String retryText,double paddingTop}) {
return Container(
width: width,
height: height,
color: Colors.white,
padding: EdgeInsets.only(top: paddingTop),
alignment: Alignment.topCenter,
child: Container(
width: 180,
height: 315.5,
margin: EdgeInsets.only(top: 62),
child: Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
Positioned(
left: 0,
top: 0,
child: Container(
width: 180,
height: 315.5,
margin: EdgeInsets.only(top: 62),
child: Column(
children: <Widget>[
Container(
width: 180,
height: 186.5,
child: Image.asset("assets/error.png"),
),
),
Positioned(
top: 166,
child: Container(
width: 180,
alignment: Alignment.center,
child: baseText(
errorText ?? "原谅我一看到美人就不淡定", 15, Color(0xff666666)),
child: Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
Positioned(
left: 0,
top: 0,
child: Container(
width: 180,
height: 186.5,
child: Image.asset("assets/error.png"),
),
),
Positioned(
top: 166,
child: Container(
width: 180,
alignment: Alignment.center,
child: baseText(
errorText ?? "原谅我一看到美人就不淡定", 15, Color(0xff666666)),
),
),
],
),
),
),
Positioned(
bottom: 60,
child: Container(
Container(
width: 150,
height: 40,
margin: EdgeInsets.only(top: 17),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
......@@ -190,12 +197,10 @@ Widget errorItem(double width, double height, VoidCallback retry,
).gestureDetector(() {
retry();
}),
),
Positioned(
bottom: 0,
child: Container(
Container(
width: 150,
height: 40,
margin: EdgeInsets.only(top: 20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Color(0xffFF5963), width: 0.5)),
......@@ -204,10 +209,8 @@ Widget errorItem(double width, double height, VoidCallback retry,
).gestureDetector(() {
AppSettings.openWIFISettings();
}),
)
],
),
));
],
)));
}
//TODO
......@@ -373,8 +376,9 @@ Widget normalRefreshHeader() {
);
}
Widget emptyItem(double width, double height, {String detail}) {
Widget emptyItem(double width, double height, {String detail,double paddingTop}) {
return Container(
padding: EdgeInsets.only(top: paddingTop),
width: width,
height: height,
color: Colors.white,
......@@ -383,20 +387,34 @@ Widget emptyItem(double width, double height, {String detail}) {
width: 175,
height: 188,
margin: EdgeInsets.only(top: 62.5),
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 175,
height: 188,
child: Image.asset("assets/empty.png"),
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
Container(
width: 175,
height: 188,
child: Image.asset("assets/empty.png"),
),
Positioned(
bottom: 17,
child: baseText(detail ?? "此处太寂寥,转转别处吧", 15, Color(0xff666666)),
)
],
),
),
Positioned(
bottom: 17,
child: baseText(detail ?? "此处太寂寥,转转别处吧", 15, Color(0xff666666)),
Container(
height:0,
color: Colors.white,
)
],
),
)
),
);
}
......@@ -491,7 +509,7 @@ Widget baseSliverTitle(String text, double width, LiveData liveData) {
}
Widget baseStateView(double width, double height, LiveData<int> stateLive,
Widget home, VoidCallback retry) {
Widget home, VoidCallback retry,{double paddingTop}) {
return StreamBuilder(
stream: stateLive.stream,
initialData: stateLive.data ?? LOADING,
......@@ -499,9 +517,9 @@ Widget baseStateView(double width, double height, LiveData<int> stateLive,
if (data.data == LOADING) {
return loadingItem();
} else if (data.data == FAIL) {
return errorItem(width, height, retry);
return errorItem(width, height, retry,paddingTop: paddingTop);
} else if (data.data == EMPTY) {
return emptyItem(width, height);
return emptyItem(width, height,paddingTop: paddingTop);
}
return home;
},
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:gm_flutter/commonModel/GMBase.dart';
import 'package:lottie/lottie.dart';
// The over-scroll distance that moves the indicator to its maximum
// displacement, as a percentage of the scrollable's container extent.
const double _kDragContainerExtentPercentage = 0.25;
// How much the scroll's drag gesture can overshoot the RefreshIndicator's
// displacement; max displacement = _kDragSizeFactorLimit * displacement.
const double _kDragSizeFactorLimit = 1.5;
// When the scroll ends, the duration of the refresh indicator's animation
// to the RefreshIndicator's displacement.
const Duration _kIndicatorSnapDuration = Duration(milliseconds: 150);
// The duration of the ScaleTransition that starts when the refresh action
// has completed.
const Duration _kIndicatorScaleDuration = Duration(milliseconds: 200);
/// The signature for a function that's called when the user has dragged a
/// [NestedScrollViewRefreshIndicator] far enough to demonstrate that they want the app to
/// refresh. The returned [Future] must complete when the refresh operation is
/// finished.
///
/// Used by [NestedScrollViewRefreshIndicator.onRefresh].
typedef NestedScrollViewRefreshCallback = Future<void> Function();
// The state machine moves through these modes only when the scrollable
// identified by scrollableKey has been scrolled to its min or max limit.
enum _RefreshIndicatorMode {
drag, // Pointer is down.
armed, // Dragged far enough that an up event will run the onRefresh callback.
snap, // Animating to the indicator's final "displacement".
refresh, // Running the refresh callback.
done, // Animating the indicator's fade-out after refreshing.
canceled, // Animating the indicator's fade-out after not arming.
}
/// A widget that supports the Material "swipe to refresh" idiom.
///
/// When the child's [Scrollable] descendant overscrolls, an animated circular
/// progress indicator is faded into view. When the scroll ends, if the
/// indicator has been dragged far enough for it to become completely opaque,
/// the [onRefresh] callback is called. The callback is expected to update the
/// scrollable's contents and then complete the [Future] it returns. The refresh
/// indicator disappears after the callback's [Future] has completed.
///
/// If the [Scrollable] might not have enough content to overscroll, consider
/// settings its `physics` property to [AlwaysScrollableScrollPhysics]:
///
/// ```dart
/// ListView(
/// physics: const AlwaysScrollableScrollPhysics(),
/// children: ...
// )
/// ```
///
/// Using [AlwaysScrollableScrollPhysics] will ensure that the scroll view is
/// always scrollable and, therefore, can trigger the [NestedScrollViewRefreshIndicator].
///
/// A [NestedScrollViewRefreshIndicator] can only be used with a vertical scroll view.
///
/// See also:
///
/// * <https://material.google.com/patterns/swipe-to-refresh.html>
/// * [NestedScrollViewRefreshIndicatorState], can be used to programmatically show the refresh indicator.
/// * [RefreshProgressIndicator], widget used by [NestedScrollViewRefreshIndicator] to show
/// the inner circular progress spinner during refreshes.
/// * [CupertinoSliverRefreshControl], an iOS equivalent of the pull-to-refresh pattern.
/// Must be used as a sliver inside a [CustomScrollView] instead of wrapping
/// around a [ScrollView] because it's a part of the scrollable instead of
/// being overlaid on top of it.
class NestedScrollViewRefreshIndicator extends StatefulWidget {
/// Creates a refresh indicator.
///
/// The [onRefresh], [child], and [notificationPredicate] arguments must be
/// non-null. The default
/// [displacement] is 40.0 logical pixels.
const NestedScrollViewRefreshIndicator({
Key key,
@required this.child,
this.displacement = 55.0,
@required this.onRefresh,
this.color,
this.backgroundColor,
this.notificationPredicate = nestedScrollViewScrollNotificationPredicate,
this.semanticsLabel,
this.semanticsValue,
}) : assert(child != null),
assert(onRefresh != null),
assert(notificationPredicate != null),
super(key: key);
/// The widget below this widget in the tree.
///
/// The refresh indicator will be stacked on top of this child. The indicator
/// will appear when child's Scrollable descendant is over-scrolled.
///
/// Typically a [ListView] or [CustomScrollView].
final Widget child;
/// The distance from the child's top or bottom edge to where the refresh
/// indicator will settle. During the drag that exposes the refresh indicator,
/// its actual displacement may significantly exceed this value.
final double displacement;
/// A function that's called when the user has dragged the refresh indicator
/// far enough to demonstrate that they want the app to refresh. The returned
/// [Future] must complete when the refresh operation is finished.
final NestedScrollViewRefreshCallback onRefresh;
/// The progress indicator's foreground color. The current theme's
/// [ThemeData.accentColor] by default.
final Color color;
/// The progress indicator's background color. The current theme's
/// [ThemeData.canvasColor] by default.
final Color backgroundColor;
/// A check that specifies whether a [ScrollNotification] should be
/// handled by this widget.
///
/// By default, checks whether `notification.depth == 0`. Set it to something
/// else for more complicated layouts.
final ScrollNotificationPredicate notificationPredicate;
/// {@macro flutter.material.progressIndicator.semanticsLabel}
///
/// This will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel]
/// if it is null.
final String semanticsLabel;
/// {@macro flutter.material.progressIndicator.semanticsValue}
final String semanticsValue;
@override
NestedScrollViewRefreshIndicatorState createState() =>
NestedScrollViewRefreshIndicatorState();
}
/// Contains the state for a [NestedScrollViewRefreshIndicator]. This class can be used to
/// programmatically show the refresh indicator, see the [show] method.
class NestedScrollViewRefreshIndicatorState
extends State<NestedScrollViewRefreshIndicator>
with TickerProviderStateMixin<NestedScrollViewRefreshIndicator> {
AnimationController _positionController;
Animation<double> _positionFactor;
Animation<double> _value;
Animation<Color> _valueColor;
_RefreshIndicatorMode _mode;
Future<void> _pendingRefreshFuture;
bool _isIndicatorAtTop;
double _dragOffset;
static final Animatable<double> _threeQuarterTween =
Tween<double>(begin: 0.0, end: 0.75);
static final Animatable<double> _kDragSizeFactorLimitTween =
Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
@override
void initState() {
super.initState();
_positionController = AnimationController(vsync: this);
_positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
_value = _positionController.drive(
_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
}
@override
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
_valueColor = _positionController.drive(
ColorTween(
begin: (widget.color ?? theme.accentColor).withOpacity(0.0),
end: (widget.color ?? theme.accentColor).withOpacity(1.0))
.chain(CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit))),
);
super.didChangeDependencies();
}
@override
void dispose() {
_positionController.dispose();
super.dispose();
}
double maxContainerExtent = 0.0;
bool _handleScrollNotification(ScrollNotification notification) {
if (!widget.notificationPredicate(notification)) {
return false;
}
maxContainerExtent =
math.max(notification.metrics.viewportDimension, maxContainerExtent);
if (notification is ScrollStartNotification &&
notification.metrics.extentBefore == 0.0 &&
_mode == null &&
_start(notification.metrics.axisDirection)) {
setState(() {
_mode = _RefreshIndicatorMode.drag;
});
return false;
}
bool indicatorAtTopNow;
switch (notification.metrics.axisDirection) {
case AxisDirection.down:
indicatorAtTopNow = true;
break;
case AxisDirection.up:
indicatorAtTopNow = false;
break;
case AxisDirection.left:
case AxisDirection.right:
indicatorAtTopNow = null;
break;
}
if (indicatorAtTopNow != _isIndicatorAtTop) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed)
_dismiss(_RefreshIndicatorMode.canceled);
} else if (notification is ScrollUpdateNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
if (notification.metrics.extentBefore > 0.0) {
_dismiss(_RefreshIndicatorMode.canceled);
} else {
_dragOffset -= notification.scrollDelta;
_checkDragOffset(maxContainerExtent);
}
}
if (_mode == _RefreshIndicatorMode.armed &&
notification.dragDetails == null) {
// On iOS start the refresh when the Scrollable bounces back from the
// overscroll (ScrollNotification indicating this don't have dragDetails
// because the scroll activity is not directly triggered by a drag).
_show();
}
} else if (notification is OverscrollNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
_dragOffset -= notification.overscroll / 2.0;
_checkDragOffset(maxContainerExtent);
}
} else if (notification is ScrollEndNotification) {
switch (_mode) {
case _RefreshIndicatorMode.armed:
_show();
break;
case _RefreshIndicatorMode.drag:
_dismiss(_RefreshIndicatorMode.canceled);
break;
default:
// do nothing
break;
}
}
return false;
}
bool _handleGlowNotification(OverscrollIndicatorNotification notification) {
if (notification.depth != 0 || !notification.leading) {
return false;
}
if (_mode == _RefreshIndicatorMode.drag) {
notification.disallowGlow();
return true;
}
return false;
}
bool _start(AxisDirection direction) {
assert(_mode == null);
assert(_isIndicatorAtTop == null);
assert(_dragOffset == null);
switch (direction) {
case AxisDirection.down:
_isIndicatorAtTop = true;
break;
case AxisDirection.up:
_isIndicatorAtTop = false;
break;
case AxisDirection.left:
case AxisDirection.right:
_isIndicatorAtTop = null;
// we do not support horizontal scroll views.
return false;
}
_dragOffset = 0.0;
_positionController.value = 0.0;
return true;
}
void _checkDragOffset(double containerExtent) {
assert(_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed);
double newValue =
_dragOffset / (containerExtent * _kDragContainerExtentPercentage);
if (_mode == _RefreshIndicatorMode.armed)
newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
_positionController.value =
newValue.clamp(0.0, 1.0) as double; // this triggers various rebuilds
if (_mode == _RefreshIndicatorMode.drag && _valueColor.value.alpha == 0xFF)
_mode = _RefreshIndicatorMode.armed;
}
// Stop showing the refresh indicator.
Future<void> _dismiss(_RefreshIndicatorMode newMode) async {
await Future<void>.value();
// This can only be called from _show() when refreshing and
// _handleScrollNotification in response to a ScrollEndNotification or
// direction change.
assert(newMode == _RefreshIndicatorMode.canceled ||
newMode == _RefreshIndicatorMode.done);
setState(() {
_mode = newMode;
});
switch (_mode) {
case _RefreshIndicatorMode.done:
break;
case _RefreshIndicatorMode.canceled:
await _positionController.animateTo(0.0,
duration: _kIndicatorScaleDuration);
break;
default:
assert(false);
}
if (mounted && _mode == newMode) {
_dragOffset = null;
_isIndicatorAtTop = null;
setState(() {
_mode = null;
});
}
}
void _show() {
assert(_mode != _RefreshIndicatorMode.refresh);
assert(_mode != _RefreshIndicatorMode.snap);
final Completer<void> completer = Completer<void>();
_pendingRefreshFuture = completer.future;
_mode = _RefreshIndicatorMode.snap;
_positionController
.animateTo(1.0 / _kDragSizeFactorLimit,
duration: _kIndicatorSnapDuration)
.then<void>((void value) {
if (mounted && _mode == _RefreshIndicatorMode.snap) {
assert(widget.onRefresh != null);
setState(() {
// Show the indeterminate progress indicator.
_mode = _RefreshIndicatorMode.refresh;
});
final Future<void> refreshResult = widget.onRefresh();
assert(() {
if (refreshResult == null)
FlutterError.reportError(FlutterErrorDetails(
exception: FlutterError('The onRefresh callback returned null.\n'
'The RefreshIndicator onRefresh callback must return a Future.'),
context: ErrorDescription('when calling onRefresh'),
library: 'material library',
));
return true;
}());
if (refreshResult == null) {
return;
}
refreshResult.whenComplete(() {
if (mounted && _mode == _RefreshIndicatorMode.refresh) {
completer.complete();
_dismiss(_RefreshIndicatorMode.done);
}
});
}
});
}
/// Show the refresh indicator and run the refresh callback as if it had
/// been started interactively. If this method is called while the refresh
/// callback is running, it quietly does nothing.
///
/// Creating the [NestedScrollViewRefreshIndicator] with a [GlobalKey<RefreshIndicatorState>]
/// makes it possible to refer to the [NestedScrollViewRefreshIndicatorState].
///
/// The future returned from this method completes when the
/// [NestedScrollViewRefreshIndicator.onRefresh] callback's future completes.
///
/// If you await the future returned by this function from a [State], you
/// should check that the state is still [mounted] before calling [setState].
///
/// When initiated in this manner, the refresh indicator is independent of any
/// actual scroll view. It defaults to showing the indicator at the top. To
/// show it at the bottom, set `atTop` to false.
Future<void> show({bool atTop = true}) {
if (_mode != _RefreshIndicatorMode.refresh &&
_mode != _RefreshIndicatorMode.snap) {
if (_mode == null) {
_start(atTop ? AxisDirection.down : AxisDirection.up);
}
_show();
}
return _pendingRefreshFuture;
}
final GlobalKey _key = GlobalKey();
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final Widget child = NotificationListener<ScrollNotification>(
key: _key,
onNotification: _handleScrollNotification,
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: _handleGlowNotification,
child: widget.child,
),
);
if (_mode == null) {
assert(_dragOffset == null);
assert(_isIndicatorAtTop == null);
return child;
}
assert(_dragOffset != null);
assert(_isIndicatorAtTop != null);
final bool showIndeterminateIndicator =
_mode == _RefreshIndicatorMode.refresh ||
_mode == _RefreshIndicatorMode.done;
return Column(
children: <Widget>[
Container(
child: SizeTransition(
axisAlignment: _isIndicatorAtTop ? 1.0 : -1.0,
sizeFactor: _positionFactor, // this is what brings it down
child: Container(
padding: _isIndicatorAtTop
? EdgeInsets.only(top: widget.displacement)
: EdgeInsets.only(bottom: widget.displacement),
alignment: _isIndicatorAtTop
? Alignment.topCenter
: Alignment.bottomCenter,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget child) {
return Container(
height: 55,
child: Lottie.asset("assets/smart_refresh_header.json",
repeat: true, reverse: false),
);
},
),
),
),
),
Expanded(
child: child,
)
],
);
// return Stack(
// children: <Widget>[
// child,
// Positioned(
// top: _isIndicatorAtTop ? 0.0 : null,
// bottom: !_isIndicatorAtTop ? 0.0 : null,
// left: 0.0,
// right: 0.0,
// child: SizeTransition(
// axisAlignment: _isIndicatorAtTop ? 1.0 : -1.0,
// sizeFactor: _positionFactor, // this is what brings it down
// child: Container(
// padding: _isIndicatorAtTop
// ? EdgeInsets.only(top: widget.displacement)
// : EdgeInsets.only(bottom: widget.displacement),
// alignment: _isIndicatorAtTop
// ? Alignment.topCenter
// : Alignment.bottomCenter,
// child: ScaleTransition(
// scale: _scaleFactor,
// child: AnimatedBuilder(
// animation: _positionController,
// builder: (BuildContext context, Widget child) {
// return RefreshProgressIndicator(
// semanticsLabel: widget.semanticsLabel ?? MaterialLocalizations.of(context).refreshIndicatorSemanticLabel,
// semanticsValue: widget.semanticsValue,
// value: showIndeterminateIndicator ? null : _value.value,
// valueColor: _valueColor,
// backgroundColor: widget.backgroundColor,
// );
// },
// ),
// ),
// ),
// ),
// ),
// ],
// );
}
}
//return true so that we can handle inner scroll notification
bool nestedScrollViewScrollNotificationPredicate(
ScrollNotification notification) {
return true;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment