-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enhanced the scoreboard functionality with the addition of a SliverPersistentHeader. Changed ScoreboardCardWidget to ScoreboardCard. More specific, it retracts when scrolling using a header delegate. Also, updated the UI by making values smaller when scrolling and added a vertical divider. Included additional styles for smaller score numbers and adjusted some element paddings.
- Loading branch information
1 parent
adfc16c
commit 393e59f
Showing
4 changed files
with
126 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,148 @@ | ||
part of carp_study_app; | ||
|
||
class ScoreboardCardWidget extends StatefulWidget { | ||
class ScoreboardCard extends StatefulWidget { | ||
final TaskListPageViewModel model; | ||
const ScoreboardCardWidget(this.model, {super.key}); | ||
const ScoreboardCard(this.model, {super.key}); | ||
@override | ||
ScoreboardCardWidgetState createState() => ScoreboardCardWidgetState(); | ||
ScoreboardCardState createState() => ScoreboardCardState(); | ||
} | ||
|
||
class ScoreboardCardWidgetState extends State<ScoreboardCardWidget> { | ||
class ScoreboardCardState extends State<ScoreboardCard> { | ||
@override | ||
Widget build(BuildContext context) { | ||
RPLocalizations locale = RPLocalizations.of(context)!; | ||
|
||
return SliverPersistentHeader( | ||
pinned: true, | ||
delegate: ScoreboardPersistentHeaderDelegate( | ||
model: widget.model, | ||
locale: locale, | ||
minExtent: 30, | ||
maxExtent: 110, | ||
), | ||
); | ||
} | ||
} | ||
|
||
/// Make a [SliverPersistentHeaderDelegate] to use in a [SliverPersistentHeader] widget, that can be used in a [CustomScrollView]. | ||
/// This is used in the [StudyPage] to make the header of the page. | ||
/// The delegate should retract from 110px to 60px when scrolling down. | ||
/// The animation should be simple and linear. A stretched header does not do anything. | ||
class ScoreboardPersistentHeaderDelegate | ||
extends SliverPersistentHeaderDelegate { | ||
TaskListPageViewModel model; | ||
RPLocalizations locale; | ||
@override | ||
final double minExtent; | ||
@override | ||
final double maxExtent; | ||
|
||
ScoreboardPersistentHeaderDelegate({ | ||
required this.minExtent, | ||
required this.maxExtent, | ||
required this.model, | ||
required this.locale, | ||
}); | ||
|
||
/// The header should should be 110px when not scrolling, and 60px when scrolling. | ||
/// The numbers should become smaller when scrolling down, by using the scoreNumberStyleSmall instead of scoreNumberStyle. | ||
@override | ||
Widget build( | ||
BuildContext context, double shrinkOffset, bool overlapsContent) { | ||
double height = 110; | ||
|
||
return Container( | ||
color: Theme.of(context).colorScheme.secondary, | ||
height: 110, | ||
height: height, | ||
child: StreamBuilder<UserTask>( | ||
stream: widget.model.userTaskEvents, | ||
stream: model.userTaskEvents, | ||
builder: (context, snapshot) { | ||
return Column( | ||
return Row( | ||
crossAxisAlignment: CrossAxisAlignment.center, | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||
children: [ | ||
Row( | ||
crossAxisAlignment: CrossAxisAlignment.center, | ||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||
children: [ | ||
Column( | ||
children: [ | ||
Text(widget.model.daysInStudy.toString(), | ||
style: scoreNumberStyle.copyWith( | ||
color: Theme.of(context).primaryColor)), | ||
Expanded( | ||
child: Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
Text(model.daysInStudy.toString(), | ||
style: scoreNumberStyle.copyWith( | ||
fontSize: calculateSize( | ||
shrinkOffset, | ||
scoreNumberStyleSmall.fontSize!, | ||
scoreNumberStyle.fontSize!), | ||
color: Theme.of(context).primaryColor)), | ||
if (shrinkOffset < 60) | ||
Text(locale.translate('cards.scoreboard.days'), | ||
style: scoreTextStyle.copyWith( | ||
color: Theme.of(context).primaryColor)), | ||
], | ||
], | ||
), | ||
), | ||
// a vertical divider line with rounded corners that spans from 10% to 90% of the height | ||
Expanded( | ||
flex: 0, | ||
child: Container( | ||
height: calculateSize( | ||
shrinkOffset, minExtent * 0.6, maxExtent * 0.6), | ||
width: 2, | ||
decoration: BoxDecoration( | ||
color: Theme.of(context).dividerColor, | ||
borderRadius: BorderRadius.circular(100), | ||
), | ||
SizedBox( | ||
height: 66, | ||
child: VerticalDivider( | ||
color: Theme.of(context).primaryColor, | ||
width: 15, | ||
)), | ||
Column( | ||
children: [ | ||
Text(widget.model.taskCompleted.toString(), | ||
style: scoreNumberStyle.copyWith( | ||
color: Theme.of(context).primaryColor)), | ||
), | ||
), | ||
Expanded( | ||
flex: 1, | ||
child: Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
Text(model.taskCompleted.toString(), | ||
style: scoreNumberStyle.copyWith( | ||
fontSize: calculateSize( | ||
shrinkOffset, | ||
scoreNumberStyleSmall.fontSize!, | ||
scoreNumberStyle.fontSize!), | ||
color: Theme.of(context).primaryColor)), | ||
if (shrinkOffset < 60) | ||
Text(locale.translate('cards.scoreboard.tasks'), | ||
style: scoreTextStyle.copyWith( | ||
color: Theme.of(context).primaryColor)), | ||
], | ||
) | ||
], | ||
), | ||
], | ||
), | ||
) | ||
], | ||
); | ||
}, | ||
), | ||
); | ||
} | ||
|
||
// A simple function that returns the font size from the scoreNumberStyle, but increasingly smaller when scrolling down. | ||
double calculateSize(double shrinkOffset, double minSize, double maxSize) { | ||
// Calculate the normalized shrinkOffset value in the range [0, 1] | ||
double normalizedShrinkOffset = shrinkOffset / maxExtent; | ||
|
||
// Calculate the font size using linear interpolation | ||
double size = maxSize - normalizedShrinkOffset * (maxSize - minSize); | ||
|
||
// Return the calculated font size | ||
return size; | ||
} | ||
|
||
@override | ||
bool shouldRebuild(ScoreboardPersistentHeaderDelegate oldDelegate) { | ||
return false; | ||
} | ||
|
||
@override | ||
FloatingHeaderSnapConfiguration get snapConfiguration => | ||
FloatingHeaderSnapConfiguration( | ||
curve: Curves.linear, | ||
duration: const Duration(milliseconds: 100), | ||
); | ||
|
||
@override | ||
OverScrollHeaderStretchConfiguration get stretchConfiguration => | ||
OverScrollHeaderStretchConfiguration(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters