From 375931e45de7d370fade79ff5d523d2a9b9c6a6e Mon Sep 17 00:00:00 2001 From: ibrahim Date: Sun, 1 Sep 2024 16:08:56 +0300 Subject: [PATCH 01/13] refactor: replace flutter_portal package --- designer_v2/lib/constants.dart | 4 - .../features/dashboard/dashboard_page.dart | 254 ++++++++++-------- designer_v2/lib/main.dart | 11 +- designer_v2/pubspec.lock | 8 - designer_v2/pubspec.yaml | 1 - 5 files changed, 148 insertions(+), 130 deletions(-) diff --git a/designer_v2/lib/constants.dart b/designer_v2/lib/constants.dart index 122ee5759..7e484948f 100644 --- a/designer_v2/lib/constants.dart +++ b/designer_v2/lib/constants.dart @@ -1,5 +1,3 @@ -import 'package:flutter_portal/flutter_portal.dart'; - class Config { static const isDebugMode = false; @@ -26,8 +24,6 @@ class Config { static const participantInactiveDuration = 3; } -const outPortalLabel = PortalLabel("out"); - const kPathSeparator = ' / '; const String rootRouteName = 'root'; diff --git a/designer_v2/lib/features/dashboard/dashboard_page.dart b/designer_v2/lib/features/dashboard/dashboard_page.dart index 3c2d60e24..bcc8b7995 100644 --- a/designer_v2/lib/features/dashboard/dashboard_page.dart +++ b/designer_v2/lib/features/dashboard/dashboard_page.dart @@ -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'; @@ -42,6 +40,48 @@ class _DashboardScreenState extends ConsumerState { } } + 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); @@ -54,64 +94,13 @@ class _DashboardScreenState extends ConsumerState { children: [ 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), @@ -136,7 +125,7 @@ class _DashboardScreenState extends ConsumerState { ), ], ), - const SizedBox(height: 24.0), // spacing between body elements + const SizedBox(height: 24.0), FutureBuilder( future: ref.read(userRepositoryProvider).fetchUser(), builder: (context, snapshot) { @@ -189,59 +178,110 @@ class _DashboardScreenState extends ConsumerState { ); } - 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 SizedBox( + width: 10, + ), + Icon( + Icons.add, + color: theme.colorScheme.primary, + size: 28, + ), + ], + ), + const Divider() + ], + )); } } diff --git a/designer_v2/lib/main.dart b/designer_v2/lib/main.dart index ea1b7350b..b442de611 100644 --- a/designer_v2/lib/main.dart +++ b/designer_v2/lib/main.dart @@ -1,10 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; -import 'package:studyu_designer_v2/constants.dart'; import 'package:studyu_designer_v2/features/app.dart'; import 'package:studyu_designer_v2/utils/performance.dart'; import 'package:studyu_flutter_common/studyu_flutter_common.dart'; @@ -27,14 +25,7 @@ Future main() async { runApp( // Make dependencies managed by Riverpod available in Widget.build methods // by wrapping the app in a [ProviderScope] - const ProviderScope( - child: Portal( - child: Portal( - labels: [outPortalLabel], - child: App(), - ), - ), - ), + const ProviderScope(child: App()), ); }, (error, stackTrace) { // TODO: top-level error handling diff --git a/designer_v2/pubspec.lock b/designer_v2/pubspec.lock index aac0d096a..97f0c0ecb 100644 --- a/designer_v2/pubspec.lock +++ b/designer_v2/pubspec.lock @@ -384,14 +384,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_portal: - dependency: "direct main" - description: - name: flutter_portal - sha256: "4601b3dc24f385b3761721bd852a3f6c09cddd4e943dd184ed58ee1f43006257" - url: "https://pub.dev" - source: hosted - version: "1.1.4" flutter_riverpod: dependency: "direct main" description: diff --git a/designer_v2/pubspec.yaml b/designer_v2/pubspec.yaml index 975cd9df7..31e950153 100644 --- a/designer_v2/pubspec.yaml +++ b/designer_v2/pubspec.yaml @@ -21,7 +21,6 @@ dependencies: flutter_colorpicker: ^1.1.0 flutter_localizations: sdk: flutter - flutter_portal: ^1.1.4 flutter_riverpod: ^2.5.1 flutter_web_plugins: sdk: flutter From 6c6ae77fbfae6c8d37cac8c8d9496d17340e5d00 Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:20:55 +0300 Subject: [PATCH 02/13] feat: db restriction policy for creation of substudies for closed templates --- database/studyu-schema.sql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/database/studyu-schema.sql b/database/studyu-schema.sql index 2999fedc7..4620d5010 100644 --- a/database/studyu-schema.sql +++ b/database/studyu-schema.sql @@ -1038,6 +1038,23 @@ CREATE POLICY "Joining a closed study should not be possible" ON public.study_su AND study.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 -- From 420700ddb665eb76830f194e9dd7eb47d57bc4ff Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:32:16 +0300 Subject: [PATCH 03/13] feat: creatSubStudy model action restriction for draft and closed templates --- designer_v2/lib/repositories/study_repository.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designer_v2/lib/repositories/study_repository.dart b/designer_v2/lib/repositories/study_repository.dart index 28232995f..58f0c33d7 100644 --- a/designer_v2/lib/repositories/study_repository.dart +++ b/designer_v2/lib/repositories/study_repository.dart @@ -169,7 +169,7 @@ class StudyRepository extends ModelRepository .read(routerProvider) .dispatch(RoutingIntents.substudyNew(model as Template)); }, - isAvailable: model.status != StudyStatus.draft && model.isTemplate, + isAvailable: model.status == StudyStatus.running && model.isTemplate, ), ModelAction( type: StudyActionType.edit, From cbd3b373fc67c750cad549666c1af2a79e39dae5 Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:26:03 +0300 Subject: [PATCH 04/13] feat(designer): disable adding new participants to substudies when parent template study is closed --- .../recruit/study_recruit_controller.dart | 32 +++++++++++++++++++ .../study_recruit_controller_state.dart | 4 +++ .../features/recruit/study_recruit_page.dart | 14 ++++++++ designer_v2/lib/localization/app_en.arb | 1 + 4 files changed, 51 insertions(+) diff --git a/designer_v2/lib/features/recruit/study_recruit_controller.dart b/designer_v2/lib/features/recruit/study_recruit_controller.dart index a12a31cb9..47119206a 100644 --- a/designer_v2/lib/features/recruit/study_recruit_controller.dart +++ b/designer_v2/lib/features/recruit/study_recruit_controller.dart @@ -36,10 +36,12 @@ class StudyRecruitController extends _$StudyRecruitController _invitesSubscription?.cancel(); }); _subscribeInvites(); + _subscribeParentTemplate(); return state; } StreamSubscription>>? _invitesSubscription; + WrappedModel? _parentTemplateSubscription; void _subscribeInvites() { print("StudyRecruitController.subscribe"); @@ -56,6 +58,36 @@ class StudyRecruitController extends _$StudyRecruitController }); // TODO onError } + void _subscribeParentTemplate() { + if (state.studyWithMetadata?.model.isSubStudy == false) { + return; + } + + state = state.copyWith( + parentTemplate: ref + .watch( + studyControllerProvider( + StudyCreationArgs( + studyID: state.studyWithMetadata!.model.parentTemplateId!, + isTemplate: true), + ), + ) + .studyWithMetadata); + } + + /*void _subscribeParentTemplate() { + if (state.studyWithMetadata?.model.isSubStudy == false) { + return; + } + + _parentTemplateSubscription = state.studyRepository + .watch(state.study.value!.parentTemplateId!) + .listen((wrappedModel) { + final parentTemplate = wrappedModel.model; + state = state.copyWith(parentTemplate: AsyncValue.data(parentTemplate)); + }); + }*/ + Intervention? getIntervention(String interventionId) { return state.study.value!.getIntervention(interventionId); } diff --git a/designer_v2/lib/features/recruit/study_recruit_controller_state.dart b/designer_v2/lib/features/recruit/study_recruit_controller_state.dart index 4efe4917c..48267a797 100644 --- a/designer_v2/lib/features/recruit/study_recruit_controller_state.dart +++ b/designer_v2/lib/features/recruit/study_recruit_controller_state.dart @@ -12,6 +12,7 @@ class StudyRecruitControllerState extends StudyControllerBaseState { required super.currentUser, required super.studyWithMetadata, required this.inviteCodeRepository, + this.parentTemplate, this.invites = const AsyncValue.loading(), }); @@ -20,6 +21,7 @@ class StudyRecruitControllerState extends StudyControllerBaseState { /// Wrapped in an [AsyncValue] that mirrors the [StudyController]'s current /// [Study] async states, so that it can be used with [AsyncValueWidget] final AsyncValue?> invites; + final WrappedModel? parentTemplate; final InviteCodeRepository inviteCodeRepository; @@ -27,6 +29,7 @@ class StudyRecruitControllerState extends StudyControllerBaseState { StudyRecruitControllerState copyWith({ WrappedModel? studyWithMetadata, AsyncValue>? invites, + WrappedModel? parentTemplate, }) { return StudyRecruitControllerState( studyId: studyId, @@ -36,6 +39,7 @@ class StudyRecruitControllerState extends StudyControllerBaseState { studyWithMetadata: studyWithMetadata ?? super.studyWithMetadata, inviteCodeRepository: inviteCodeRepository, invites: invites ?? this.invites, + parentTemplate: parentTemplate ?? this.parentTemplate, ); } diff --git a/designer_v2/lib/features/recruit/study_recruit_page.dart b/designer_v2/lib/features/recruit/study_recruit_page.dart index d04a4d2f4..fe7181075 100644 --- a/designer_v2/lib/features/recruit/study_recruit_page.dart +++ b/designer_v2/lib/features/recruit/study_recruit_page.dart @@ -24,6 +24,9 @@ class StudyRecruitScreen extends StudyPageWidget { final state = ref.watch(studyRecruitControllerProvider(studyCreationArgs)); final controller = ref.watch(studyRecruitControllerProvider(studyCreationArgs).notifier); + final bool isSubStudy = state.studyWithMetadata?.model.isSubStudy == true; + final bool isParentTemplateClosed = + state.parentTemplate?.model.isClosed == true; if (state.studyWithMetadata?.model.isTemplate == true) { return Padding( @@ -36,6 +39,17 @@ class StudyRecruitScreen extends StudyPageWidget { ); } + if (isSubStudy && isParentTemplateClosed) { + return Padding( + padding: const EdgeInsets.only(top: 24), + child: EmptyBody( + icon: Icons.link_off_rounded, + title: tr.code_list_template_title, + description: "", + ), + ); + } + return AsyncValueWidget?>( value: state.invites, data: (studyInvites) => Column( diff --git a/designer_v2/lib/localization/app_en.arb b/designer_v2/lib/localization/app_en.arb index 2d1ee57e2..2c0fe4e7b 100644 --- a/designer_v2/lib/localization/app_en.arb +++ b/designer_v2/lib/localization/app_en.arb @@ -576,6 +576,7 @@ "code_list_empty_title": "You haven't invited anyone yet", "code_list_empty_description": "Add participants to your study via access codes.", "code_list_template_title": "Recruiting participants is not available for Template Trials.", + "code_list_closed_parent_template_title": "Recruiting participants is not available for Closed Parent Template Trials.", "code_list_header_code": "Code", "action_button_code_new": "New code", "@__________________STUDYPAGE_MONITOR__________________": {}, From 6fee81374033ba8a920ab8f44bdbba291db7a7d3 Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:26:21 +0300 Subject: [PATCH 05/13] feat(designer): disable adding new participants to substudies when parent template study is closed --- designer_v2/lib/features/recruit/study_recruit_controller.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/designer_v2/lib/features/recruit/study_recruit_controller.dart b/designer_v2/lib/features/recruit/study_recruit_controller.dart index 47119206a..18c2f01fb 100644 --- a/designer_v2/lib/features/recruit/study_recruit_controller.dart +++ b/designer_v2/lib/features/recruit/study_recruit_controller.dart @@ -41,7 +41,6 @@ class StudyRecruitController extends _$StudyRecruitController } StreamSubscription>>? _invitesSubscription; - WrappedModel? _parentTemplateSubscription; void _subscribeInvites() { print("StudyRecruitController.subscribe"); From c3666e48e397054839d49b80864d3ce442d0d08d Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:27:10 +0300 Subject: [PATCH 06/13] feat(designer): disable adding new participants to substudies when parent template study is closed --- .../lib/features/recruit/study_recruit_controller.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/designer_v2/lib/features/recruit/study_recruit_controller.dart b/designer_v2/lib/features/recruit/study_recruit_controller.dart index 18c2f01fb..5dc998596 100644 --- a/designer_v2/lib/features/recruit/study_recruit_controller.dart +++ b/designer_v2/lib/features/recruit/study_recruit_controller.dart @@ -36,7 +36,7 @@ class StudyRecruitController extends _$StudyRecruitController _invitesSubscription?.cancel(); }); _subscribeInvites(); - _subscribeParentTemplate(); + _getParentTemplate(); return state; } @@ -57,7 +57,7 @@ class StudyRecruitController extends _$StudyRecruitController }); // TODO onError } - void _subscribeParentTemplate() { + void _getParentTemplate() { if (state.studyWithMetadata?.model.isSubStudy == false) { return; } From ba5eac1b1ff980e1e516ba2181a1eb479bbbafe6 Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:27:55 +0300 Subject: [PATCH 07/13] chore: code cleanup --- .../features/recruit/study_recruit_controller.dart | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/designer_v2/lib/features/recruit/study_recruit_controller.dart b/designer_v2/lib/features/recruit/study_recruit_controller.dart index 5dc998596..4079fa9b3 100644 --- a/designer_v2/lib/features/recruit/study_recruit_controller.dart +++ b/designer_v2/lib/features/recruit/study_recruit_controller.dart @@ -74,19 +74,6 @@ class StudyRecruitController extends _$StudyRecruitController .studyWithMetadata); } - /*void _subscribeParentTemplate() { - if (state.studyWithMetadata?.model.isSubStudy == false) { - return; - } - - _parentTemplateSubscription = state.studyRepository - .watch(state.study.value!.parentTemplateId!) - .listen((wrappedModel) { - final parentTemplate = wrappedModel.model; - state = state.copyWith(parentTemplate: AsyncValue.data(parentTemplate)); - }); - }*/ - Intervention? getIntervention(String interventionId) { return state.study.value!.getIntervention(interventionId); } From 03a74288e1ff056c435360bd2782a3ac89043498 Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:48:03 +0300 Subject: [PATCH 08/13] chore: translation --- designer_v2/lib/features/recruit/study_recruit_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designer_v2/lib/features/recruit/study_recruit_page.dart b/designer_v2/lib/features/recruit/study_recruit_page.dart index fe7181075..9b550916c 100644 --- a/designer_v2/lib/features/recruit/study_recruit_page.dart +++ b/designer_v2/lib/features/recruit/study_recruit_page.dart @@ -44,7 +44,7 @@ class StudyRecruitScreen extends StudyPageWidget { padding: const EdgeInsets.only(top: 24), child: EmptyBody( icon: Icons.link_off_rounded, - title: tr.code_list_template_title, + title: tr.code_list_closed_parent_template_title, description: "", ), ); From 6cec786deeeaba08306c72110b0f060de2ed997f Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:54:57 +0300 Subject: [PATCH 09/13] feat(db): prevent joining substudies with closed parent templates policy --- database/studyu-schema.sql | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/database/studyu-schema.sql b/database/studyu-schema.sql index 4620d5010..079f66d24 100644 --- a/database/studyu-schema.sql +++ b/database/studyu-schema.sql @@ -1038,6 +1038,26 @@ 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 -- From 733c93249576b29979d17f025df2803041c56092 Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:27:17 +0300 Subject: [PATCH 10/13] feat: navigate user to welcome page if there is an error on kickoff --- app/lib/screens/study/onboarding/kickoff.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/lib/screens/study/onboarding/kickoff.dart b/app/lib/screens/study/onboarding/kickoff.dart index d59c681a9..2238caab5 100644 --- a/app/lib/screens/study/onboarding/kickoff.dart +++ b/app/lib/screens/study/onboarding/kickoff.dart @@ -38,7 +38,14 @@ class _KickoffScreen extends State { (_) => 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); } } From 7e1bfddd770af0423165f7c6c3b7dfa6cadad8ba Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:44:19 +0300 Subject: [PATCH 11/13] chore: translation for create study subtitles --- designer_v2/lib/localization/app_en.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/designer_v2/lib/localization/app_en.arb b/designer_v2/lib/localization/app_en.arb index 2c0fe4e7b..4b846ccec 100644 --- a/designer_v2/lib/localization/app_en.arb +++ b/designer_v2/lib/localization/app_en.arb @@ -93,9 +93,9 @@ "action_button_new_study": "New trial", "action_button_create": "Create", "action_button_standalone_study_title": "Standalone Trial", - "action_button_standalone_study_subtitle": "Create a new fully customizable, independent trial.", + "action_button_standalone_study_subtitle": "Create a new fully customizable Standalone trial, which can be used for one person or also for a series of Standalone trials with the same specifications for everyone", "action_button_template_title": "Template Trial", - "action_button_template_subtitle": "Create a new Template Trial which consists of Subtrials.\nThe Template Trial can restrict the customization of the Subtrials.\nThe Subtrials are prefilled with the configuration of the Template Trial.\nThe Template Trial itself is not a study and cannot be run.", + "action_button_template_subtitle": "Create a new Template Trial, which is a template for a series of Standalone Trials where every participant can have a slightly different trial. For example, run a trial investigating different non-pharmacological interventions on sleep quality, where each person can choose their own intervention of interest and use one of three pre-defined measures of sleep quality.", "action_button_template_hint": "Hint: Use the three dots menu next to a Template Trial to create a Subtrial.", "search": "Search", "studies_list_header_title": "Title", From cb70edae13fa966f378c5a8ab64cd69a161da884 Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:49:05 +0300 Subject: [PATCH 12/13] chore: translation --- designer_v2/lib/localization/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designer_v2/lib/localization/app_en.arb b/designer_v2/lib/localization/app_en.arb index 4b846ccec..baa6cc062 100644 --- a/designer_v2/lib/localization/app_en.arb +++ b/designer_v2/lib/localization/app_en.arb @@ -95,7 +95,7 @@ "action_button_standalone_study_title": "Standalone Trial", "action_button_standalone_study_subtitle": "Create a new fully customizable Standalone trial, which can be used for one person or also for a series of Standalone trials with the same specifications for everyone", "action_button_template_title": "Template Trial", - "action_button_template_subtitle": "Create a new Template Trial, which is a template for a series of Standalone Trials where every participant can have a slightly different trial. For example, run a trial investigating different non-pharmacological interventions on sleep quality, where each person can choose their own intervention of interest and use one of three pre-defined measures of sleep quality.", + "action_button_template_subtitle": "Create a new Template Trial, which is a template for a series of Standalone Trials where every participant can have a slightly different trial.\n\nFor example, run a trial investigating different non-pharmacological interventions on sleep quality, where each person can choose their own intervention of interest and use one of three pre-defined measures of sleep quality.", "action_button_template_hint": "Hint: Use the three dots menu next to a Template Trial to create a Subtrial.", "search": "Search", "studies_list_header_title": "Title", From bc8189eab27a9fc27acd4eb520e3c4970da95aed Mon Sep 17 00:00:00 2001 From: Ibrahim Ozkan <51259479+ibrahimozkn@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:50:31 +0300 Subject: [PATCH 13/13] style: create popup menu item --- designer_v2/lib/features/dashboard/dashboard_page.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/designer_v2/lib/features/dashboard/dashboard_page.dart b/designer_v2/lib/features/dashboard/dashboard_page.dart index bcc8b7995..677db1662 100644 --- a/designer_v2/lib/features/dashboard/dashboard_page.dart +++ b/designer_v2/lib/features/dashboard/dashboard_page.dart @@ -270,14 +270,6 @@ class _DashboardScreenState extends ConsumerState { ], ), ), - const SizedBox( - width: 10, - ), - Icon( - Icons.add, - color: theme.colorScheme.primary, - size: 28, - ), ], ), const Divider()