Skip to content

Commit

Permalink
Merge branch 'feature/templatestudies' into feature/filter
Browse files Browse the repository at this point in the history
  • Loading branch information
johannesvedder authored Oct 2, 2024
2 parents 2bbb13e + bc8189e commit 5202be4
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 133 deletions.
7 changes: 7 additions & 0 deletions app/lib/screens/study/onboarding/kickoff.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ class _KickoffScreen extends State<KickoffScreen> {
(_) => false,
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.error),
duration: const Duration(seconds: 30),
),
);
StudyULogger.fatal('Failed creating subject: $e');
Navigator.pushNamedAndRemoveUntil(context, Routes.welcome, (_) => false);
}
}

Expand Down
37 changes: 37 additions & 0 deletions database/studyu-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,43 @@ CREATE POLICY "Joining a closed study should not be possible" ON public.study_su
AND study.status = 'closed'::public.study_status
));

--
-- Name: Joining a substudy with a closed parent template should not be possible; Type: POLICY; Schema: public; Owner: postgres
--
CREATE POLICY "Joining a substudy with a closed parent template should not be possible"
ON public.study_subject
AS RESTRICTIVE
FOR INSERT
WITH CHECK (
NOT EXISTS (
SELECT 1
FROM public.study parent
WHERE parent.id = (
SELECT study.parent_template_id
FROM public.study
WHERE study.id = study_subject.study_id
)
AND parent.status = 'closed'::public.study_status
)
);

--
-- Name: Creating substudies is not possible for closed templates; Type: POLICY; Schema: public; Owner: postgres
--
CREATE POLICY "Creating substudies is not possible for closed templates"
ON public.study
AS RESTRICTIVE
FOR INSERT
WITH CHECK (
parent_template_id IS NULL
OR NOT EXISTS (
SELECT 1
FROM public.study parent
WHERE parent.id = study.parent_template_id
AND parent.status = 'closed'::public.study_status
)
);

--
-- Name: app_config; Type: ROW SECURITY; Schema: public; Owner: postgres
--
Expand Down
4 changes: 0 additions & 4 deletions designer_v2/lib/constants.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'package:flutter_portal/flutter_portal.dart';

class Config {
static const isDebugMode = false;

Expand All @@ -26,8 +24,6 @@ class Config {
static const participantInactiveDuration = 3;
}

const outPortalLabel = PortalLabel("out");

const kPathSeparator = ' / ';

const String rootRouteName = 'root';
Expand Down
246 changes: 139 additions & 107 deletions designer_v2/lib/features/dashboard/dashboard_page.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_designer_v2/common_views/async_value_widget.dart';
import 'package:studyu_designer_v2/common_views/empty_body.dart';
import 'package:studyu_designer_v2/common_views/primary_button.dart';
import 'package:studyu_designer_v2/common_views/search.dart';
import 'package:studyu_designer_v2/constants.dart';
import 'package:studyu_designer_v2/features/dashboard/dashboard_controller.dart';
import 'package:studyu_designer_v2/features/dashboard/dashboard_scaffold.dart';
import 'package:studyu_designer_v2/features/dashboard/dashboard_state.dart';
Expand Down Expand Up @@ -42,6 +40,48 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
}
}

final GlobalKey _createMenuButtonKey = GlobalKey();

void _showDropdownMenu(BuildContext context,
{required DashboardController controller}) {
final RenderBox button =
_createMenuButtonKey.currentContext!.findRenderObject()! as RenderBox;
final RenderBox overlay =
Overlay.of(context).context.findRenderObject()! as RenderBox;

final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(
button.size.bottomLeft(Offset(0, 10)),
ancestor: overlay,
),
button.localToGlobal(
button.size.bottomRight(Offset(0, 10)),
ancestor: overlay,
),
),
Offset.zero & overlay.size,
);

showMenu(
context: context,
position: position,
items: [
_buildCreatePopupMenuItem(
title: tr.action_button_standalone_study_title,
subtitle: tr.action_button_standalone_study_subtitle,
onTap: () => controller.onClickNewStudy(false),
),
_buildCreatePopupMenuItem(
title: tr.action_button_template_title,
subtitle: tr.action_button_template_subtitle,
hint: tr.action_button_template_hint,
onTap: () => controller.onClickNewStudy(true),
),
],
);
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Expand All @@ -54,64 +94,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
children: <Widget>[
Row(
children: [
PortalTarget(
visible: state.createNewMenuOpen,
portalCandidateLabels: const [outPortalLabel],
portalFollower: GestureDetector(
onTap: () => controller.setCreateNewMenuOpen(false),
child: Container(color: Colors.transparent),
),
child: const SizedBox.shrink(),
),
PortalTarget(
visible: state.createNewMenuOpen,
anchor: const Aligned(
follower: Alignment.topLeft,
target: Alignment.bottomLeft,
),
portalFollower: GestureDetector(
onTap: () => controller.setCreateNewMenuOpen(false),
child: Container(
width: 600,
margin: const EdgeInsets.only(top: 10.0),
child: Material(
color: theme.colorScheme.onPrimary,
borderRadius: BorderRadius.circular(16),
elevation: 20.0,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildCreateNewDropdownItem(
title: tr.action_button_standalone_study_title,
subtitle:
tr.action_button_standalone_study_subtitle,
onTap: () => controller.onClickNewStudy(false),
),
const Divider(
height: 0,
),
_buildCreateNewDropdownItem(
title: tr.action_button_template_title,
subtitle: tr.action_button_template_subtitle,
hint: tr.action_button_template_hint,
onTap: () => controller.onClickNewStudy(true),
),
],
),
),
),
),
),
child: SizedBox(
height: 36.0,
child: PrimaryButton(
text: tr.action_button_create,
onPressed: () => controller
.setCreateNewMenuOpen(!state.createNewMenuOpen),
),
SizedBox(
key: _createMenuButtonKey,
height: 36.0,
child: PrimaryButton(
text: tr.action_button_create,
onPressed: () =>
_showDropdownMenu(context, controller: controller),
),
),
const SizedBox(width: 28.0),
Expand All @@ -136,7 +125,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
),
],
),
const SizedBox(height: 24.0), // spacing between body elements
const SizedBox(height: 24.0),
FutureBuilder<StudyUUser>(
future: ref.read(userRepositoryProvider).fetchUser(),
builder: (context, snapshot) {
Expand Down Expand Up @@ -193,59 +182,102 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
);
}

Widget _buildCreateNewDropdownItem({
// Widget _buildCreateNewDropdownItem({
// required String title,
// required String subtitle,
// String? hint,
// GestureTapCallback? onTap,
// }) {
// final theme = Theme.of(context);
// return Material(
// color: theme.colorScheme.onPrimary,
// child: InkWell(
// onTap: onTap,
// child: Container(
// padding: const EdgeInsets.all(20),
// child: Row(
// children: [
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// title,
// style: theme.textTheme.titleMedium
// ?.copyWith(color: theme.colorScheme.primary),
// ),
// Text(subtitle),
// if (hint != null)
// Padding(
// padding: const EdgeInsets.only(top: 8),
// child: Text(
// hint,
// style: const TextStyle(fontStyle: FontStyle.italic),
// ),
// )
// else
// const SizedBox.shrink(),
// ],
// ),
// ),
// const SizedBox(
// width: 20,
// ),
// Icon(
// Icons.add,
// color: theme.colorScheme.primary,
// size: 28,
// ),
// const SizedBox(
// width: 8,
// ),
// ],
// ),
// ),
// ),
// );
// }

PopupMenuItem _buildCreatePopupMenuItem({
required String title,
required String subtitle,
String? hint,
GestureTapCallback? onTap,
}) {
final theme = Theme.of(context);
return Material(
color: theme.colorScheme.onPrimary,
child: InkWell(
return PopupMenuItem(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.titleMedium
?.copyWith(color: theme.colorScheme.primary),
),
Text(subtitle),
if (hint != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
hint,
style: const TextStyle(fontStyle: FontStyle.italic),
),
)
else
const SizedBox.shrink(),
],
child: Column(
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.titleMedium
?.copyWith(color: theme.colorScheme.primary),
),
Text(subtitle),
if (hint != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
hint,
style: const TextStyle(fontStyle: FontStyle.italic),
),
)
else
const SizedBox.shrink(),
],
),
),
),
const SizedBox(
width: 20,
),
Icon(
Icons.add,
color: theme.colorScheme.primary,
size: 28,
),
const SizedBox(
width: 8,
),
],
),
),
),
);
],
),
const Divider()
],
));
}
}
18 changes: 18 additions & 0 deletions designer_v2/lib/features/recruit/study_recruit_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class StudyRecruitController extends _$StudyRecruitController
_invitesSubscription?.cancel();
});
_subscribeInvites();
_getParentTemplate();
return state;
}

Expand All @@ -56,6 +57,23 @@ class StudyRecruitController extends _$StudyRecruitController
}); // TODO onError
}

void _getParentTemplate() {
if (state.studyWithMetadata?.model.isSubStudy == false) {
return;
}

state = state.copyWith(
parentTemplate: ref
.watch(
studyControllerProvider(
StudyCreationArgs(
studyID: state.studyWithMetadata!.model.parentTemplateId!,
isTemplate: true),
),
)
.studyWithMetadata);
}

Intervention? getIntervention(String interventionId) {
return state.study.value!.getIntervention(interventionId);
}
Expand Down
Loading

0 comments on commit 5202be4

Please sign in to comment.