Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adaptive Trials #647

Draft
wants to merge 40 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
23f5fed
reordable list view working on intervention page
malte-j Jan 22, 2024
76ddd90
docs: update UML documentation
Jan 22, 2024
b5cb59c
view working
malte-j Jan 22, 2024
c301888
thompson sampling algo impl
malte-j Feb 17, 2024
0931581
Merge branch 'mp-ws-23' of github.com:malte-j/studyu into mp-ws-23
malte-j Feb 17, 2024
5a32244
docs: update UML documentation
Feb 17, 2024
ea8bb3f
mp23 study schedule, designer backend save
konstantin-spiess Mar 20, 2024
393072e
docs: update UML documentation
Mar 20, 2024
dfca421
update intro screen with correct study duration
malte-j Mar 21, 2024
ee4b641
docs: update UML documentation
Mar 21, 2024
76f760c
add all segments
konstantin-spiess Mar 23, 2024
ae9138e
docs: update UML documentation
Mar 23, 2024
7c1b817
add table overview
malte-j Mar 23, 2024
91bbaa3
Merge branch 'mp-ws-23' of github.com:malte-j/studyu into mp-ws-23
malte-j Mar 23, 2024
d043389
docs: update UML documentation
Mar 23, 2024
3ec551f
add observations to study schedule
konstantin-spiess Mar 23, 2024
889025b
Merge branch 'mp-ws-23' of github.com:malte-j/studyu into mp-ws-23
konstantin-spiess Mar 23, 2024
61d8b61
docs: update UML documentation
Mar 23, 2024
2f8826e
add deciding metric to thompson sampling
konstantin-spiess Mar 23, 2024
450f7e6
Merge branch 'mp-ws-23' of github.com:malte-j/studyu into mp-ws-23
konstantin-spiess Mar 23, 2024
9af1ee2
work on fixing duration of stuf
malte-j Mar 23, 2024
2682251
Merge branch 'mp-ws-23' of github.com:malte-j/studyu into mp-ws-23
malte-j Mar 23, 2024
246d960
dates and questions working
malte-j Mar 23, 2024
6f6206f
fix surveys
malte-j Mar 23, 2024
e455d97
docs: update UML documentation
Mar 23, 2024
82479fd
thompson sampling working
malte-j Mar 25, 2024
daf564b
working backtracking
malte-j Mar 25, 2024
3f32cc8
docs: update UML documentation
Mar 25, 2024
3112936
Merge branch 'dev' into feat/adaptive-trials
johannesvedder Jun 28, 2024
f6b664a
chore: fix merge errors
johannesvedder Jun 28, 2024
6101c97
fix: adapt database schema to new schedule
johannesvedder Jun 28, 2024
5e35f7e
refactor: mp23 schedule no longer depends on study interventions and …
johannesvedder Jun 28, 2024
b7b7b4f
fix: adapt auto-save to work partly with mp23_schedule
johannesvedder Jun 28, 2024
18cee04
chore: format and refactor
johannesvedder Jun 28, 2024
e5c159a
chore: remove dev files
johannesvedder Jun 28, 2024
4f6d015
chore: refactor
johannesvedder Jun 28, 2024
6fa4f7f
fix: schedule enum json
johannesvedder Jun 28, 2024
7d44112
Merge branch 'refs/heads/dev' into feat/adaptive-trials
johannesvedder Jul 4, 2024
39aba90
chore: remove study_schedule_form_view.dart since it is currently uns…
johannesvedder Jul 4, 2024
0e0290b
chore: merge cleanup
johannesvedder Jul 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:studyu_app/routes.dart';
import 'package:studyu_app/screens/study/dashboard/task_overview_tab/progress_row.dart';
import 'package:studyu_app/screens/study/dashboard/task_overview_tab/task_box.dart';
import 'package:studyu_app/theme.dart';
import 'package:studyu_app/screens/study/onboarding/calendar_overview.dart';
import 'package:studyu_app/widgets/intervention_card.dart';
import 'package:studyu_core/core.dart';

Expand Down Expand Up @@ -75,54 +74,60 @@ class _TaskOverviewState extends State<TaskOverview> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 8),
ProgressRow(subject: widget.subject),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: Text(
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 8),
// ProgressRow(subject: widget.subject),

Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center(child: CalendarOverview(subject: widget.subject)),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Row(
children: [
Text(
AppLocalizations.of(context)!.intervention_current,
style: theme.textTheme.titleLarge,
),
),
const Spacer(),
Text(
'${widget.subject!.daysLeftForPhase(widget.subject!.getInterventionIndexForDate(DateTime.now()))} ${AppLocalizations.of(context)!.days_left}',
style: const TextStyle(color: primaryColor),
),
],
),
const SizedBox(height: 8),
InterventionCardTitle(
intervention:
widget.subject!.getInterventionForDate(DateTime.now()),
),
const SizedBox(height: 8),
Text(
AppLocalizations.of(context)!.today_tasks,
style: theme.textTheme.titleLarge,
),
],
),
),
// Todo: find good way to calculate duration of intervention and display it
Expanded(
child: ListView(
children: [
...buildScheduleToday(context),
],
// const Spacer(),
// Text(
// '${widget.subject!.daysLeftForPhase(widget.subject!.getInterventionIndexForDate(DateTime.now()))} ${AppLocalizations.of(context)!.days_left}',
// style: const TextStyle(color: primaryColor),
// )
],
),
const SizedBox(height: 8),
InterventionCardTitle(
intervention:
widget.subject!.getInterventionForDate(DateTime.now()),
),
const SizedBox(height: 8),
Text(
AppLocalizations.of(context)!.today_tasks,
style: theme.textTheme.titleLarge,
),
],
),
),
),
],
// Todo: find good way to calculate duration of intervention and display it
...buildScheduleToday(context),
// Expanded(
// child: ListView(
// children: [
// ...buildScheduleToday(context),
// ],
// ),
// ),
],
),
);
}
}
251 changes: 251 additions & 0 deletions app/lib/screens/study/onboarding/calendar_overview.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:studyu_core/core.dart';
import 'package:table_calendar/table_calendar.dart';

class CalendarOverview extends StatefulWidget {
final StudySubject? subject;

const CalendarOverview({required this.subject, super.key});

@override
_CalendarOverviewState createState() => _CalendarOverviewState();
}

class _CalendarOverviewState extends State<CalendarOverview> {
DateTime _focusedDay = DateTime.now();

// init
@override
void initState() {
super.initState();
}

int _dayOfYear(DateTime date) {
return normalizeDate(date).difference(DateTime.utc(date.year)).inDays + 1;
}

@override
Widget build(BuildContext context) {
final kToday = DateTime.now();
final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
final List<List<Color>> colorScheme = [
[
const Color.fromRGBO(15, 174, 40, 1),
const Color.fromRGBO(176, 255, 189, 1),
],
[
const Color.fromRGBO(92, 54, 173, 1),
const Color.fromRGBO(211, 191, 255, 1),
],
[
const Color.fromRGBO(173, 73, 208, 1),
const Color.fromRGBO(237, 186, 255, 1),
],
[
const Color.fromRGBO(222, 183, 45, 1),
const Color.fromRGBO(255, 239, 181, 1),
]
];

final schedule = widget.subject?.study.mp23Schedule;
if (schedule == null) {
throw Exception('Something went wrong, we need a schedule here');
}

final interventions = widget.subject!.study.interventions;
final segments = widget.subject!.study.mp23Schedule.segments;

// function for building
// BuildContext, DateTime, DateTime
Widget buildCalendarDay(
BuildContext context,
DateTime day,
DateTime focusedDay, [
bool today = false,
]) {
final text = DateFormat.d().format(day);

DateTime studyStartDay = widget.subject!.startedAt ?? DateTime.now();

studyStartDay = studyStartDay.add(const Duration(days: 1));

final nthDay = _dayOfYear(day) - _dayOfYear(studyStartDay);

StudyScheduleSegment? segment;

try {
segment = widget.subject?.study.getSegmentForDay(nthDay).$1;
} catch (e) {
print(e);
}

List<Color> colors = [
const Color.fromARGB(0, 0, 0, 0),
if (today) Colors.blue[600]! else Colors.grey[400]!,
];

// gradient
Gradient? gradient;

if (segment is BaselineScheduleSegment) {
colors = [const Color.fromARGB(255, 228, 228, 228), Colors.black];
} else if (segment is ThompsonSamplingScheduleSegment) {
// TODO_NOW if day is in past, show the actual intervention
if (isSameDay(day, DateTime.now()) ||
isBeforeDay(day, DateTime.now())) {
print(
"isSameDay($day, ${DateTime.now()}: ${isSameDay(day, DateTime.now())}",
);
print(
"isBeforeDay($day, ${DateTime.now()}: ${isBeforeDay(day, DateTime.now())}",
);
final intervention = widget.subject!.getInterventionForDate(day)!;
// final intervention = schedule.getInterventionForDay(nthDay, [])!;
if (widget.subject != null) {
final index =
widget.subject!.study.interventions.indexOf(intervention);
colors = colorScheme[index % colorScheme.length];
}
} else {
colors = [Colors.white, Colors.white];
final List<Color> gradientColors = [];

for (int i = 0; i < widget.subject!.study.interventions.length; i++) {
gradientColors.add(colorScheme[i % colorScheme.length][0]);
}

gradient = LinearGradient(
colors: gradientColors,
transform: const GradientRotation(1),
);
}
} else if (segment is AlternatingScheduleSegment) {
colors = colorScheme[0];
final interventionOnDay =
widget.subject?.study.getInterventionForDay(nthDay, []);

if (interventionOnDay != null) {
final index =
widget.subject!.study.interventions.indexOf(interventionOnDay);
colors = colorScheme[index % colorScheme.length];
}
}

final decoration = gradient != null
? BoxDecoration(
gradient: gradient,
shape: today ? BoxShape.circle : BoxShape.rectangle,
)
: BoxDecoration(
color: colors[0],
shape: today ? BoxShape.circle : BoxShape.rectangle,
);

return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
decoration: decoration,
child: Padding(
padding: EdgeInsets.zero,
child: Center(
child: Text(
text,
style: TextStyle(color: colors[1]),
),
),
),
),
),
);
}

