I am setting up an animated profile page with NestedScrollView and slivers. But when I tap on the button to follow a store, it doesn't change until I scroll and I don't understand.
Here is the profile page :
Scaffold(
backgroundColor: primaryDark,
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverPersistentHeader(
delegate: SliverPersistentDelegate(
widget.user, state.products, follow, setState),
pinned: true,
),
// lets create a long list to make the content scrollable
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: SliverToBoxAdapter(
child: Column(
children: [
Container(
color: primaryDark,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
widget.user.username,
style: TextStyle(
fontSize: 16,
color: primaryWhite,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
Text(
widget.user.city.isEmpty
? widget.user.country
: (widget.user.city +
', ' +
widget.user.country),
style: TextStyle(
fontSize: 14,
color: primaryWhite,
),
),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0),
child: StatefulBuilder(
builder: (context, state) {
return rowStars(
// callback: (){},
numberOfFullStars: widget.user.userNote,
type: "sellerStars",
colors: [primaryGreen, primaryDark],
sizes: [12.5, 7.5],
//
);
}),
),
const SizedBox(height: 15),
Divider(
height: 0,
color: primaryGrey4,
),
const SizedBox(height: 5),
],
),
),
],
),
),
),
];
},
body: _buildContent(
context,
width,
state.user,
state.products,
state.showMessage,
)),
);
and the SliverPersistentDelegate
class SliverPersistentDelegate extends SliverPersistentHeaderDelegate {
final UserModel user;
final List<Product> products;
bool follow;
final double maxHeaderHeight = 120;
final double minHeaderHeight = kToolbarHeight + 20;
final double maxImageSize = 63;
final double minImageSize = 0;
final StateSetter stateSetter;
SliverPersistentDelegate(
this.user, this.products, this.follow, this.stateSetter);
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
final size = MediaQuery.of(context).size;
final percent = shrinkOffset / (maxHeaderHeight - 65);
final percent2 = shrinkOffset / (maxHeaderHeight);
final currentImageSize = (maxImageSize * (1 - percent)).clamp(
minImageSize,
maxImageSize,
);
final currentImagePosition = ((size.width / 2 - 65) * (1 - percent)).clamp(
minImageSize,
maxImageSize,
);
return Container(
color: primaryDark,
child: Container(
color: primaryGrey7.withOpacity(percent2 * 2 < 1 ? percent2 * 2 : 1),
child: Stack(
children: [
Positioned(
top: MediaQuery.of(context).viewPadding.top + 15,
left: currentImagePosition + 55,
child: (user.isOfficial)
? Row(
mainAxisAlignment: MainAxisAlignment.start,
//crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.username ?? "",
style: TextStyle(
color: primaryWhite.withOpacity(percent2),
fontSize: 20,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.normal,
),
),
SizedBox(width: 2.0),
currentImageSize <= 8 && user.isOfficial
? SvgPicture.asset(
"assets/svg/certification.svg",
)
: SizedBox.shrink()
],
)
: Text(
user?.username ?? "",
style: TextStyle(
color: primaryWhite.withOpacity(percent2),
fontSize: 24,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.normal,
),
),
),
Positioned(
left: 15,
top: currentImageSize == 0
? MediaQuery.of(context).viewPadding.top + 15
: MediaQuery.of(context).viewPadding.top + 33,
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(
Icons.arrow_back_sharp,
size: 30,
color: primaryWhite,
),
),
),
Positioned(
right: 50,
top: currentImageSize == 0
? MediaQuery.of(context).viewPadding.top + 15
: MediaQuery.of(context).viewPadding.top + 33,
child: (userConnected != null &&
userConnected.id != null &&
user.id != userConnected.id)
? GestureDetector(
onTap: () {
if (follow) {
// unfollow event
BlocProvider.of<ProfileVisitedBloc>(context).add(
ProfileVisitedEventUnfollowing(
user: user,
),
);
BlocProvider.of<FollowingBloc>(context).add(
FollowingDisplayStarted(
uid: userConnected.id,
),
);
stateSetter(
() {
follow = false;
},
);
} else {
// follow event
BlocProvider.of<ProfileVisitedBloc>(context).add(
ProfileVisitedEventFollowing(user: user),
);
BlocProvider.of<FollowingBloc>(context).add(
FollowingDisplayStarted(
uid: userConnected.id,
),
);
stateSetter(
() {
follow = true;
},
);
}
},
child: SvgPicture.asset(
(follow)
? "assets/svg/icon_follow.svg"
: "assets/svg/icon_unfollow.svg",
width: 30,
height: 30,
),
)
: SizedBox.shrink()),
Positioned(
right: 7,
top: currentImageSize == 0
? MediaQuery.of(context).viewPadding.top + 15
: MediaQuery.of(context).viewPadding.top + 33,
child: (userConnected != null &&
userConnected.id != null &&
user.id != userConnected.id)
? GestureDetector(
onTap: () {
displayCupertinoDialogReport(
user,
MediaQuery.of(context).size.width,
context,
products,
);
},
child: Icon(
Icons.more_vert,
size: 30,
color: primaryWhite,
),
)
: SizedBox.shrink()),
Positioned(
left: currentImagePosition + 87,
top: MediaQuery.of(context).viewPadding.top + 15,
bottom: 0,
child: Hero(
tag: 'profile',
child: Container(
width: currentImageSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: user.isOfficial
? SvgPicture.asset(
'assets/svg/tag_faces_official.svg',
)
: SvgPicture.asset(
'assets/svg/tag_faces.svg',
)),
),
),
],
),
),
);
}
@override
double get maxExtent => maxHeaderHeight;
@override
double get minExtent => minHeaderHeight;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
POST: i tried to include a StateStater in the parameters to update the value of the "follow" variable but nothing.
The full page :
class AnimatedUserVisitedProfile extends StatefulWidget {
final User user;
const AnimatedUserVisitedProfile({Key key, this.user}) : super(key: key);
@override
State<AnimatedUserVisitedProfile> createState() =>
_AnimatedUserVisitedProfileState();
}
class _AnimatedUserVisitedProfileState
extends State<AnimatedUserVisitedProfile> {
Completer<void> _refreshCompleter;
@override
void initState() {
super.initState();
_refreshCompleter = Completer<void>();
BlocProvider.of<ProfileVisitedBloc>(context).add(
ProfileVisitedEventClicked(
uid: widget.user.id,
),
);
}
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return BlocConsumer<ProfileVisitedBloc, ProfileVisitedState>(
listener: (context, state) {
if (state is ProfileVisitedStateLoaded) {
_refreshCompleter?.complete();
_refreshCompleter = Completer();
}
}, builder: (context, state) {
if (state is ProfileVisitedStateLoaded) {
if (state.showMessage != null && state.showMessage) {
Timer.periodic(Duration(seconds: 1), (timer) {
if (this.mounted) {
BlocProvider.of<ProfileVisitedBloc>(context).add(
ProfileVisitedMessageDisappeared(),
);
Navigator.pop(context);
}
timer.cancel();
});
}
bool follow = (userConnected == null)
? false
: (widget.user != null &&
widget.user.followers.contains(userConnected.id))
? true
: false;
return Scaffold(
backgroundColor: primaryDark,
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverPersistentHeader(
delegate: SliverPersistentDelegate(
widget.user, state.products, follow, setState),
pinned: true,
),
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: SliverToBoxAdapter(
child: Column(
children: [
Container(
color: primaryDark,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
widget.user.username,
style: TextStyle(
fontSize: 16,
color: primaryWhite,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
Text(
widget.user.city.isEmpty
? widget.user.country
: (widget.user.city +
', ' +
widget.user.country),
style: TextStyle(
fontSize: 14,
color: primaryWhite,
),
),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0),
child: StatefulBuilder(
builder: (context, state) {
return rowStars(
// callback: (){},
numberOfFullStars: widget.user.userNote,
type: "sellerStars",
colors: [primaryGreen, primaryDark],
sizes: [12.5, 7.5],
//
);
}),
),
const SizedBox(height: 15),
Divider(
height: 0,
color: primaryGrey4,
),
const SizedBox(height: 5),
],
),
),
],
),
),
),
];
},
body: _buildContent(
context,
width,
state.user,
state.products,
state.showMessage,
)),
);
}
return Center(
child: Container(
color: Colors.black,
child: loading(),
),
);
});
}
Widget _buildContent(
BuildContext context,
double width,
User user,
List<Product> products,
bool showMessage,
) {//return a ListView of products
}
class SliverPersistentDelegate extends SliverPersistentHeaderDelegate {
final UserModel user;
final List<Product> products;
bool follow;
final double maxHeaderHeight = 120;
final double minHeaderHeight = kToolbarHeight + 20;
final double maxImageSize = 63;
final double minImageSize = 0;
final StateSetter stateSetter;
SliverPersistentDelegate(
this.user, this.products, this.follow, this.stateSetter);
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
final size = MediaQuery.of(context).size;
final percent = shrinkOffset / (maxHeaderHeight - 65);
final percent2 = shrinkOffset / (maxHeaderHeight);
final currentImageSize = (maxImageSize * (1 - percent)).clamp(
minImageSize,
maxImageSize,
);
final currentImagePosition = ((size.width / 2 - 65) * (1 - percent)).clamp(
minImageSize,
maxImageSize,
);
return Container(
color: primaryDark,
child: Container(
color: primaryGrey7.withOpacity(percent2 * 2 < 1 ? percent2 * 2 : 1),
child: Stack(
children: [
Positioned(
top: MediaQuery.of(context).viewPadding.top + 15,
left: currentImagePosition + 55,
child: (user.isOfficial)
? Row(
mainAxisAlignment: MainAxisAlignment.start,
//crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.username ?? "",
style: TextStyle(
color: primaryWhite.withOpacity(percent2),
fontSize: 20,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.normal,
),
),
SizedBox(width: 2.0),
currentImageSize <= 8 && user.isOfficial
? SvgPicture.asset(
"assets/svg/certification.svg",
)
: SizedBox.shrink()
],
)
: Text(
user?.username ?? "",
style: TextStyle(
color: primaryWhite.withOpacity(percent2),
fontSize: 24,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.normal,
),
),
),
Positioned(
left: 15,
top: currentImageSize == 0
? MediaQuery.of(context).viewPadding.top + 15
: MediaQuery.of(context).viewPadding.top + 33,
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(
Icons.arrow_back_sharp,
size: 30,
color: primaryWhite,
),
),
),
Positioned(
right: 50,
top: currentImageSize == 0
? MediaQuery.of(context).viewPadding.top + 15
: MediaQuery.of(context).viewPadding.top + 33,
child: (userConnected != null &&
userConnected.id != null &&
user.id != userConnected.id)
? GestureDetector(
onTap: () {
if (follow) {
// unfollow event
BlocProvider.of<ProfileVisitedBloc>(context).add(
ProfileVisitedEventUnfollowing(
user: user,
),
);
BlocProvider.of<FollowingBloc>(context).add(
FollowingDisplayStarted(
uid: userConnected.id,
),
);
stateSetter(
() {
follow = false;
},
);
} else {
// follow event
BlocProvider.of<ProfileVisitedBloc>(context).add(
ProfileVisitedEventFollowing(user: user),
);
BlocProvider.of<FollowingBloc>(context).add(
FollowingDisplayStarted(
uid: userConnected.id,
),
);
stateSetter(
() {
follow = true;
},
);
}
},
child: SvgPicture.asset(
(follow)
? "assets/svg/icon_follow.svg"
: "assets/svg/icon_unfollow.svg",
width: 30,
height: 30,
),
)
: SizedBox.shrink()),
Positioned(
right: 7,
top: currentImageSize == 0
? MediaQuery.of(context).viewPadding.top + 15
: MediaQuery.of(context).viewPadding.top + 33,
child: (userConnected != null &&
userConnected.id != null &&
user.id != userConnected.id)
? GestureDetector(
onTap: () {
displayCupertinoDialogReport(
user,
MediaQuery.of(context).size.width,
context,
products,
);
},
child: Icon(
Icons.more_vert,
size: 30,
color: primaryWhite,
),
)
: SizedBox.shrink()),
Positioned(
left: currentImagePosition + 87,
top: MediaQuery.of(context).viewPadding.top + 15,
bottom: 0,
child: Hero(
tag: 'profile',
child: Container(
width: currentImageSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: user.isOfficial
? SvgPicture.asset(
'assets/svg/tag_faces_official.svg',
)
: SvgPicture.asset(
'assets/svg/tag_faces.svg',
)),
),
),
],
),
),
);
}
@override
double get maxExtent => maxHeaderHeight;
@override
double get minExtent => minHeaderHeight;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}