Skip to content

Commit

Permalink
Added sliver persistent header
Browse files Browse the repository at this point in the history
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
LarsRefsgaard committed Jul 24, 2023
1 parent adfc16c commit 393e59f
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 36 deletions.
1 change: 1 addition & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:ui' as ui;
import 'dart:io';

import 'package:flutter/material.dart' hide TimeOfDay;
import 'package:flutter/src/rendering/sliver_persistent_header.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
Expand Down
151 changes: 118 additions & 33 deletions lib/ui/cards/scoreboard_card.dart
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();
}
5 changes: 5 additions & 0 deletions lib/ui/carp_study_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ TextStyle scoreNumberStyle = const TextStyle(
fontWeight: FontWeight.w800,
color: Color.fromRGBO(32, 111, 162, 1));

TextStyle scoreNumberStyleSmall = const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
color: Color.fromRGBO(32, 111, 162, 1));

TextStyle scoreTextStyle = const TextStyle(
fontSize: 12, fontWeight: FontWeight.w700, color: Color(0xff77A8C8));

Expand Down
5 changes: 2 additions & 3 deletions lib/ui/pages/task_list_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,10 @@ class TaskListPageState extends State<TaskListPage> {
} else {
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: ScoreboardCardWidget(widget.model)),
ScoreboardCard(widget.model),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(15),
padding: const EdgeInsets.all(16),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
Expand Down

0 comments on commit 393e59f

Please sign in to comment.