// init
return Container(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
children: [
TableCalendar(
firstDay: kFirstDay,
lastDay: kLastDay,
focusedDay: _focusedDay,
headerStyle: const HeaderStyle(titleCentered: true),
availableCalendarFormats: const {
CalendarFormat.month: 'Month',
},
calendarBuilders: CalendarBuilders(
selectedBuilder: buildCalendarDay,
todayBuilder: (context, day, focusedDay) =>
buildCalendarDay(context, day, focusedDay, true),
defaultBuilder: buildCalendarDay,
outsideBuilder: (context, day, focusedDay) {
final text = DateFormat.d().format(day);

return Center(
child: Text(
text,
style: const TextStyle(color: Color.fromARGB(0, 0, 0, 0)),
),
);
},
),
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
),
if (segments.any((s) => s is BaselineScheduleSegment))
const Label(
color: Color.fromARGB(255, 228, 228, 228),
text: "Baseline",
),
for (int i = 0; i < interventions.length; i++)
Label(
color: colorScheme[i % colorScheme.length][0],
text: interventions[i].name ?? '',
),
],
),
);
}
}

// class for rendering labels

class Label extends StatelessWidget {
final String text;
final Color color;
final Color? borderColor;

const Label({
required this.text,
required this.color,
this.borderColor,
super.key,
});

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
border:
borderColor != null ? Border.all(color: borderColor!) : null,
),
),
const SizedBox(width: 8),
Text(text),
],
),
);
}
}
4 changes: 3 additions & 1 deletion app/lib/screens/study/onboarding/journey_overview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
import 'package:provider/provider.dart';
import 'package:studyu_app/models/app_state.dart';
import 'package:studyu_app/routes.dart';
import 'package:studyu_app/screens/study/onboarding/calendar_overview.dart';
import 'package:studyu_app/screens/study/onboarding/onboarding_progress.dart';
import 'package:studyu_app/widgets/bottom_onboarding_navigation.dart';
import 'package:studyu_core/core.dart';
Expand Down Expand Up @@ -61,7 +62,8 @@ class _JourneyOverviewScreen extends State<JourneyOverviewScreen> {
child: Column(
children: [
//StudyTile.fromUserStudy(study: study),
Timeline(subject: subject),
// Timeline(subject: subject),
CalendarOverview(subject: subject),
],
),
),
Expand Down
6 changes: 6 additions & 0 deletions app/lib/util/datetime.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
bool isBeforeDay(DateTime a, DateTime b) {
final numA = "${a.year}${a.month}${a.day}";
final numB = "${b.year}${b.month}${b.day}";

return int.parse(numA) < int.parse(numB);
}
Loading
Loading