public override void initState() { base.initState(); this._refreshController = new RefreshController(); this._isHaveTitle = false; this._titleHeight = 0.0f; this._controller = new AnimationController( duration: TimeSpan.FromMilliseconds(100), vsync: this ); RelativeRectTween rectTween = new RelativeRectTween( RelativeRect.fromLTRB(0, navBarHeight, 0, 0), RelativeRect.fromLTRB(0, 13, 0, 0) ); this._animation = rectTween.animate(this._controller); SchedulerBinding.instance.addPostFrameCallback(_ => { this.widget.actionModel.startFetchArticleDetail(); this.widget.actionModel.fetchArticleDetail(this.widget.viewModel.articleId); }); this._loginSubId = EventBus.subscribe(EventBusConstant.login_success, args => { this.widget.actionModel.startFetchArticleDetail(); this.widget.actionModel.fetchArticleDetail(this.widget.viewModel.articleId); }); this._jumpState = _ArticleJumpToCommentState.Inactive; this._cachedCommentPosition = null; }
Widget _buildNavigationBar(bool isShowRightWidget = true) { Widget titleWidget = new Container(); if (this._isHaveTitle) { titleWidget = new Text( this._article.title, style: CTextStyle.PXLargeMedium, maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center ); } Widget rightWidget = new Container(); if (isShowRightWidget) { string rightWidgetTitle = this._article.commentCount > 0 ? $"{this._article.commentCount}个评论" : "评论"; rightWidget = new Container( margin: EdgeInsets.only(8, right: 16), child: new CustomButton( padding: EdgeInsets.zero, onPressed: () => { //do not jump if we are already at the exact comment position if (this._refreshController.scrollController.position.pixels == this._cachedCommentPosition) { return; } //first frame: create a new scroll view in which the center of the viewport is the comment widget this.setState( () => { this._jumpState = _ArticleJumpToCommentState.active; }); SchedulerBinding.instance.addPostFrameCallback((TimeSpan value2) => { //calculate the comment position = curPixel(0) - minScrollExtent var commentPosition = -this._refreshController.scrollController.position .minScrollExtent; //cache the current comment position this._cachedCommentPosition = commentPosition; //second frame: create a new scroll view which starts from the default first widget //and then jump to the calculated comment position this.setState(() => { this._refreshController.scrollController.jumpTo(commentPosition); //assume that when we jump to the comment, the title should always be shown as the header //this assumption will fail when an article is shorter than 16 pixels in height (as referred to in _onNotification this._controller.forward(); this._isHaveTitle = true; }); }); }, child: new Container( width: 88, height: 28, alignment: Alignment.center, decoration: new BoxDecoration( border: Border.all(CColors.PrimaryBlue), borderRadius: BorderRadius.all(14) ), child: new Text( data: rightWidgetTitle, style: new TextStyle( fontSize: 14, fontFamily: "Roboto-Medium", color: CColors.PrimaryBlue ) ) ) ) ); } return(new CustomAppBar( () => this.widget.actionModel.mainRouterPop(), new Expanded( child: new Stack( fit: StackFit.expand, children: new List <Widget> { new PositionedTransition( rect: this._animation, child: titleWidget ) } ) ), rightWidget: rightWidget, this._isHaveTitle ? CColors.Separator2 : CColors.Transparent )); }
public override Widget build(BuildContext context) { this.widget.viewModel.articleDict.TryGetValue(this.widget.viewModel.articleId, out this._article); if (this.widget.viewModel.articleDetailLoading && (this._article == null || !this._article.isNotFirst)) { return(new Container( color: CColors.White, child: new CustomSafeArea( child: new Column( children: new List <Widget> { this._buildNavigationBar(false), new ArticleDetailLoading() } ) ) )); } if (this._article == null || this._article.channelId == null) { return(new Container()); } if (this._article.ownerType == "user") { if (this._article.userId != null && this.widget.viewModel.userDict.TryGetValue(this._article.userId, out this._user)) { this._user = this.widget.viewModel.userDict[this._article.userId]; } } if (this._article.ownerType == "team") { if (this._article.teamId != null && this.widget.viewModel.teamDict.TryGetValue(this._article.teamId, out this._team)) { this._team = this.widget.viewModel.teamDict[this._article.teamId]; } } if (this._titleHeight == 0f && this._article.title.isNotEmpty()) { this._titleHeight = CTextUtils.CalculateTextHeight( text: this._article.title, textStyle: CTextStyle.H3, MediaQuery.of(context).size.width - 16 * 2, // 16 is horizontal padding null ) + 16; // 16 is top padding this.setState(() => { }); } var commentIndex = 0; var originItems = this._article == null ? new List <Widget>() : this._buildItems(context, out commentIndex); commentIndex = this._jumpState == _ArticleJumpToCommentState.active ? commentIndex : 0; this._jumpState = _ArticleJumpToCommentState.Inactive; var child = new Container( color: CColors.Background, child: new Column( children: new List <Widget> { this._buildNavigationBar(), new Expanded( child: new CustomScrollbar( new CenteredRefresher( controller: this._refreshController, enablePullDown: false, enablePullUp: this._article.hasMore, onRefresh: this._onRefresh, onNotification: this._onNotification, children: originItems, centerIndex: commentIndex ) ) ), new ArticleTabBar(this._article.like, () => { if (!this.widget.viewModel.isLoggedIn) { this.widget.actionModel.pushToLogin(); } else { AnalyticsManager.ClickComment("Article", this._article.channelId, this._article.title); ActionSheetUtils.showModalActionSheet(new CustomInput( doneCallBack: text => { ActionSheetUtils.hiddenModalPopup(); this.widget.actionModel.sendComment(this._article.channelId, text, Snowflake.CreateNonce(), null ); }) ); } }, () => { if (!this.widget.viewModel.isLoggedIn) { this.widget.actionModel.pushToLogin(); } else { AnalyticsManager.ClickComment("Article", this._article.channelId, this._article.title); ActionSheetUtils.showModalActionSheet(new CustomInput( doneCallBack: text => { ActionSheetUtils.hiddenModalPopup(); this.widget.actionModel.sendComment(this._article.channelId, text, Snowflake.CreateNonce(), null ); }) ); } }, () => { if (!this.widget.viewModel.isLoggedIn) { this.widget.actionModel.pushToLogin(); } else { if (!this._article.like) { this.widget.actionModel.likeArticle(this._article.id); } } }, shareCallback: this.share ) } ) ); return(new Container( color: CColors.White, child: new CustomSafeArea( child: child ) )); }
Widget _buildNavigationBar(bool isShowRightWidget = true) { Widget titleWidget = new Container(); if (this._isHaveTitle) { titleWidget = new Text( this._article.title, style: CTextStyle.PXLargeMedium, maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center ); } Widget rightWidget = new Container(); if (isShowRightWidget) { string rightWidgetTitle = this._article.commentCount > 0 ? $"{this._article.commentCount} 评论" : "抢个沙发"; rightWidget = new Container( margin: EdgeInsets.only(8, right: 16), child: new CustomButton( padding: EdgeInsets.zero, onPressed: () => { //do not jump if we are already at the exact comment position if (this._refreshController.scrollController.position.pixels == this._cachedCommentPosition) { return; } //first frame: create a new scroll view in which the center of the viewport is the comment widget this.setState( () => { this._jumpState = _ArticleJumpToCommentState.active; }); SchedulerBinding.instance.addPostFrameCallback((TimeSpan value2) => { //calculate the comment position = curPixel(0) - minScrollExtent var commentPosition = -this._refreshController.scrollController.position .minScrollExtent; //cache the current comment position this._cachedCommentPosition = commentPosition; //second frame: rebuild a smartRefresher with the cached _cacheCommmentPosition this.setState(() => { this._needRebuildWithCachedCommentPosition = true; }); }); }, child: new Container( height: 28, padding: EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.center, decoration: new BoxDecoration( color: CColors.PrimaryBlue, borderRadius: BorderRadius.all(14) ), child: new Text( data: rightWidgetTitle, style: new TextStyle( fontSize: 14, fontFamily: "Roboto-Medium", color: CColors.White ) ) ) ) ); } return(new CustomAppBar( () => this.widget.actionModel.mainRouterPop(), new Expanded( child: new Stack( fit: StackFit.expand, children: new List <Widget> { new PositionedTransition( rect: this._animation, child: titleWidget ) } ) ), rightWidget: rightWidget, this._isHaveTitle ? CColors.Separator2 : CColors.Transparent )); }
public override Widget build(BuildContext context) { this.widget.viewModel.articleDict.TryGetValue(key: this.widget.viewModel.articleId, value: out this._article); if (this.widget.viewModel.articleDetailLoading && (this._article == null || !this._article.isNotFirst)) { return(new Container( color: CColors.White, child: new CustomSafeArea( child: new Column( children: new List <Widget> { this._buildNavigationBar(false), new ArticleDetailLoading() } ) ) )); } if (this._article == null || this._article.channelId == null) { return(new Container( color: CColors.White, child: new CustomSafeArea( child: new Column( children: new List <Widget> { this._buildNavigationBar(false), new Flexible( child: new BlankView("帖子不存在", "image/default-history") ) } ) ) ));; } if (this._article.ownerType == "user") { if (this._article.userId != null && this.widget.viewModel.userDict.TryGetValue(this._article.userId, out this._user)) { this._user = this.widget.viewModel.userDict[key : this._article.userId]; } } if (this._article.ownerType == "team") { if (this._article.teamId != null && this.widget.viewModel.teamDict.TryGetValue(this._article.teamId, out this._team)) { this._team = this.widget.viewModel.teamDict[key : this._article.teamId]; } } if (this._titleHeight == 0f && this._article.title.isNotEmpty()) { this._titleHeight = CTextUtils.CalculateTextHeight( text: this._article.title, textStyle: CTextStyle.H3, MediaQuery.of(context).size.width - 16 * 2, // 16 is horizontal padding null ) + 16; // 16 is top padding this.setState(() => { }); } var commentIndex = 0; var originItems = this._article == null ? new List <Widget>() : this._buildItems(context, out commentIndex); commentIndex = this._jumpState == _ArticleJumpToCommentState.active ? commentIndex : 0; this._jumpState = _ArticleJumpToCommentState.Inactive; Widget contentWidget; //happens at the next frame after user presses the "Comment" button //we rebuild a CenteredRefresher so that we can calculate out the comment section's position if (this._needRebuildWithCachedCommentPosition == false && commentIndex != 0) { contentWidget = new CenteredRefresher( controller: this._refreshController, enablePullDown: false, enablePullUp: this._article.hasMore, onRefresh: this._onRefresh, onNotification: this._onNotification, children: originItems, centerIndex: commentIndex ); } else { //happens when the page is updated or (when _needRebuildWithCachedCommentPosition is true) at the next frame after //a CenteredRefresher is created and the comment section's position is estimated //we use 0 or this estimated position to initiate the SmartRefresher's init scroll offset, respectively D.assert(!this._needRebuildWithCachedCommentPosition || this._cachedCommentPosition != null); contentWidget = new SmartRefresher( initialOffset: this._needRebuildWithCachedCommentPosition ? this._cachedCommentPosition.Value : 0f, controller: this._refreshController, enablePullDown: false, enablePullUp: this._article.hasMore, onRefresh: this._onRefresh, onNotification: this._onNotification, child: ListView.builder( physics: new AlwaysScrollableScrollPhysics(), itemCount: originItems.Count, itemBuilder: (cxt, index) => originItems[index] )); if (this._needRebuildWithCachedCommentPosition) { this._needRebuildWithCachedCommentPosition = false; //assume that when we jump to the comment, the title should always be shown as the header //this assumption will fail when an article is shorter than 16 pixels in height (as referred to in _onNotification this._controller.forward(); this._isHaveTitle = true; } } var child = new Container( color: CColors.Background, child: new Column( children: new List <Widget> { this._buildNavigationBar(), new Expanded( child: new CustomScrollbar( child: contentWidget ) ), this._buildArticleTabBar() } ) ); return(new Container( color: CColors.White, child: new CustomSafeArea( child: child ) )); }
public override Widget build(BuildContext context) { this.widget.viewModel.articleDict.TryGetValue(key: this.widget.viewModel.articleId, value: out this._article); if (this.widget.viewModel.articleDetailLoading && (this._article == null || !this._article.isNotFirst)) { return(new Container( color: CColors.White, child: new CustomSafeArea( child: new Column( children: new List <Widget> { this._buildNavigationBar(false), new ArticleDetailLoading() } ) ) )); } if (this._article == null || this._article.channelId == null) { return(new Container()); } if (this._article.ownerType == "user") { if (this._article.userId != null && this.widget.viewModel.userDict.TryGetValue(this._article.userId, out this._user)) { this._user = this.widget.viewModel.userDict[key : this._article.userId]; } } if (this._article.ownerType == "team") { if (this._article.teamId != null && this.widget.viewModel.teamDict.TryGetValue(this._article.teamId, out this._team)) { this._team = this.widget.viewModel.teamDict[key : this._article.teamId]; } } if (this._titleHeight == 0f && this._article.title.isNotEmpty()) { this._titleHeight = CTextUtils.CalculateTextHeight( text: this._article.title, textStyle: CTextStyle.H3, MediaQuery.of(context).size.width - 16 * 2, // 16 is horizontal padding null ) + 16; // 16 is top padding this.setState(() => { }); } var commentIndex = 0; var originItems = this._article == null ? new List <Widget>() : this._buildItems(context, out commentIndex); commentIndex = this._jumpState == _ArticleJumpToCommentState.active ? commentIndex : 0; this._jumpState = _ArticleJumpToCommentState.Inactive; var child = new Container( color: CColors.Background, child: new Column( children: new List <Widget> { this._buildNavigationBar(), new Expanded( child: new CustomScrollbar( new CenteredRefresher( controller: this._refreshController, enablePullDown: false, enablePullUp: this._article.hasMore, onRefresh: this._onRefresh, onNotification: this._onNotification, children: originItems, centerIndex: commentIndex ) ) ), this._buildArticleTabBar() } ) ); return(new Container( color: CColors.White, child: new CustomSafeArea( child: child ) )); }