diff --git a/.gitignore b/.gitignore index f3702eab..1854c3ba 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ *.log *.pyc *.swp +*.freezed.dart +*.g.dart .DS_Store .atom/ .buildlog/ diff --git a/android/app/build.gradle b/android/app/build.gradle index 7d5d91dd..2b353645 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -53,7 +53,7 @@ android { applicationId "com.plane.so" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/integration_test/dashboard_test.dart b/integration-tests/dashboard_test.dart similarity index 93% rename from integration_test/dashboard_test.dart rename to integration-tests/dashboard_test.dart index 7091850d..5c208e11 100644 --- a/integration_test/dashboard_test.dart +++ b/integration-tests/dashboard_test.dart @@ -2,18 +2,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:plane/bottom_sheets/global_search_sheet.dart'; -import 'package:plane/bottom_sheets/select_workspace.dart'; +import 'package:plane/bottom-sheets/global_search_sheet.dart'; +import 'package:plane/bottom-sheets/select_workspace.dart'; import 'package:plane/models/user_profile_model.dart'; -import 'package:plane/models/workspace_model.dart'; +import 'package:plane/models/Workspace/workspace_model.dart'; import 'package:plane/provider/dashboard_provider.dart'; import 'package:plane/provider/global_search_provider.dart'; import 'package:plane/provider/profile_provider.dart'; import 'package:plane/provider/projects_provider.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/workspace_provider.dart'; -import 'package:plane/screens/MainScreens/Home/Dashboard/dash_board_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_workspace.dart'; +import 'package:plane/screens/dashboard/dash_board_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; import 'package:plane/utils/enums.dart'; diff --git a/lib/app.dart b/lib/app.dart index 7d224532..796c9265 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:loading_indicator/loading_indicator.dart'; import 'package:plane/provider/workspace_provider.dart'; -import 'package:plane/screens/on_boarding/auth/invite_co_workers.dart'; -import 'package:plane/screens/on_boarding/auth/join_workspaces.dart'; -import 'package:plane/screens/on_boarding/auth/setup_profile_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_workspace.dart'; -import 'package:plane/screens/on_boarding/auth/sign_in.dart'; +import 'package:plane/screens/onboarding/auth/invite_co_workers.dart'; +import 'package:plane/screens/onboarding/auth/join_workspaces.dart'; +import 'package:plane/screens/onboarding/auth/setup_profile_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; +import 'package:plane/screens/onboarding/auth/sign_in.dart'; import 'package:plane/startup/dependency_resolver.dart'; import 'package:plane/widgets/error_state.dart'; import 'utils/enums.dart'; @@ -31,7 +31,9 @@ class _AppState extends ConsumerState { final configProvider = ref.watch(ProviderList.configProvider); final themeProvider = ref.watch(ProviderList.themeProvider); return Scaffold( - body: (configProvider.getConfigState == StateEnum.loading || + body: ( + // TODO -> Config API should be re-initiated once the API added in this base API + // configProvider.getConfigState == StateEnum.loading || profileProv.getProfileState == StateEnum.loading || workspaceProv.workspaceInvitationState == StateEnum.loading) ? Center( diff --git a/lib/bottom_sheets/add_attachment_sheet.dart b/lib/bottom-sheets/add_attachment_sheet.dart similarity index 100% rename from lib/bottom_sheets/add_attachment_sheet.dart rename to lib/bottom-sheets/add_attachment_sheet.dart diff --git a/lib/bottom_sheets/add_link_sheet.dart b/lib/bottom-sheets/add_link_sheet.dart similarity index 100% rename from lib/bottom_sheets/add_link_sheet.dart rename to lib/bottom-sheets/add_link_sheet.dart diff --git a/lib/bottom_sheets/assignee_sheet.dart b/lib/bottom-sheets/assignee_sheet.dart similarity index 100% rename from lib/bottom_sheets/assignee_sheet.dart rename to lib/bottom-sheets/assignee_sheet.dart diff --git a/lib/bottom_sheets/block_sheet.dart b/lib/bottom-sheets/block_sheet.dart similarity index 100% rename from lib/bottom_sheets/block_sheet.dart rename to lib/bottom-sheets/block_sheet.dart diff --git a/lib/bottom_sheets/company_size_sheet.dart b/lib/bottom-sheets/company_size_sheet.dart similarity index 100% rename from lib/bottom_sheets/company_size_sheet.dart rename to lib/bottom-sheets/company_size_sheet.dart diff --git a/lib/bottom_sheets/create_estimate.dart b/lib/bottom-sheets/create_estimate.dart similarity index 100% rename from lib/bottom_sheets/create_estimate.dart rename to lib/bottom-sheets/create_estimate.dart diff --git a/lib/bottom_sheets/delete_cycle_sheet.dart b/lib/bottom-sheets/delete_cycle_sheet.dart similarity index 100% rename from lib/bottom_sheets/delete_cycle_sheet.dart rename to lib/bottom-sheets/delete_cycle_sheet.dart diff --git a/lib/bottom_sheets/delete_estimate_sheet.dart b/lib/bottom-sheets/delete_estimate_sheet.dart similarity index 100% rename from lib/bottom_sheets/delete_estimate_sheet.dart rename to lib/bottom-sheets/delete_estimate_sheet.dart diff --git a/lib/bottom_sheets/delete_labels_sheet.dart b/lib/bottom-sheets/delete_labels_sheet.dart similarity index 80% rename from lib/bottom_sheets/delete_labels_sheet.dart rename to lib/bottom-sheets/delete_labels_sheet.dart index db97111b..8f5fd315 100644 --- a/lib/bottom_sheets/delete_labels_sheet.dart +++ b/lib/bottom-sheets/delete_labels_sheet.dart @@ -20,7 +20,7 @@ class DeleteLabelSheet extends ConsumerStatefulWidget { class _DeleteLabelSheetState extends ConsumerState { @override Widget build(BuildContext context) { - final issuesProvider = ref.read(ProviderList.issuesProvider); + final labelNotifier = ref.read(ProviderList.labelProvider.notifier); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), child: Column( @@ -62,20 +62,7 @@ class _DeleteLabelSheetState extends ConsumerState { padding: const EdgeInsets.only(bottom: 20), child: Button( ontap: () async { - await issuesProvider - .issueLabels( - slug: ref - .watch(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .watch(ProviderList.projectProvider) - .currentProject['id'], - method: CRUD.delete, - data: {}, - labelId: widget.labelId, - ref: ref) - .then((value) { + await labelNotifier.deleteLabel(widget.labelId).then((value) { ref.read(ProviderList.issuesProvider).filterIssues( slug: ref .watch(ProviderList.workspaceProvider) diff --git a/lib/bottom_sheets/delete_leave_project_sheet.dart b/lib/bottom-sheets/delete_leave_project_sheet.dart similarity index 97% rename from lib/bottom_sheets/delete_leave_project_sheet.dart rename to lib/bottom-sheets/delete_leave_project_sheet.dart index 37a76648..39c1de11 100644 --- a/lib/bottom_sheets/delete_leave_project_sheet.dart +++ b/lib/bottom-sheets/delete_leave_project_sheet.dart @@ -28,6 +28,8 @@ class _DeleteLeaveProjectSheetState Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); final projectProvider = ref.watch(ProviderList.projectProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); + return GestureDetector( onTap: () { FocusScope.of(context).unfocus(); @@ -234,7 +236,8 @@ class _DeleteLeaveProjectSheetState postHogService( eventName: 'LEAVE_PROJECT', properties: widget.data, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); Navigator.of(context) ..pop() ..pop() @@ -260,7 +263,9 @@ class _DeleteLeaveProjectSheetState postHogService( eventName: 'DELETE_PROJECT', properties: widget.data, - ref: ref); + userEmail: + profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); } }); Navigator.of(context) diff --git a/lib/bottom_sheets/delete_module_sheet.dart b/lib/bottom-sheets/delete_module_sheet.dart similarity index 100% rename from lib/bottom_sheets/delete_module_sheet.dart rename to lib/bottom-sheets/delete_module_sheet.dart diff --git a/lib/bottom_sheets/delete_project_sheet.dart b/lib/bottom-sheets/delete_project_sheet.dart similarity index 98% rename from lib/bottom_sheets/delete_project_sheet.dart rename to lib/bottom-sheets/delete_project_sheet.dart index cd08b1ce..5829e185 100644 --- a/lib/bottom_sheets/delete_project_sheet.dart +++ b/lib/bottom-sheets/delete_project_sheet.dart @@ -26,6 +26,7 @@ class _DeleteProjectSheetState extends ConsumerState { Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); final projectProviderRead = ref.watch(ProviderList.projectProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), @@ -209,7 +210,8 @@ class _DeleteProjectSheetState extends ConsumerState { postHogService( eventName: 'DELETE_PROJECT', properties: widget.data, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); } }); Navigator.of(context) diff --git a/lib/bottom_sheets/delete_state_sheet.dart b/lib/bottom-sheets/delete_state_sheet.dart similarity index 71% rename from lib/bottom_sheets/delete_state_sheet.dart rename to lib/bottom-sheets/delete_state_sheet.dart index 06e29125..0b8ca099 100644 --- a/lib/bottom_sheets/delete_state_sheet.dart +++ b/lib/bottom-sheets/delete_state_sheet.dart @@ -21,11 +21,11 @@ class DeleteStateSheet extends ConsumerStatefulWidget { class _DeleteStateSheetState extends ConsumerState { @override Widget build(BuildContext context) { - final projectProvider = ref.watch(ProviderList.projectProvider); final themeProvider = ref.watch(ProviderList.themeProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + final statesProviderRead = ref.watch(ProviderList.statesProvider.notifier); return LoadingWidget( - loading: projectProvider.stateCrudState == StateEnum.loading, + loading: statesProvider.deleteState == StateEnum.loading, allowBorderRadius: true, widgetClass: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 30), @@ -70,31 +70,11 @@ class _DeleteStateSheetState extends ConsumerState { Button( color: const Color.fromRGBO(254, 242, 242, 1), textColor: themeProvider.themeManager.textErrorColor, - filledButton: false, borderColor: themeProvider.themeManager.textErrorColor, text: 'Delete', ontap: () async { - await projectProvider.stateCrud( - slug: ref - .watch(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projId: ref - .watch(ProviderList.projectProvider) - .currentProject['id'], - method: CRUD.delete, - stateId: widget.stateId, - context: context, - data: {}, - ref: ref); - issuesProvider.getStates( - slug: ref - .watch(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .watch(ProviderList.projectProvider) - .currentProject['id'], + await statesProviderRead.deleteState( + stateId: widget.stateId, ); Navigator.of(context).pop(); }, diff --git a/lib/bottom_sheets/delete_workspace_sheet.dart b/lib/bottom-sheets/delete_workspace_sheet.dart similarity index 97% rename from lib/bottom_sheets/delete_workspace_sheet.dart rename to lib/bottom-sheets/delete_workspace_sheet.dart index 96d504a3..3575a0ba 100644 --- a/lib/bottom_sheets/delete_workspace_sheet.dart +++ b/lib/bottom-sheets/delete_workspace_sheet.dart @@ -26,6 +26,7 @@ class _DeleteOrLeaveWorkpaceState extends ConsumerState { Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); final workspaceProvider = ref.watch(ProviderList.workspaceProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); return GestureDetector( onTap: () { FocusScope.of(context).unfocus(); @@ -240,7 +241,8 @@ class _DeleteOrLeaveWorkpaceState extends ConsumerState { properties: { 'WORKSPACE_NAME': widget.workspaceName }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); await ref .watch(ProviderList.profileProvider) .updateProfile(data: { @@ -279,7 +281,8 @@ class _DeleteOrLeaveWorkpaceState extends ConsumerState { properties: { 'WORKSPACE_NAME': widget.workspaceName }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); await ref .watch(ProviderList.profileProvider) .updateProfile(data: { diff --git a/lib/bottom_sheets/edit_block_sheet.dart b/lib/bottom-sheets/edit_block_sheet.dart similarity index 100% rename from lib/bottom_sheets/edit_block_sheet.dart rename to lib/bottom-sheets/edit_block_sheet.dart diff --git a/lib/bottom_sheets/edit_page_sheeet.dart b/lib/bottom-sheets/edit_page_sheeet.dart similarity index 100% rename from lib/bottom_sheets/edit_page_sheeet.dart rename to lib/bottom-sheets/edit_page_sheeet.dart diff --git a/lib/bottom_sheets/emoji_sheet.dart b/lib/bottom-sheets/emoji_sheet.dart similarity index 100% rename from lib/bottom_sheets/emoji_sheet.dart rename to lib/bottom-sheets/emoji_sheet.dart diff --git a/lib/bottom_sheets/filters/filter_sheet.dart b/lib/bottom-sheets/filters/filter_sheet.dart similarity index 90% rename from lib/bottom_sheets/filters/filter_sheet.dart rename to lib/bottom-sheets/filters/filter_sheet.dart index dd881f27..e45c5bfc 100644 --- a/lib/bottom_sheets/filters/filter_sheet.dart +++ b/lib/bottom-sheets/filters/filter_sheet.dart @@ -5,6 +5,7 @@ import 'package:calendar_date_picker2/calendar_date_picker2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:plane/models/project/state/state_model.dart'; import 'package:plane/models/issues.dart'; import 'package:plane/provider/cycles_provider.dart'; import 'package:plane/provider/modules_provider.dart'; @@ -36,18 +37,21 @@ part 'widgets/labels_filter.dart'; // ignore: must_be_immutable class FilterSheet extends ConsumerStatefulWidget { - FilterSheet( - {super.key, - required this.issueCategory, - this.filtersData, - this.fromViews = false, - this.isArchived = false, - this.fromCreateView = false}); + FilterSheet({ + super.key, + required this.issueCategory, + this.filtersData, + this.fromViews = false, + this.isArchived = false, + this.fromCreateView = false, + this.cycleOrModuleId, + }); final IssueCategory issueCategory; final bool fromCreateView; final bool fromViews; final bool isArchived; dynamic filtersData; + String? cycleOrModuleId; @override ConsumerState createState() => _FilterSheetState(); } @@ -67,14 +71,20 @@ class _FilterSheetState extends ConsumerState { setState(() {}); }, ref: ref); - super.initState(); } @override Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); - + log(ref + .read(ProviderList.cyclesProvider) + .issues + .filters + .priorities + .toString()); + log(state.filters.priorities.toString()); + return Container( padding: const EdgeInsets.only(top: 20, left: 20, right: 20), child: Stack( @@ -156,9 +166,9 @@ class _FilterSheetState extends ConsumerState { ? Container() : _saveView(state: state, ref: ref), _applyFilterButton( - state: state, - context: context, - ) + state: state, + context: context, + cycleOrModuleId: widget.cycleOrModuleId) ], ), ), diff --git a/lib/bottom_sheets/filters/filter_sheet_state.dart b/lib/bottom-sheets/filters/filter_sheet_state.dart similarity index 72% rename from lib/bottom_sheets/filters/filter_sheet_state.dart rename to lib/bottom-sheets/filters/filter_sheet_state.dart index ff8c0d67..e596e2dd 100644 --- a/lib/bottom_sheets/filters/filter_sheet_state.dart +++ b/lib/bottom-sheets/filters/filter_sheet_state.dart @@ -18,13 +18,27 @@ class _FilterState { isFilterDataEmpty = isFilterEmpty(tempFilters: myIssuesProvider.issues.filters); - } else { + } else if (issueCategory == IssueCategory.issues) { final issuesProvider = ref.read(ProviderList.issuesProvider); filters = Filters.fromJson(Filters.toJson(issuesProvider.issues.filters)); isFilterDataEmpty = isFilterEmpty(tempFilters: issuesProvider.issues.filters); + } else if (issueCategory == IssueCategory.cycleIssues) { + final cyclesProvider = ref.read(ProviderList.cyclesProvider); + filters = + Filters.fromJson(Filters.toJson(cyclesProvider.issues.filters)); + + isFilterDataEmpty = + isFilterEmpty(tempFilters: cyclesProvider.issues.filters); + } else if (issueCategory == IssueCategory.moduleIssues) { + final modulesProvider = ref.read(ProviderList.modulesProvider); + filters = + Filters.fromJson(Filters.toJson(modulesProvider.issues.filters)); + + isFilterDataEmpty = + isFilterEmpty(tempFilters: modulesProvider.issues.filters); } } else { filters = Filters.fromJson(filtersData["Filters"]); @@ -64,12 +78,57 @@ class _FilterState { {'icon': Icons.do_disturb_alt_outlined, 'text': 'none', 'color': '#A3A3A3'} ]; - List states = [ - {'id': 'backlog', 'name': 'Backlog', 'color': '#5e6ad2'}, - {'id': 'unstarted', 'name': 'Unstarted', 'color': '#eb5757'}, - {'id': 'started', 'name': 'Started', 'color': '#26b5ce'}, - {'id': 'completed', 'name': 'Completed', 'color': '#f2c94c'}, - {'id': 'cancelled', 'name': 'Cancelled', 'color': '#4cb782'} + List states = [ + StateModel.initialize().copyWith( + group: 'backlog', + name: 'Backlog', + color: '#5e6ad2', + stateIcon: SvgPicture.asset( + 'assets/svg_images/circle.svg', + color: '#5e6ad2'.toColor(), + height: 20, + width: 20, + )), + StateModel.initialize().copyWith( + group: 'unstarted', + name: 'Unstarted', + color: '#eb5757', + stateIcon: SvgPicture.asset( + 'assets/svg_images/unstarted.svg', + color: '#eb5757'.toColor(), + height: 20, + width: 20, + )), + StateModel.initialize().copyWith( + group: 'started', + name: 'Started', + color: '#26b5ce', + stateIcon: SvgPicture.asset( + 'assets/svg_images/in_progress.svg', + color: '#26b5ce'.toColor(), + height: 20, + width: 20, + )), + StateModel.initialize().copyWith( + group: 'completed', + name: 'Completed', + color: '#f2c94c', + stateIcon: SvgPicture.asset( + 'assets/svg_images/done.svg', + color: '#f2c94c'.toColor(), + height: 20, + width: 20, + )), + StateModel.initialize().copyWith( + group: 'cancelled', + name: 'Cancelled', + color: '#4cb782', + stateIcon: SvgPicture.asset( + 'assets/svg_images/cancelled.svg', + color: '#4cb782'.toColor(), + height: 20, + width: 20, + )) ]; Filters filters = Filters( @@ -185,7 +244,8 @@ class _FilterState { } } - void _applyFilters(BuildContext context) { + void _applyFilters( + {required BuildContext context, String? cycleOrModuleId}) async { final MyIssuesProvider myIssuesProvider = ref.read(ProviderList.myIssuesProvider); final IssuesProvider issuesProvider = ref.read(ProviderList.issuesProvider); @@ -207,21 +267,19 @@ class _FilterState { } issueCategory == IssueCategory.myIssues ? myIssuesProvider.issues.filters = filters - : issuesProvider.issues.filters = filters; + : issueCategory == IssueCategory.cycleIssues + ? cyclesProvider.issues.filters = filters + : issueCategory == IssueCategory.moduleIssues + ? modulesProvider.issues.filters = filters + : issuesProvider.issues.filters = filters; if (issueCategory == IssueCategory.cycleIssues) { - cyclesProvider - .filterCycleIssues( - slug: slug, - projectId: projID, - ) - .then((value) => cyclesProvider.initializeBoard()); + cyclesProvider.updateCycleView(); + cyclesProvider.filterCycleIssues( + slug: slug, projectId: projID, cycleID: cycleOrModuleId, ref: ref); } else if (issueCategory == IssueCategory.moduleIssues) { - modulesProvider - .filterModuleIssues( - slug: slug, - projectId: projID, - ) - .then((value) => modulesProvider.initializeBoard()); + modulesProvider.updateModuleView(); + modulesProvider.filterModuleIssues( + slug: slug, projectId: projID, ref: ref, moduleID: cycleOrModuleId); } else if (issueCategory == IssueCategory.myIssues) { myIssuesProvider.updateMyIssueView(); myIssuesProvider.filterIssues(); diff --git a/lib/bottom_sheets/filters/widgets/assigness_filter.dart b/lib/bottom-sheets/filters/widgets/assigness_filter.dart similarity index 100% rename from lib/bottom_sheets/filters/widgets/assigness_filter.dart rename to lib/bottom-sheets/filters/widgets/assigness_filter.dart diff --git a/lib/bottom_sheets/filters/widgets/created_by_filter.dart b/lib/bottom-sheets/filters/widgets/created_by_filter.dart similarity index 100% rename from lib/bottom_sheets/filters/widgets/created_by_filter.dart rename to lib/bottom-sheets/filters/widgets/created_by_filter.dart diff --git a/lib/bottom_sheets/filters/widgets/due_date_filter.dart b/lib/bottom-sheets/filters/widgets/due_date_filter.dart similarity index 100% rename from lib/bottom_sheets/filters/widgets/due_date_filter.dart rename to lib/bottom-sheets/filters/widgets/due_date_filter.dart diff --git a/lib/bottom_sheets/filters/widgets/filter_buttons.dart b/lib/bottom-sheets/filters/widgets/filter_buttons.dart similarity index 93% rename from lib/bottom_sheets/filters/widgets/filter_buttons.dart rename to lib/bottom-sheets/filters/widgets/filter_buttons.dart index c6a00e92..1fdc8fc0 100644 --- a/lib/bottom_sheets/filters/widgets/filter_buttons.dart +++ b/lib/bottom-sheets/filters/widgets/filter_buttons.dart @@ -18,7 +18,7 @@ Widget _clearFilterButton( stateGroup: [], subscriber: [], ); - state._applyFilters(ref.context); + state._applyFilters(context: ref.context); }, child: Container( height: 35, @@ -94,10 +94,10 @@ Widget _saveView({required _FilterState state, required WidgetRef ref}) { ); } -Widget _applyFilterButton({ - required _FilterState state, - required BuildContext context, -}) { +Widget _applyFilterButton( + {required _FilterState state, + required BuildContext context, + String? cycleOrModuleId}) { return Container( height: 50, width: state.fromCreateView || state.issueCategory == IssueCategory.myIssues @@ -107,7 +107,7 @@ Widget _applyFilterButton({ child: Button( text: state.fromCreateView ? 'Add Filter' : 'Apply Filter', ontap: () { - state._applyFilters(context); + state._applyFilters(context:context, cycleOrModuleId: cycleOrModuleId ?? ''); }, textColor: Colors.white, ), diff --git a/lib/bottom_sheets/filters/widgets/labels_filter.dart b/lib/bottom-sheets/filters/widgets/labels_filter.dart similarity index 68% rename from lib/bottom_sheets/filters/widgets/labels_filter.dart rename to lib/bottom-sheets/filters/widgets/labels_filter.dart index 47821f57..b7e48f6d 100644 --- a/lib/bottom_sheets/filters/widgets/labels_filter.dart +++ b/lib/bottom-sheets/filters/widgets/labels_filter.dart @@ -11,16 +11,14 @@ class _LabelFilter extends ConsumerStatefulWidget { class __LabelFilterState extends ConsumerState<_LabelFilter> { @override Widget build(BuildContext context) { - final ThemeProvider themeProvider = ref.read(ProviderList.themeProvider); - final IssuesProvider issuesProvider = - ref.watch(ProviderList.issuesProvider); - final MyIssuesProvider myIssuesProvider = - ref.watch(ProviderList.myIssuesProvider); + final themeProvider = ref.read(ProviderList.themeProvider); + final labelsProvider = ref.watch(ProviderList.labelProvider); + return CustomExpansionTile( title: 'Labels', child: (widget.state.issueCategory == IssueCategory.myIssues - ? myIssuesProvider.labels - : issuesProvider.labels) + ? labelsProvider.workspaceLabels + : labelsProvider.projectLabels) .isEmpty ? Container( padding: const EdgeInsets.only(left: 25), @@ -31,14 +29,15 @@ class __LabelFilterState extends ConsumerState<_LabelFilter> { ) : Wrap( children: (widget.state.issueCategory == IssueCategory.myIssues - ? myIssuesProvider.labels - : issuesProvider.labels) - .map((e) => GestureDetector( + ? labelsProvider.workspaceLabels + : labelsProvider.projectLabels) + .values + .map((label) => GestureDetector( onTap: () { - if (widget.state.filters.labels.contains(e['id'])) { - widget.state.filters.labels.remove(e['id']); + if (widget.state.filters.labels.contains(label.id)) { + widget.state.filters.labels.remove(label.id); } else { - widget.state.filters.labels.add(e['id']); + widget.state.filters.labels.add(label.id); } widget.state.setState(); }, @@ -46,11 +45,11 @@ class __LabelFilterState extends ConsumerState<_LabelFilter> { ref: ref, icon: CircleAvatar( radius: 5, - backgroundColor: e['color'].toString().toColor()), - text: e['name'], + backgroundColor: label.color.toColor()), + text: label.name, selected: - widget.state.filters.labels.contains(e['id']), - color: widget.state.filters.labels.contains(e['id']) + widget.state.filters.labels.contains(label.id), + color: widget.state.filters.labels.contains(label.id) ? themeProvider.themeManager.primaryColour : themeProvider .themeManager.secondaryBackgroundDefaultColor, diff --git a/lib/bottom_sheets/filters/widgets/priority_filter.dart b/lib/bottom-sheets/filters/widgets/priority_filter.dart similarity index 100% rename from lib/bottom_sheets/filters/widgets/priority_filter.dart rename to lib/bottom-sheets/filters/widgets/priority_filter.dart diff --git a/lib/bottom_sheets/filters/widgets/start_date_filter.dart b/lib/bottom-sheets/filters/widgets/start_date_filter.dart similarity index 100% rename from lib/bottom_sheets/filters/widgets/start_date_filter.dart rename to lib/bottom-sheets/filters/widgets/start_date_filter.dart diff --git a/lib/bottom_sheets/filters/widgets/state_filter.dart b/lib/bottom-sheets/filters/widgets/state_filter.dart similarity index 63% rename from lib/bottom_sheets/filters/widgets/state_filter.dart rename to lib/bottom-sheets/filters/widgets/state_filter.dart index 49af85de..d4565b91 100644 --- a/lib/bottom_sheets/filters/widgets/state_filter.dart +++ b/lib/bottom-sheets/filters/widgets/state_filter.dart @@ -12,57 +12,54 @@ class __StateFilterState extends ConsumerState<_StateFilter> { @override Widget build(BuildContext context) { final ThemeProvider themeProvider = ref.read(ProviderList.themeProvider); - final IssuesProvider issuesProvider = ref.read(ProviderList.issuesProvider); + final statesProvider = ref.read(ProviderList.statesProvider); return CustomExpansionTile( title: 'State', child: Wrap( children: (widget.state.issueCategory == IssueCategory.myIssues ? widget.state.states - : issuesProvider.states.values) - .map((e) { - final String key = widget.state.issueCategory == IssueCategory.myIssues - ? 'id' - : 'group'; + : statesProvider.projectStates.values) + .map((state) { return (widget.state.isArchived && - (e[key] == 'backlog' || - e[key] == 'unstarted' || - e[key] == 'started')) + (state.group == 'backlog' || + state.group == 'unstarted' || + state.group == 'started')) ? Container() : GestureDetector( onTap: () { - if (widget.state.filters.states.contains(e['id'])) { - widget.state.filters.states.remove(e['id']); + if (widget.state.filters.states.contains(state.group)) { + widget.state.filters.states.remove(state.group); } else { - widget.state.filters.states.add(e['id']); + widget.state.filters.states.add(state.group); } widget.state.setState(); }, child: RectangularChip( ref: ref, icon: SvgPicture.asset( - e[key] == 'backlog' + state.group == 'backlog' ? 'assets/svg_images/circle.svg' - : e[key] == 'cancelled' + : state.group == 'cancelled' ? 'assets/svg_images/cancelled.svg' - : e[key] == 'started' + : state.group == 'started' ? 'assets/svg_images/in_progress.svg' - : e[key] == 'completed' + : state.group == 'completed' ? 'assets/svg_images/done.svg' : 'assets/svg_images/unstarted.svg', colorFilter: ColorFilter.mode( - widget.state.filters.states.contains(e['id']) + widget.state.filters.states.contains(state.group) ? (Colors.white) - : e['color'].toString().toColor(), + : state.color.toColor(), BlendMode.srcIn), height: 20, width: 20, ), - text: e['name'], - color: widget.state.filters.states.contains(e['id']) + text: state.name, + color: widget.state.filters.states.contains(state.group) ? themeProvider.themeManager.primaryColour : themeProvider .themeManager.secondaryBackgroundDefaultColor, - selected: widget.state.filters.states.contains(e['id']), + selected: widget.state.filters.states.contains(state.group), ), ); }).toList()), diff --git a/lib/bottom_sheets/global_search_sheet.dart b/lib/bottom-sheets/global_search_sheet.dart similarity index 96% rename from lib/bottom_sheets/global_search_sheet.dart rename to lib/bottom-sheets/global_search_sheet.dart index 7578f706..0005d4bb 100644 --- a/lib/bottom_sheets/global_search_sheet.dart +++ b/lib/bottom-sheets/global_search_sheet.dart @@ -2,34 +2,33 @@ import 'dart:async'; import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:loading_indicator/loading_indicator.dart'; import 'package:plane/config/const.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/Import%20&%20Export/import_export.dart'; -import 'package:plane/screens/MainScreens/Profile/WorkpsaceSettings/members.dart'; -import 'package:plane/screens/MainScreens/Profile/WorkpsaceSettings/workspace_general.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/create_cycle.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ModulesTab/create_module.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views_detail.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/project_detail.dart'; -import 'package:plane/screens/MainScreens/Projects/create_project_screen.dart'; +import 'package:plane/screens/import-export/import_export.dart'; +import 'package:plane/screens/profile/workpsace-settings/members.dart'; +import 'package:plane/screens/profile/workpsace-settings/workspace_general.dart'; +import 'package:plane/screens/project/create_project_screen.dart'; +import 'package:plane/screens/project/cycles/create_cycle.dart'; +import 'package:plane/screens/project/cycles/cycle-detail/cycle_issues_page.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; +import 'package:plane/screens/project/modules/create_module.dart'; +import 'package:plane/screens/project/modules/module-detail/module_issues_page.dart'; +import 'package:plane/screens/project/project_detail.dart'; +import 'package:plane/screens/project/views/views_detail.dart'; import 'package:plane/screens/create_view_screen.dart'; import 'package:plane/screens/integrations.dart'; -import 'package:plane/screens/on_boarding/auth/join_workspaces.dart'; +import 'package:plane/screens/onboarding/auth/join_workspaces.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class GlobalSearchSheet extends ConsumerStatefulWidget { const GlobalSearchSheet({super.key}); @@ -449,7 +448,7 @@ class _GlobalSearchSheetState extends ConsumerState { 'title': 'Imports & Exports', 'screen': () => { Navigator.of(context).push( - MaterialPageRoute(builder: (context) => const ImportEport())) + MaterialPageRoute(builder: (context) => const ImportExport())) }, 'icon': 'assets/images/global_search_icons/imports&exports.png' }, @@ -1011,14 +1010,13 @@ class _GlobalSearchSheetState extends ConsumerState { Navigator.push( Const.globalKey.currentContext!, MaterialPageRoute( - builder: (context) => CycleDetail( + builder: (context) => ModuleDetail( moduleId: globalSearchProvider.data!.modules[index].id, moduleName: globalSearchProvider .data!.modules[index].name, projId: globalSearchProvider .data!.modules[index].projectId, - fromModule: true, ), ), ); @@ -1038,13 +1036,13 @@ class _GlobalSearchSheetState extends ConsumerState { width: 10, ), SizedBox( - width: width * 0.8, - child: CustomText( - globalSearchProvider - .data!.modules[index].name, - type: FontStyle.Medium, - maxLines: 2, - )), + width: width * 0.8, + child: CustomText( + globalSearchProvider.data!.modules[index].name, + type: FontStyle.Medium, + maxLines: 2, + ), + ), ], ), ), diff --git a/lib/bottom_sheets/goto_plane_web_notifier_sheet.dart b/lib/bottom-sheets/goto_plane_web_notifier_sheet.dart similarity index 100% rename from lib/bottom_sheets/goto_plane_web_notifier_sheet.dart rename to lib/bottom-sheets/goto_plane_web_notifier_sheet.dart diff --git a/lib/bottom_sheets/issue_detail_cycles_sheet.dart b/lib/bottom-sheets/issue_detail_cycles_sheet.dart similarity index 99% rename from lib/bottom_sheets/issue_detail_cycles_sheet.dart rename to lib/bottom-sheets/issue_detail_cycles_sheet.dart index 452dccd4..272e7e37 100644 --- a/lib/bottom_sheets/issue_detail_cycles_sheet.dart +++ b/lib/bottom-sheets/issue_detail_cycles_sheet.dart @@ -110,6 +110,7 @@ class _IssueDetailCyclesListState extends ConsumerState { slug: workspaceProvider.selectedWorkspace.workspaceSlug, projectId: projectProvider.currentProject['id'], + ref: ref ); issuesProvider.filterIssues( slug: workspaceProvider diff --git a/lib/bottom_sheets/issue_detail_modules_list.dart b/lib/bottom-sheets/issue_detail_modules_list.dart similarity index 99% rename from lib/bottom_sheets/issue_detail_modules_list.dart rename to lib/bottom-sheets/issue_detail_modules_list.dart index a15ed8e2..92f2be9a 100644 --- a/lib/bottom_sheets/issue_detail_modules_list.dart +++ b/lib/bottom-sheets/issue_detail_modules_list.dart @@ -101,6 +101,7 @@ class _IssueDetailMoudlesListState slug: workspaceProvider.selectedWorkspace.workspaceSlug, projectId: projectProvider.currentProject['id'], + ref: ref ); issuesProvider.filterIssues( slug: workspaceProvider diff --git a/lib/bottom_sheets/issues_list_sheet.dart b/lib/bottom-sheets/issues_list_sheet.dart similarity index 99% rename from lib/bottom_sheets/issues_list_sheet.dart rename to lib/bottom-sheets/issues_list_sheet.dart index cb6d1031..9a021860 100644 --- a/lib/bottom_sheets/issues_list_sheet.dart +++ b/lib/bottom-sheets/issues_list_sheet.dart @@ -490,6 +490,7 @@ class _IssuesListSheetState extends ConsumerState { .watch(ProviderList .projectProvider) .currentProject['id'], + ref: ref ); issuesProvider.filterIssues( slug: ref @@ -536,6 +537,7 @@ class _IssuesListSheetState extends ConsumerState { .read(ProviderList .projectProvider) .currentProject['id'], + ref: ref ); issuesProvider.filterIssues( slug: ref diff --git a/lib/bottom_sheets/label_sheet.dart b/lib/bottom-sheets/label_sheet.dart similarity index 91% rename from lib/bottom_sheets/label_sheet.dart rename to lib/bottom-sheets/label_sheet.dart index 8d9d6573..7fc22250 100644 --- a/lib/bottom_sheets/label_sheet.dart +++ b/lib/bottom-sheets/label_sheet.dart @@ -41,7 +41,7 @@ class _LabelSheetState extends ConsumerState { @override Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); + final labelProvider = ref.watch(ProviderList.labelProvider); final pageProvider = ref.watch(ProviderList.pageProvider); final workspaceProvider = ref.watch(ProviderList.workspaceProvider); final projectProvider = ref.watch(ProviderList.projectProvider); @@ -84,16 +84,17 @@ class _LabelSheetState extends ConsumerState { ), Container(height: 20), Wrap( - children: issuesProvider.labels.map((label) { - return search.text.toLowerCase().contains( - label["name"].toString().toLowerCase()) || + children: labelProvider.projectLabels.values.map((label) { + return search.text + .toLowerCase() + .contains(label.name.toLowerCase()) || search.text.trim().isEmpty ? GestureDetector( onTap: () { - if (selectedLabels.contains(label["id"])) { - selectedLabels.remove(label["id"]); + if (selectedLabels.contains(label.id)) { + selectedLabels.remove(label.id); } else { - selectedLabels.add(label["id"]); + selectedLabels.add(label.id); } setState(() {}); }, @@ -103,7 +104,7 @@ class _LabelSheetState extends ConsumerState { padding: const EdgeInsets.symmetric( horizontal: 15, vertical: 10), decoration: BoxDecoration( - color: selectedLabels.contains(label["id"]) + color: selectedLabels.contains(label.id) ? themeProvider.themeManager.primaryColour : (themeProvider.themeManager .primaryBackgroundDefaultColor), @@ -118,16 +119,15 @@ class _LabelSheetState extends ConsumerState { children: [ CircleAvatar( radius: 6, - backgroundColor: - label["color"].toString().toColor()), + backgroundColor: label.color.toColor()), const SizedBox( width: 10, ), CustomText( - label["name"], + label.name, type: FontStyle.Small, overrride: true, - color: selectedLabels.contains(label["id"]) + color: selectedLabels.contains(label.id) ? Colors.white : (themeProvider .themeManager.primaryTextColor), diff --git a/lib/bottom_sheets/lead_sheet.dart b/lib/bottom-sheets/lead_sheet.dart similarity index 100% rename from lib/bottom_sheets/lead_sheet.dart rename to lib/bottom-sheets/lead_sheet.dart diff --git a/lib/bottom_sheets/member_status.dart b/lib/bottom-sheets/member_status.dart similarity index 100% rename from lib/bottom_sheets/member_status.dart rename to lib/bottom-sheets/member_status.dart diff --git a/lib/bottom_sheets/notification_filter_sheet.dart b/lib/bottom-sheets/notification_filter_sheet.dart similarity index 97% rename from lib/bottom_sheets/notification_filter_sheet.dart rename to lib/bottom-sheets/notification_filter_sheet.dart index d582ad5a..2261a602 100644 --- a/lib/bottom_sheets/notification_filter_sheet.dart +++ b/lib/bottom-sheets/notification_filter_sheet.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Notification/extra_notification.dart'; +import 'package:plane/screens/notifications/extra_notification.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_divider.dart'; import 'package:plane/widgets/custom_text.dart'; diff --git a/lib/bottom_sheets/notification_more_options_sheet.dart b/lib/bottom-sheets/notification_more_options_sheet.dart similarity index 100% rename from lib/bottom_sheets/notification_more_options_sheet.dart rename to lib/bottom-sheets/notification_more_options_sheet.dart diff --git a/lib/bottom_sheets/page_filter_sheet.dart b/lib/bottom-sheets/page_filter_sheet.dart similarity index 100% rename from lib/bottom_sheets/page_filter_sheet.dart rename to lib/bottom-sheets/page_filter_sheet.dart diff --git a/lib/bottom_sheets/permission_role_sheet.dart b/lib/bottom-sheets/permission_role_sheet.dart similarity index 100% rename from lib/bottom_sheets/permission_role_sheet.dart rename to lib/bottom-sheets/permission_role_sheet.dart diff --git a/lib/bottom_sheets/project_invite_memebers_sheet.dart b/lib/bottom-sheets/project_invite_memebers_sheet.dart similarity index 96% rename from lib/bottom_sheets/project_invite_memebers_sheet.dart rename to lib/bottom-sheets/project_invite_memebers_sheet.dart index c3cbda0e..49531eb2 100644 --- a/lib/bottom_sheets/project_invite_memebers_sheet.dart +++ b/lib/bottom-sheets/project_invite_memebers_sheet.dart @@ -2,8 +2,8 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/member_status.dart'; -import 'package:plane/bottom_sheets/select_emails.dart'; +import 'package:plane/bottom-sheets/member_status.dart'; +import 'package:plane/bottom-sheets/select_emails.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; @@ -47,6 +47,7 @@ class _ProjectInviteMembersSheetState final themeProvider = ref.watch(ProviderList.themeProvider); final workspaceProvider = ref.watch(ProviderList.workspaceProvider); final projectProvider = ref.watch(ProviderList.projectProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); final BuildContext mainBuildContext = context; return LoadingWidget( loading: projectProvider.projectInvitationState == StateEnum.loading, @@ -333,7 +334,8 @@ class _ProjectInviteMembersSheetState projectProvider.projectDetailModel!.name, 'INVITED_PROJECT_MEMBER': emailController.text }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); //show success snackbar CustomToast.showToast(mainBuildContext, @@ -341,10 +343,10 @@ class _ProjectInviteMembersSheetState toastType: ToastType.success); Navigator.pop(mainBuildContext); projectProvider.getProjectMembers( - slug: workspaceProvider - .selectedWorkspace.workspaceSlug, - projId: - projectProvider.projectDetailModel!.id!); + slug: workspaceProvider + .selectedWorkspace.workspaceSlug, + projId: projectProvider.projectDetailModel!.id!, + ); } else { CustomToast.showToast( mainBuildContext, diff --git a/lib/bottom_sheets/project_lead_assignee_sheet.dart b/lib/bottom-sheets/project_lead_assignee_sheet.dart similarity index 100% rename from lib/bottom_sheets/project_lead_assignee_sheet.dart rename to lib/bottom-sheets/project_lead_assignee_sheet.dart diff --git a/lib/bottom_sheets/project_select_cover_image.dart b/lib/bottom-sheets/project_select_cover_image.dart similarity index 100% rename from lib/bottom_sheets/project_select_cover_image.dart rename to lib/bottom-sheets/project_select_cover_image.dart diff --git a/lib/bottom_sheets/role_sheet.dart b/lib/bottom-sheets/role_sheet.dart similarity index 100% rename from lib/bottom_sheets/role_sheet.dart rename to lib/bottom-sheets/role_sheet.dart diff --git a/lib/bottom_sheets/selectProjectSheet.dart b/lib/bottom-sheets/selectProjectSheet.dart similarity index 100% rename from lib/bottom_sheets/selectProjectSheet.dart rename to lib/bottom-sheets/selectProjectSheet.dart diff --git a/lib/bottom_sheets/select_automation_state.dart b/lib/bottom-sheets/select_automation_state.dart similarity index 100% rename from lib/bottom_sheets/select_automation_state.dart rename to lib/bottom-sheets/select_automation_state.dart diff --git a/lib/bottom_sheets/select_cycle_sheet.dart b/lib/bottom-sheets/select_cycle_sheet.dart similarity index 100% rename from lib/bottom_sheets/select_cycle_sheet.dart rename to lib/bottom-sheets/select_cycle_sheet.dart diff --git a/lib/bottom_sheets/select_emails.dart b/lib/bottom-sheets/select_emails.dart similarity index 100% rename from lib/bottom_sheets/select_emails.dart rename to lib/bottom-sheets/select_emails.dart diff --git a/lib/bottom_sheets/select_estimate.dart b/lib/bottom-sheets/select_estimate.dart similarity index 100% rename from lib/bottom_sheets/select_estimate.dart rename to lib/bottom-sheets/select_estimate.dart diff --git a/lib/bottom_sheets/select_issue_labels.dart b/lib/bottom-sheets/select_issue_labels.dart similarity index 87% rename from lib/bottom_sheets/select_issue_labels.dart rename to lib/bottom-sheets/select_issue_labels.dart index 23b2ab63..d52accd1 100644 --- a/lib/bottom_sheets/select_issue_labels.dart +++ b/lib/bottom-sheets/select_issue_labels.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:loading_indicator/loading_indicator.dart'; +import 'package:plane/models/Project/Label/label.model.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/custom_toast.dart'; @@ -50,6 +51,8 @@ class _SelectIssueLabelsState extends ConsumerState { Widget build(BuildContext context) { final issuesProvider = ref.watch(ProviderList.issuesProvider); final themeProvider = ref.watch(ProviderList.themeProvider); + final labelProvider = ref.watch(ProviderList.labelProvider); + final labelNotifier = ref.read(ProviderList.labelProvider.notifier); return Container( padding: EdgeInsets.only(bottom: bottomSheetConstBottomPadding), height: MediaQuery.of(context).size.height * 0.8, @@ -86,22 +89,25 @@ class _SelectIssueLabelsState extends ConsumerState { ), Container(height: 15), Expanded( - child: issuesProvider.labels.isNotEmpty + child: labelProvider.projectLabels.isNotEmpty ? ListView.builder( - itemCount: issuesProvider.labels.length, + itemCount: labelProvider.projectLabels.length, shrinkWrap: true, itemBuilder: (context, index) { + final labelId = labelProvider.projectLabels.keys + .elementAt(index); + final label = labelProvider.projectLabels.values + .elementAt(index); return InkWell( onTap: () { // setState(() { if (widget.createIssue) { if (issuesProvider.selectedLabels - .contains(issuesProvider.labels[index])) { + .contains(labelId)) { issuesProvider.selectedLabels - .remove(issuesProvider.labels[index]); + .remove(labelId); } else { - issuesProvider.selectedLabels - .add(issuesProvider.labels[index]); + issuesProvider.selectedLabels.add(labelId); } final prov = ref.watch(ProviderList.issuesProvider); @@ -112,43 +118,36 @@ class _SelectIssueLabelsState extends ConsumerState { prov.setsState(); } else { setState(() { - if (issueDetailsLabels.contains( - issuesProvider.labels[index]['id'])) { - issueDetailsLabels.remove( - issuesProvider.labels[index]['id']); + if (issueDetailsLabels.contains(labelId)) { + issueDetailsLabels.remove(labelId); } else { - issueDetailsLabels.add( - issuesProvider.labels[index]['id']); + issueDetailsLabels.add(labelId); } }); } - // }); }, child: Container( padding: const EdgeInsets.symmetric( vertical: 10, ), - margin: - issuesProvider.labels.length == index + 1 - ? const EdgeInsets.only(bottom: 35) - : null, + margin: labelProvider.projectLabels.length == + index + 1 + ? const EdgeInsets.only(bottom: 35) + : null, child: Column( children: [ Row( children: [ CircleAvatar( radius: 8, - backgroundColor: issuesProvider - .labels[index]['color'] - .toString() - .toColor(), + backgroundColor: + label.color.toColor(), ), Container(width: 10), SizedBox( width: width * 0.7, child: CustomText( - issuesProvider.labels[index]['name'] - .toString(), + label.name.toString(), maxLines: 1, overflow: TextOverflow.ellipsis, type: FontStyle.Medium, @@ -160,9 +159,9 @@ class _SelectIssueLabelsState extends ConsumerState { const Spacer(), widget.createIssue ? createIssueSelectedIconsWidget( - issuesProvider.labels[index]) + label) : issueDetailSelectedIconsWidget( - index), + label), const SizedBox(width: 10) ], ), @@ -316,7 +315,7 @@ class _SelectIssueLabelsState extends ConsumerState { ], ), ), - issuesProvider.labelState == StateEnum.loading + labelProvider.labelState == StateEnum.loading ? Center( child: Container( decoration: BoxDecoration( @@ -369,23 +368,12 @@ class _SelectIssueLabelsState extends ConsumerState { message: 'Color is not valid', toastType: ToastType.failure); } else { - await issuesProvider.issueLabels( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: widget.createIssue - ? ref - .read(ProviderList.issuesProvider) - .createIssueProjectData['id'] - : ref - .read(ProviderList.projectProvider) - .currentProject['id'], - data: { - "name": labelContrtoller.text, - "color": "#${colorController.text}" - }, - ref: ref); + await labelNotifier.updateLabel( + { + 'name': labelContrtoller.text, + 'color': colorController.text, + }, + ); setState(() { createNew = false; colorController.clear(); @@ -439,9 +427,9 @@ class _SelectIssueLabelsState extends ConsumerState { ); } - Widget createIssueSelectedIconsWidget(Map data) { + Widget createIssueSelectedIconsWidget(LabelModel label) { var issuesProvider = ref.watch(ProviderList.issuesProvider); - return issuesProvider.selectedLabels.contains(data) + return issuesProvider.selectedLabels.contains(label.id) ? const Icon( Icons.done, color: Color.fromRGBO(8, 171, 34, 1), @@ -449,9 +437,8 @@ class _SelectIssueLabelsState extends ConsumerState { : const SizedBox.shrink(); } - Widget issueDetailSelectedIconsWidget(int idx) { - final issuesProvider = ref.watch(ProviderList.issuesProvider); - return issueDetailsLabels.contains(issuesProvider.labels[idx]['id']) + Widget issueDetailSelectedIconsWidget(LabelModel label) { + return issueDetailsLabels.contains(label.id) ? const Icon( Icons.done, color: Color.fromRGBO(8, 171, 34, 1), diff --git a/lib/bottom_sheets/select_month.dart b/lib/bottom-sheets/select_month.dart similarity index 100% rename from lib/bottom_sheets/select_month.dart rename to lib/bottom-sheets/select_month.dart diff --git a/lib/bottom_sheets/select_month_sheet.dart b/lib/bottom-sheets/select_month_sheet.dart similarity index 100% rename from lib/bottom_sheets/select_month_sheet.dart rename to lib/bottom-sheets/select_month_sheet.dart diff --git a/lib/bottom_sheets/select_priority.dart b/lib/bottom-sheets/select_priority.dart similarity index 100% rename from lib/bottom_sheets/select_priority.dart rename to lib/bottom-sheets/select_priority.dart diff --git a/lib/bottom_sheets/select_project_members.dart b/lib/bottom-sheets/select_project_members.dart similarity index 81% rename from lib/bottom_sheets/select_project_members.dart rename to lib/bottom-sheets/select_project_members.dart index d72191c2..edc24123 100644 --- a/lib/bottom_sheets/select_project_members.dart +++ b/lib/bottom-sheets/select_project_members.dart @@ -26,18 +26,19 @@ class _SelectProjectMembersState extends ConsumerState { @override void initState() { - if (ref.read(ProviderList.issuesProvider).members.isEmpty || + if (ref.read(ProviderList.projectProvider).projectMembers.isEmpty || widget.createIssue) { - ref.read(ProviderList.issuesProvider).getProjectMembers( + ref.read(ProviderList.projectProvider).getProjectMembers( slug: ref .read(ProviderList.workspaceProvider) .selectedWorkspace .workspaceSlug, - projID: widget.createIssue + projId: widget.createIssue ? ref .read(ProviderList.issuesProvider) .createIssueProjectData['id'] - : ref.read(ProviderList.projectProvider).currentProject['id']); + : ref.read(ProviderList.projectProvider).currentProject['id'], + ); } selectedMembers = ref.read(ProviderList.issuesProvider).createIssuedata['members'] ?? {}; @@ -60,6 +61,7 @@ class _SelectProjectMembersState extends ConsumerState { final issuesProvider = ref.watch(ProviderList.issuesProvider); final issueProvider = ref.watch(ProviderList.issueProvider); final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); return WillPopScope( onWillPop: () async { issuesProvider.createIssuedata['members'] = @@ -106,7 +108,7 @@ class _SelectProjectMembersState extends ConsumerState { child: ListView( children: [ ListView.builder( - itemCount: issuesProvider.members.length, + itemCount: projectProvider.projectMembers.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, @@ -115,37 +117,42 @@ class _SelectProjectMembersState extends ConsumerState { onTap: () { if (widget.createIssue) { setState(() { - if (selectedMembers[ - issuesProvider.members[index] - ['member']['id']] == + if (selectedMembers[projectProvider + .projectMembers[index] + ['member']['id']] == null) { - selectedMembers[issuesProvider - .members[index]['member']['id']] = { - "avatar": - issuesProvider.members[index] - ['member']['avatar'], - "display_name": - issuesProvider.members[index] - ['member']['display_name'], - "id": issuesProvider.members[index] + selectedMembers[projectProvider + .projectMembers[index]['member'] + ['id']] = { + "avatar": projectProvider + .projectMembers[index] + ['member']['avatar'], + "display_name": projectProvider + .projectMembers[index] + ['member']['display_name'], + "id": projectProvider + .projectMembers[index] ['member']['id'] }; } else { - selectedMembers.remove(issuesProvider - .members[index]['member']['id']); + selectedMembers.remove(projectProvider + .projectMembers[index]['member'] + ['id']); } }); } else { setState(() { if (issueDetailSelectedMembers.contains( - issuesProvider.members[index] + projectProvider.projectMembers[index] ['member']['id'])) { issueDetailSelectedMembers.remove( - issuesProvider.members[index] + projectProvider + .projectMembers[index] ['member']['id']); } else { issueDetailSelectedMembers.add( - issuesProvider.members[index] + projectProvider + .projectMembers[index] ['member']['id']); } }); @@ -166,14 +173,15 @@ class _SelectProjectMembersState extends ConsumerState { width: 30, child: MemberLogoWidget( padding: EdgeInsets.zero, - imageUrl: - issuesProvider.members[index] - ['member']['avatar'], + imageUrl: projectProvider + .projectMembers[index] + ['member']['avatar'], colorForErrorWidget: const Color.fromRGBO( 55, 65, 81, 1), memberNameFirstLetterForErrorWidget: - issuesProvider.members[index] + projectProvider + .projectMembers[index] ['member'] ['display_name'][0] .toString() @@ -186,7 +194,8 @@ class _SelectProjectMembersState extends ConsumerState { SizedBox( width: width * 0.7, child: CustomText( - issuesProvider.members[index] + projectProvider + .projectMembers[index] ['member']['display_name'], type: FontStyle.Medium, fontWeight: FontWeightt.Regular, @@ -222,7 +231,7 @@ class _SelectProjectMembersState extends ConsumerState { ], ), ), - issuesProvider.membersState == StateEnum.loading + projectProvider.projectMembersState == StateEnum.loading ? Container( alignment: Alignment.center, color: themeProvider @@ -292,8 +301,10 @@ class _SelectProjectMembersState extends ConsumerState { } Widget createIsseuSelectedMembersWidget(int idx) { - final issuesProvider = ref.watch(ProviderList.issuesProvider); - return selectedMembers[issuesProvider.members[idx]['member']['id']] != null + final projectProvider = ref.watch(ProviderList.projectProvider); + return selectedMembers[projectProvider.projectMembers[idx]['member'] + ['id']] != + null ? const Icon( Icons.done, color: Color.fromRGBO(8, 171, 34, 1), @@ -302,9 +313,9 @@ class _SelectProjectMembersState extends ConsumerState { } Widget issueDetailSelectedMembersWidget(int idx) { - final issuesProvider = ref.read(ProviderList.issuesProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); return issueDetailSelectedMembers - .contains(issuesProvider.members[idx]['member']['id']) + .contains(projectProvider.projectMembers[idx]['member']['id']) ? const Icon( Icons.done, color: Color.fromRGBO(8, 171, 34, 1), diff --git a/lib/bottom_sheets/select_states.dart b/lib/bottom-sheets/select_states.dart similarity index 95% rename from lib/bottom_sheets/select_states.dart rename to lib/bottom-sheets/select_states.dart index 401d08dc..9d7260fd 100644 --- a/lib/bottom_sheets/select_states.dart +++ b/lib/bottom-sheets/select_states.dart @@ -23,13 +23,15 @@ class _SelectStatesState extends ConsumerState { @override void initState() { final prov = ref.read(ProviderList.issuesProvider); - if (prov.states.isEmpty) { - prov.getStates( + final statesNotifier = ref.watch(ProviderList.statesProvider.notifier); + final statesProvider = ref.watch(ProviderList.statesProvider); + if (statesProvider.projectStates.isEmpty) { + statesNotifier.getStates( slug: ref .read(ProviderList.workspaceProvider) .selectedWorkspace .workspaceSlug, - projID: ref + projectId: ref .read(ProviderList.issuesProvider) .createIssueProjectData['id']); } @@ -42,7 +44,7 @@ class _SelectStatesState extends ConsumerState { // ? prov.states['state']['id'] // : ''; // log(prov.createIssuedata['state'].toString()); - selectedState = prov.createIssuedata['state'] ?? prov.states.keys.first; + selectedState = prov.createIssuedata['state'] ?? statesProvider.projectStates.keys.first; super.initState(); } @@ -64,6 +66,7 @@ class _SelectStatesState extends ConsumerState { final issuesProvider = ref.watch(ProviderList.issuesProvider); final issueProvider = ref.watch(ProviderList.issueProvider); final themeProvider = ref.watch(ProviderList.themeProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); return WillPopScope( onWillPop: () async { final prov = ref.read(ProviderList.issuesProvider); @@ -268,16 +271,18 @@ class _SelectStatesState extends ConsumerState { IconButton( onPressed: () { final prov = ref.read(ProviderList.issuesProvider); + final statesProvider = + ref.watch(ProviderList.statesProvider.notifier); // if (selectedState.isNotEmpty) { prov.createIssuedata['state'] = selectedState; // print('state'); // print(prov.createIssuedata['state'].toString()); - prov.getStates( + statesProvider.getStates( slug: ref .read(ProviderList.workspaceProvider) .selectedWorkspace .workspaceSlug, - projID: ref + projectId: ref .read(ProviderList.projectProvider) .currentProject['id']); prov.setsState(); @@ -290,7 +295,7 @@ class _SelectStatesState extends ConsumerState { ], ), ), - issuesProvider.statesState == StateEnum.loading + statesProvider.statesState == StateEnum.loading ? Container( alignment: Alignment.center, color: Colors.white.withOpacity(0.7), diff --git a/lib/bottom_sheets/select_workspace.dart b/lib/bottom-sheets/select_workspace.dart similarity index 99% rename from lib/bottom_sheets/select_workspace.dart rename to lib/bottom-sheets/select_workspace.dart index c900de40..50cb93fa 100644 --- a/lib/bottom_sheets/select_workspace.dart +++ b/lib/bottom-sheets/select_workspace.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/screens/on_boarding/auth/setup_workspace.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; import 'package:plane/utils/color_manager.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; diff --git a/lib/bottom_sheets/snooze_time_sheet.dart b/lib/bottom-sheets/snooze_time_sheet.dart similarity index 100% rename from lib/bottom_sheets/snooze_time_sheet.dart rename to lib/bottom-sheets/snooze_time_sheet.dart diff --git a/lib/bottom_sheets/status_sheet.dart b/lib/bottom-sheets/status_sheet.dart similarity index 100% rename from lib/bottom_sheets/status_sheet.dart rename to lib/bottom-sheets/status_sheet.dart diff --git a/lib/bottom_sheets/time_zone_selector_sheet.dart b/lib/bottom-sheets/time_zone_selector_sheet.dart similarity index 100% rename from lib/bottom_sheets/time_zone_selector_sheet.dart rename to lib/bottom-sheets/time_zone_selector_sheet.dart diff --git a/lib/bottom_sheets/type_sheet.dart b/lib/bottom-sheets/type_sheet.dart similarity index 87% rename from lib/bottom_sheets/type_sheet.dart rename to lib/bottom-sheets/type_sheet.dart index 923fb37a..6e042a5a 100644 --- a/lib/bottom_sheets/type_sheet.dart +++ b/lib/bottom-sheets/type_sheet.dart @@ -20,11 +20,11 @@ class _TypeSheetState extends ConsumerState { final dynamic prov = widget.issueCategory == IssueCategory.myIssues ? ref.read(ProviderList.myIssuesProvider) : ref.read(ProviderList.issuesProvider); - selected = prov.issues.projectView == ProjectView.kanban + selected = prov.issues.projectView == IssueLayout.kanban ? 0 - : prov.issues.projectView == ProjectView.list + : prov.issues.projectView == IssueLayout.list ? 1 - : prov.issues.projectView == ProjectView.calendar + : prov.issues.projectView == IssueLayout.calendar ? 2 : 3; super.initState(); @@ -80,13 +80,13 @@ class _TypeSheetState extends ConsumerState { .workspaceSlug; if (widget.issueCategory == IssueCategory.myIssues) { - myIssuesProv.issues.projectView = ProjectView.kanban; + myIssuesProv.issues.projectView = IssueLayout.kanban; myIssuesProv.setState(); myIssuesProv.updateMyIssueView(); } else { - prov.issues.projectView = ProjectView.kanban; + prov.issues.projectView = IssueLayout.kanban; if (widget.issueCategory == IssueCategory.issues) { - prov.tempProjectView = ProjectView.kanban; + prov.tempProjectView = IssueLayout.kanban; } if (prov.issues.groupBY == GroupBY.none) { prov.issues.groupBY = GroupBY.state; @@ -95,20 +95,20 @@ class _TypeSheetState extends ConsumerState { ref .read(ProviderList.modulesProvider) .filterModuleIssues( - slug: worspaceSlug, projectId: projID); + slug: worspaceSlug, projectId: projID, ref: ref); } else if (widget.issueCategory == IssueCategory.cycleIssues) { ref .read(ProviderList.cyclesProvider) .filterCycleIssues( - slug: worspaceSlug, projectId: projID); + slug: worspaceSlug, projectId: projID, ref: ref); } else if (widget.issueCategory == IssueCategory.issues || widget.issueCategory == IssueCategory.views) { prov.filterIssues(slug: worspaceSlug, projID: projID); } } - // prov.tempProjectView = ProjectView.kanban; + // prov.tempProjectView = IssueLayout.kanban; prov.setsState(); if (widget.issueCategory == IssueCategory.issues) { prov.updateProjectView(); @@ -137,12 +137,12 @@ class _TypeSheetState extends ConsumerState { if (widget.issueCategory == IssueCategory.myIssues) { myIssuesProv.issues.projectView = - ProjectView.kanban; + IssueLayout.kanban; myIssuesProv.setState(); myIssuesProv.updateMyIssueView(); } else { - prov.issues.projectView = ProjectView.kanban; - prov.tempProjectView = ProjectView.kanban; + prov.issues.projectView = IssueLayout.kanban; + prov.tempProjectView = IssueLayout.kanban; prov.setsState(); if (widget.issueCategory == IssueCategory.issues) { @@ -174,13 +174,13 @@ class _TypeSheetState extends ConsumerState { child: InkWell( onTap: () { if (widget.issueCategory == IssueCategory.myIssues) { - myIssuesProv.issues.projectView = ProjectView.list; + myIssuesProv.issues.projectView = IssueLayout.list; myIssuesProv.setState(); myIssuesProv.updateMyIssueView(); } else { - prov.issues.projectView = ProjectView.list; + prov.issues.projectView = IssueLayout.list; if (widget.issueCategory == IssueCategory.issues) { - prov.tempProjectView = ProjectView.list; + prov.tempProjectView = IssueLayout.list; } prov.setsState(); @@ -211,12 +211,12 @@ class _TypeSheetState extends ConsumerState { if (widget.issueCategory == IssueCategory.myIssues) { myIssuesProv.issues.projectView = - ProjectView.list; + IssueLayout.list; myIssuesProv.setState(); myIssuesProv.updateMyIssueView(); } else { - prov.issues.projectView = ProjectView.list; - prov.tempProjectView = ProjectView.list; + prov.issues.projectView = IssueLayout.list; + prov.tempProjectView = IssueLayout.list; prov.setsState(); if (widget.issueCategory == IssueCategory.issues) { @@ -252,10 +252,10 @@ class _TypeSheetState extends ConsumerState { width: double.infinity, child: InkWell( onTap: () { - prov.issues.projectView = ProjectView.calendar; + prov.issues.projectView = IssueLayout.calendar; if (widget.issueCategory == IssueCategory.issues) { - prov.tempProjectView = ProjectView.calendar; + prov.tempProjectView = IssueLayout.calendar; } prov.setsState(); @@ -285,8 +285,8 @@ class _TypeSheetState extends ConsumerState { value: 2, onChanged: (val) { prov.issues.projectView = - ProjectView.calendar; - prov.tempProjectView = ProjectView.calendar; + IssueLayout.calendar; + prov.tempProjectView = IssueLayout.calendar; prov.setsState(); if (widget.issueCategory == @@ -322,10 +322,10 @@ class _TypeSheetState extends ConsumerState { width: double.infinity, child: InkWell( onTap: () { - prov.issues.projectView = ProjectView.spreadsheet; + prov.issues.projectView = IssueLayout.spreadsheet; if (widget.issueCategory == IssueCategory.issues) { - prov.tempProjectView = ProjectView.spreadsheet; + prov.tempProjectView = IssueLayout.spreadsheet; } prov.setsState(); @@ -355,9 +355,9 @@ class _TypeSheetState extends ConsumerState { value: 3, onChanged: (val) { prov.issues.projectView = - ProjectView.spreadsheet; + IssueLayout.spreadsheet; prov.tempProjectView = - ProjectView.spreadsheet; + IssueLayout.spreadsheet; prov.setsState(); if (widget.issueCategory == @@ -388,25 +388,25 @@ class _TypeSheetState extends ConsumerState { // ontap: () { // if (widget.issueCategory == IssueCategory.myIssues) { // if (selected == 0) { - // myIssuesProv.issues.projectView = ProjectView.kanban; + // myIssuesProv.issues.projectView = IssueLayout.kanban; // } else if (selected == 1) { - // myIssuesProv.issues.projectView = ProjectView.list; + // myIssuesProv.issues.projectView = IssueLayout.list; // } // myIssuesProv.setState(); // myIssuesProv.updateMyIssueView(); // } else { // if (selected == 0) { - // prov.issues.projectView = ProjectView.kanban; - // prov.tempProjectView = ProjectView.kanban; + // prov.issues.projectView = IssueLayout.kanban; + // prov.tempProjectView = IssueLayout.kanban; // } else if (selected == 1) { - // prov.issues.projectView = ProjectView.list; - // prov.tempProjectView = ProjectView.list; + // prov.issues.projectView = IssueLayout.list; + // prov.tempProjectView = IssueLayout.list; // } else if (selected == 2) { - // prov.issues.projectView = ProjectView.calendar; - // prov.tempProjectView = ProjectView.calendar; + // prov.issues.projectView = IssueLayout.calendar; + // prov.tempProjectView = IssueLayout.calendar; // } else if (selected == 3) { - // prov.issues.projectView = ProjectView.spreadsheet; - // prov.tempProjectView = ProjectView.spreadsheet; + // prov.issues.projectView = IssueLayout.spreadsheet; + // prov.tempProjectView = IssueLayout.spreadsheet; // } // prov.setsState(); // if (widget.issueCategory == IssueCategory.issues) { diff --git a/lib/bottom_sheets/views_and_layout_sheet.dart b/lib/bottom-sheets/views_and_layout_sheet.dart similarity index 99% rename from lib/bottom_sheets/views_and_layout_sheet.dart rename to lib/bottom-sheets/views_and_layout_sheet.dart index efa2f32e..40fbc7c5 100644 --- a/lib/bottom_sheets/views_and_layout_sheet.dart +++ b/lib/bottom-sheets/views_and_layout_sheet.dart @@ -141,11 +141,11 @@ class _ViewsAndLayoutSheetState extends ConsumerState { final dynamic prov = widget.issueCategory == IssueCategory.myIssues ? ref.read(ProviderList.myIssuesProvider) : ref.read(ProviderList.issuesProvider); - selected = prov.issues.projectView == ProjectView.kanban + selected = prov.issues.projectView == IssueLayout.kanban ? 0 - : prov.issues.projectView == ProjectView.list + : prov.issues.projectView == IssueLayout.list ? 1 - : prov.issues.projectView == ProjectView.calendar + : prov.issues.projectView == IssueLayout.calendar ? 2 : 3; @@ -298,7 +298,7 @@ class _ViewsAndLayoutSheetState extends ConsumerState { child: InkWell( onTap: () { myIssuesProvider.issues.projectView = - ProjectView.list; + IssueLayout.list; myIssuesProvider.setState(); myIssuesProvider.updateMyIssueView(); Navigator.of(context).pop(); @@ -340,7 +340,7 @@ class _ViewsAndLayoutSheetState extends ConsumerState { child: InkWell( onTap: () { myIssuesProvider.issues.projectView = - ProjectView.kanban; + IssueLayout.kanban; myIssuesProvider.setState(); myIssuesProvider.updateMyIssueView(); Navigator.of(context).pop(); @@ -636,7 +636,7 @@ class _ViewsAndLayoutSheetState extends ConsumerState { .themeManager.primaryColour), myIssuesProvider.issues.projectView == - ProjectView.list + IssueLayout.list ? RadioListTile( fillColor: groupBy == 'none' ? null @@ -980,7 +980,7 @@ class _ViewsAndLayoutSheetState extends ConsumerState { tag['name'] != 'ID' && tag['name'] != 'Assignee') && myIssuesProvider.issues.projectView == - ProjectView.list) + IssueLayout.list) ? const SizedBox() : GestureDetector( onTap: () { diff --git a/lib/bottom_sheets/views_sheet.dart b/lib/bottom-sheets/views_sheet.dart similarity index 91% rename from lib/bottom_sheets/views_sheet.dart rename to lib/bottom-sheets/views_sheet.dart index 6eeae719..7281a1ef 100644 --- a/lib/bottom_sheets/views_sheet.dart +++ b/lib/bottom-sheets/views_sheet.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/utils/custom_toast.dart'; @@ -17,12 +15,14 @@ class ViewsSheet extends ConsumerStatefulWidget { this.cycleId, this.fromView = false, this.isArchived = false, + this.moduleId, super.key, }); final Enum issueCategory; - final ProjectView projectView; + final IssueLayout projectView; final bool fromView; final String? cycleId; + final String? moduleId; final bool isArchived; @override @@ -172,12 +172,12 @@ class _ViewsSheetState extends ConsumerState { Column( children: [ const SizedBox(height: 10), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? const SizedBox( height: 40, ) : Container(), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? CustomExpansionTile( title: 'Group by', child: Wrap( @@ -376,7 +376,7 @@ class _ViewsSheetState extends ConsumerState { ListTileControlAffinity.leading, activeColor: themeProvider .themeManager.primaryColour), - widget.projectView == ProjectView.list + widget.projectView == IssueLayout.list ? RadioListTile( fillColor: groupBy == 'none' ? null @@ -414,12 +414,12 @@ class _ViewsSheetState extends ConsumerState { ], ), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? customHorizontalLine() : Container(), //expansion tile for order by having two checkboxes last created and last updated - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? CustomExpansionTile( title: 'Order by', child: Wrap( @@ -559,12 +559,12 @@ class _ViewsSheetState extends ConsumerState { ) : Container(), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? customHorizontalLine() : Container(), //expansion tile for issue type having three checkboxes all issues, active issues and backlog issues - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? CustomExpansionTile( title: 'Issue type', child: Wrap( @@ -649,11 +649,11 @@ class _ViewsSheetState extends ConsumerState { ) : Container(), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? customHorizontalLine() : Container(), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? InkWell( onTap: () { setState(() { @@ -702,10 +702,10 @@ class _ViewsSheetState extends ConsumerState { ) : Container(), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? customHorizontalLine() : Container(), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? InkWell( onTap: () { setState(() { @@ -753,13 +753,13 @@ class _ViewsSheetState extends ConsumerState { ), ) : Container(), - issueProvider.issues.projectView != ProjectView.spreadsheet + issueProvider.issues.projectView != IssueLayout.spreadsheet ? Container( height: 20, ) : Container(), - issueProvider.issues.projectView == ProjectView.spreadsheet + issueProvider.issues.projectView == IssueLayout.spreadsheet ? Container( height: 45, ) @@ -780,14 +780,13 @@ class _ViewsSheetState extends ConsumerState { issueProvider.getProjectView(reset: true).then((value) { if (widget.issueCategory == IssueCategory.cycleIssues) { cyclesProvider.filterCycleIssues( - slug: slug, - projectId: projID, - ); + slug: slug, projectId: projID, ref: ref); } else if (widget.issueCategory == IssueCategory.moduleIssues) { modulesProvider.filterModuleIssues( slug: slug, projectId: projID, + ref: ref, ); } else { issueProvider.filterIssues( @@ -827,15 +826,11 @@ class _ViewsSheetState extends ConsumerState { if (widget.issueCategory == IssueCategory.cycleIssues) { cyclesProvider.filterCycleIssues( - slug: slug, - projectId: projID, - ); + slug: slug, projectId: projID, ref: ref); } else if (widget.issueCategory == IssueCategory.moduleIssues) { modulesProvider.filterModuleIssues( - slug: slug, - projectId: projID, - ); + slug: slug, projectId: projID, ref: ref); } else { issueProvider.filterIssues( fromViews: false, @@ -875,18 +870,18 @@ class _ViewsSheetState extends ConsumerState { : (((tag['name'] == 'Created on' || tag['name'] == 'Updated on') && issueProvider.issues.projectView != - ProjectView.spreadsheet) || + IssueLayout.spreadsheet) || ((tag['name'] == 'ID' || tag['name'] == 'Attachment Count' || tag['name'] == 'Link' || tag['name'] == 'Sub Issue Count') && issueProvider.issues.projectView == - ProjectView.spreadsheet) || + IssueLayout.spreadsheet) || ((tag['name'] != 'Priority' && tag['name'] != 'ID' && tag['name'] != 'Assignee') && issueProvider.issues.projectView == - ProjectView.list)) + IssueLayout.list)) ? const SizedBox() : GestureDetector( onTap: () { @@ -959,14 +954,7 @@ class _ViewsSheetState extends ConsumerState { }); myIssuesProvider.filterIssues(); } - } else if (issueProvider.issues.groupBY != - Issues.toGroupBY(groupBy) || - issueProvider.issues.orderBY != - Issues.toOrderBY(orderBy) || - issueProvider.issues.issueType != - Issues.toIssueType(issueType) || - issueProvider.showEmptyStates != showEmptyStates || - issueProvider.issues.showSubIssues != showSubIssues) { + } else { setState(() { issueProvider.issues.orderBY = Issues.toOrderBY(orderBy); @@ -978,39 +966,24 @@ class _ViewsSheetState extends ConsumerState { issueProvider.issues.showSubIssues = showSubIssues; }); if (widget.issueCategory == IssueCategory.cycleIssues) { - cyclesProvider.filterCycleIssues( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - ); + setState(() { + cyclesProvider.issues.groupBY = + Issues.toGroupBY(groupBy); + cyclesProvider.issues.orderBY = + Issues.toOrderBY(orderBy); + }); + cyclesProvider.applyCycleIssuesView(ref: ref); } else if (widget.issueCategory == IssueCategory.moduleIssues) { - modulesProvider.filterModuleIssues( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - ); + setState(() { + modulesProvider.issues.groupBY = + Issues.toGroupBY(groupBy); + modulesProvider.issues.orderBY = + Issues.toOrderBY(orderBy); + }); + modulesProvider.applyModuleIssuesView(ref: ref); } else { - issueProvider.filterIssues( - fromViews: widget.fromView, - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - issueCategory: IssueCategory.issues, - isArchived: widget.isArchived, - ); + issueProvider.applyIssueView(); } } @@ -1039,36 +1012,35 @@ class _ViewsSheetState extends ConsumerState { myIssuesProvider.updateMyIssueView(); } else { if (widget.issueCategory == IssueCategory.cycleIssues) { - issueProvider.updateIssueProperties( - properties: properties, - issueCategory: widget.issueCategory, - ); - cyclesProvider.issues.displayProperties = properties; + // issueProvider.updateIssueProperties( + // properties: properties, + // issueCategory: widget.issueCategory, + // ); + // cyclesProvider.issues.displayProperties = properties; cyclesProvider.showEmptyStates = showEmptyStates; + cyclesProvider.updateCycleView(); } else if (widget.issueCategory == IssueCategory.moduleIssues) { - issueProvider.updateIssueProperties( - properties: properties, - issueCategory: widget.issueCategory, - ); - modulesProvider.issues.displayProperties = properties; + // issueProvider.updateIssueProperties( + // properties: properties, + // issueCategory: widget.issueCategory, + // ); + // modulesProvider.issues.displayProperties = properties; modulesProvider.showEmptyStates = showEmptyStates; + modulesProvider.updateModuleView(); } else { - issueProvider.updateIssueProperties( - properties: properties, - issueCategory: widget.issueCategory, - ); - issueProvider.issues.displayProperties = properties; - issueProvider.showEmptyStates = showEmptyStates; + // issueProvider.updateIssueProperties( + // properties: properties, + // issueCategory: widget.issueCategory, + // ); + // issueProvider.issues.displayProperties = properties; + // issueProvider.showEmptyStates = showEmptyStates; } if (widget.issueCategory == IssueCategory.issues) { issueProvider.updateProjectView(); } } - - log(displayProperties.toString()); - Navigator.of(context).pop(); }, textColor: Colors.white, diff --git a/lib/bottom_sheets/whats_new_sheet.dart b/lib/bottom-sheets/whats_new_sheet.dart similarity index 100% rename from lib/bottom_sheets/whats_new_sheet.dart rename to lib/bottom-sheets/whats_new_sheet.dart diff --git a/lib/bottom_sheets/workspace_logo.dart b/lib/bottom-sheets/workspace_logo.dart similarity index 100% rename from lib/bottom_sheets/workspace_logo.dart rename to lib/bottom-sheets/workspace_logo.dart diff --git a/lib/config/apis.dart b/lib/config/apis.dart index 71cc659b..0761e92b 100644 --- a/lib/config/apis.dart +++ b/lib/config/apis.dart @@ -8,10 +8,9 @@ class APIs { static String generateMagicLink = '/api/magic-generate/'; static String googleAuth = '$baseApi/api/social-auth/'; static String magicValidate = '/api/magic-sign-in/'; - static String profile = '/api/users/me/'; + static String profile = '$baseApi/api/users/me/'; static String listWorkspaceInvitaion = - '/api/users/me/invitations/workspaces/'; - static String joinWorkspace = '$baseApi/api/users/me/invitations/workspaces/'; + '$baseApi/api/users/me/workspaces/invitations/'; static String createWorkspace = '$baseApi/api/workspaces/'; static String inviteToWorkspace = '$baseApi/api/workspaces/\$SLUG/invite/'; static String inviteToProject = @@ -35,7 +34,7 @@ class APIs { static String states = '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/states/'; static String orderByGroupByTypeIssues = - '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/issues/?order_by=\$ORDERBY&group_by=\$GROUPBY&type=\$TYPE'; + '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/issues/?type=\$TYPE'; static String orderByGroupByTypeArchivedIssues = '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/archived-issues/?order_by=\$ORDERBY&group_by=\$GROUPBY&type=\$TYPE'; static String orderByGroupByIssues = @@ -44,6 +43,10 @@ class APIs { '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/members/'; static String userIssueView = '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/project-members/me'; + static String cycleIssueView = + '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/cycles/\$CYCLEID/user-properties/'; + static String moduleIssueView = + '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/modules/\$MODULEID/user-properties/'; static String myIssuesView = '$baseApi/api/workspaces/\$SLUG/workspace-members/me/'; static String updateMyIssuesView = @@ -66,8 +69,8 @@ class APIs { '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/issue-labels/'; static String projectViews = '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/project-views/'; - static String issueProperties = - '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/issue-properties/'; + static String issueDisplayProperties = + '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/user-properties/'; static String issueDetails = '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/issues/\$ISSUEID/'; static String subIssues = @@ -82,9 +85,9 @@ class APIs { static String toggleFavoriteCycle = '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/user-favorite-cycles/'; static String orderByGroupByCycleIssues = - '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/cycles/\$CYCLEID/cycle-issues/?order_by=\$ORDERBY&group_by=\$GROUPBY&type=\$TYPE/'; + '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/cycles/\$CYCLEID/cycle-issues/?type=\$TYPE/'; static String orderByGroupByModuleIssues = - '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/modules/\$MODULEID/module-issues/?order_by=\$ORDERBY&group_by=\$GROUPBY&type=\$TYPE/'; + '$baseApi/api/workspaces/\$SLUG/projects/\$PROJECTID/modules/\$MODULEID/module-issues/?type=\$TYPE/'; static String myIssues = '$baseApi/api/workspaces/\$SLUG/my-issues/?order_by=\$ORDERBY&group_by=\$GROUPBY&type=\$TYPE'; static String projectIdentifier = @@ -149,4 +152,5 @@ class APIs { static String retrieveUserRoleOnWorkspace = '$baseApi/api/workspaces/\$SLUG/workspace-members/me/'; static String configApi = '$baseApi/api/mobile-configs'; + static String unsplashApi = '$baseApi/api/unsplash'; } diff --git a/lib/kanban/Provider/board_provider.dart b/lib/kanban/Provider/board_provider.dart index c4b6ce22..ed9855e6 100644 --- a/lib/kanban/Provider/board_provider.dart +++ b/lib/kanban/Provider/board_provider.dart @@ -82,7 +82,7 @@ class BoardProvider extends ChangeNotifier { required bool groupEmptyStates}) { var themeProvider = ref.read(ProviderList.themeProvider); board = BoardState( - boardID: boardID, + boardID: boardID, textStyle: textStyle, lists: [], isCardsDraggable: isCardsDraggable, @@ -110,7 +110,6 @@ class BoardProvider extends ChangeNotifier { cardPlaceHolderDecoration: cardPlaceHolderDecoration, listDecoration: listDecoration, boardDecoration: boardDecoration); - // log("LENGTH=${data.length}"); BoardList emptyStates = BoardList( // footer: data[i].footer, index: double.maxFinite.toInt(), @@ -138,7 +137,7 @@ class BoardProvider extends ChangeNotifier { const SizedBox( width: 10, ), - data[i].leading!, + // data[i].leading!, const SizedBox( width: 10, ), diff --git a/lib/main.dart b/lib/main.dart index 9cb36a7e..e81835f5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/config/config_variables.dart'; import 'package:plane/config/plane_keys.dart'; -import 'package:plane/screens/on_boarding/on_boarding_screen.dart'; +import 'package:plane/screens/onboarding/on_boarding_screen.dart'; import 'package:plane/services/shared_preference_service.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/startup/dependency_resolver.dart'; diff --git a/lib/models/Project/Label/label.model.dart b/lib/models/Project/Label/label.model.dart new file mode 100644 index 00000000..9741773d --- /dev/null +++ b/lib/models/Project/Label/label.model.dart @@ -0,0 +1,21 @@ +// ignore_for_file: non_constant_identifier_names + +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'label.model.freezed.dart'; +part 'label.model.g.dart'; + +@freezed +class LabelModel with _$LabelModel { + const factory LabelModel({ + String? parent, + required String name, + required String color, + required String id, + required String project_id, + required String workspace_id, + required double sort_order, + }) = _LabelModel; + + factory LabelModel.fromJson(Map json) => + _$LabelModelFromJson(json); +} diff --git a/lib/models/Project/issue-filter-and-properties/issue_filter_and_properties.dart b/lib/models/Project/issue-filter-and-properties/issue_filter_and_properties.dart new file mode 100644 index 00000000..7bc39609 --- /dev/null +++ b/lib/models/Project/issue-filter-and-properties/issue_filter_and_properties.dart @@ -0,0 +1,76 @@ +// ignore_for_file: non_constant_identifier_names, invalid_annotation_target +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'issue_filter_and_properties.freezed.dart'; +part 'issue_filter_and_properties.g.dart'; + +@freezed +class FiltersModel with _$FiltersModel{ + + const factory FiltersModel({ + required List? priority, + required List? state, + required List? assignees, + required List? labels, + required List? created_by, + required List? target_date, + }) = _FiltersModel; + + factory FiltersModel.fromJson(Map json) => + _$FiltersModelFromJson(json); + +} + +@freezed +class DisplayFiltersModel with _$DisplayFiltersModel{ + const factory DisplayFiltersModel({ + + required String? type, + required String layout, + required String? group_by, + required String order_by, + required bool sub_issue, + required bool show_empty_groups, + required String calendar_date_range, + }) = _DisplayFiltersModel; + + factory DisplayFiltersModel.fromJson(Map json) => + _$DisplayFiltersModelFromJson(json); + +} + +@freezed +class DisplayPropertiesModel with _$DisplayPropertiesModel{ + + const factory DisplayPropertiesModel({ + @JsonKey(defaultValue: false) + required bool key, + @JsonKey(defaultValue: false) + required bool link, + @JsonKey(defaultValue: false) + required bool state, + @JsonKey(defaultValue: false) + required bool labels, + @JsonKey(defaultValue: false) + required bool assignee, + @JsonKey(defaultValue: false) + required bool due_date, + @JsonKey(defaultValue: false) + required bool estimate, + @JsonKey(defaultValue: false) + required bool priority, + @JsonKey(defaultValue: false) + required bool created_on, + @JsonKey(defaultValue: false) + required bool start_date, + @JsonKey(defaultValue: false) + required bool updated_on, + @JsonKey(defaultValue: false) + required bool sub_issue_count, + @JsonKey(defaultValue: false) + required bool attachment_count, + }) = _DisplayPropertiesModel; + + factory DisplayPropertiesModel.fromJson(Map json) => + _$DisplayPropertiesModelFromJson(json); + +} \ No newline at end of file diff --git a/lib/models/Project/project-member/project_member.model.dart b/lib/models/Project/project-member/project_member.model.dart new file mode 100644 index 00000000..e251cbc2 --- /dev/null +++ b/lib/models/Project/project-member/project_member.model.dart @@ -0,0 +1,20 @@ + +// ignore_for_file: non_constant_identifier_names + +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'project_member.model.freezed.dart'; +part 'project_member.model.g.dart'; + +@freezed +class ProjectMemberLite with _$ProjectMemberLite{ + + const factory ProjectMemberLite({ + required String id, + String? member_avatar, + required String member_display_name, + required String member_id, + })= _ProjectMemberLite; + + factory ProjectMemberLite.fromJson(Map json) => + _$ProjectMemberLiteFromJson(json); +} \ No newline at end of file diff --git a/lib/models/Project/project.model.dart b/lib/models/Project/project.model.dart new file mode 100644 index 00000000..8cfe44e9 --- /dev/null +++ b/lib/models/Project/project.model.dart @@ -0,0 +1,77 @@ +// ignore_for_file: non_constant_identifier_names + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:plane/models/Project/project-member/project_member.model.dart'; +import 'package:plane/models/Workspace/workspace_model.dart'; +import 'package:plane/utils/enums.dart'; + +part 'project.model.freezed.dart'; +part 'project.model.g.dart'; + +@freezed +class ProjectModel with _$ProjectModel { + const factory ProjectModel({ + required int archive_in, + required int close_in, + required DateTime created_at, + required String created_by, + required String? cover_image, + required bool cycle_view, + required bool issue_views_view, + required bool module_view, + required bool page_view, + required bool inbox_view, + String? default_assigne, + String? default_state, + required String description, + String? emoji, + String? estimate, + IconProp? icon_prop, + required String id, + required String identifier, + required bool is_deployed, + required bool is_favorite, + required bool is_member, + Role? member_role, + required List members, + required String name, + required int network, + String? project_lead, + required int? sort_order, + required int total_cycles, + required int total_members, + required int total_modules, + required DateTime updated_at, + required String updated_by, + required String workspace, + required WorkspaceLiteModel workspace_detail, + }) = _ProjectModel; + + factory ProjectModel.fromJson(Map json) => + _$ProjectModelFromJson(json); +} + +@freezed +class IconProp with _$IconProp { + const factory IconProp({ + required final String name, + required final String color, + }) = _IconProp; + + factory IconProp.fromJson(Map json) => + _$IconPropFromJson(json); +} + +@freezed +class ProjectLiteModel with _$ProjectLiteModel { + const factory ProjectLiteModel({ + required String id, + required String identifier, + required String name, + required String description, + required String emoji, + }) = _ProjectLiteModel; + + factory ProjectLiteModel.fromJson(Map json) => + _$ProjectLiteModelFromJson(json); +} diff --git a/lib/models/Project/state/state_model.dart b/lib/models/Project/state/state_model.dart new file mode 100644 index 00000000..4022e576 --- /dev/null +++ b/lib/models/Project/state/state_model.dart @@ -0,0 +1,40 @@ +// ignore_for_file: non_constant_identifier_names, invalid_annotation_target + +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'state_model.freezed.dart'; +part 'state_model.g.dart'; + +@freezed +class StateModel with _$StateModel { + const factory StateModel({ + required final String id, + required final String project_id, + required final String workspace_id, + required final String name, + required final String color, + required final String group, + @JsonKey(name: 'default') required final bool is_default, + required final String? description, + required final double sequence, + @JsonKey(includeFromJson: false, includeToJson: false) Widget? stateIcon, + }) = _StateModel; + + factory StateModel.fromJson(Map json) => + _$StateModelFromJson(json); + + factory StateModel.initialize() { + return const StateModel( + id: '', + project_id: '', + workspace_id: '', + name: '', + color: '', + group: '', + is_default: false, + description: '', + sequence: 0, + stateIcon: null, + ); + } +} diff --git a/lib/models/Project/view/view_model.dart b/lib/models/Project/view/view_model.dart new file mode 100644 index 00000000..badb0c17 --- /dev/null +++ b/lib/models/Project/view/view_model.dart @@ -0,0 +1,33 @@ +// ignore_for_file: non_constant_identifier_names + +import 'package:plane/models/project/issue-filter-and-properties/issue_filter_and_properties.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'view_model.freezed.dart'; +part 'view_model.g.dart'; + +@freezed +class ViewModel with _$ViewModel{ + + const factory ViewModel({ + required String id, + required bool is_favorite, + required String created_at, + required String updated_at, + required String name, + required String? description, + required FiltersModel filters, + required DisplayFiltersModel display_filters, + required DisplayPropertiesModel display_properties, + required int access, + required double sort_order, + required String created_by, + required String updated_by, + required String workspace, + required String project, + }) = _ViewModel; + + factory ViewModel.fromJson(Map json) => + _$ViewModelFromJson(json); + +} + diff --git a/lib/models/workspace_model.dart b/lib/models/Workspace/workspace_model.dart similarity index 77% rename from lib/models/workspace_model.dart rename to lib/models/Workspace/workspace_model.dart index 9f215064..6f4c2c25 100644 --- a/lib/models/workspace_model.dart +++ b/lib/models/Workspace/workspace_model.dart @@ -1,5 +1,7 @@ - +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:plane/config/config_variables.dart'; +part 'workspace_model.freezed.dart'; +part 'workspace_model.g.dart'; class WorkspaceModel { WorkspaceModel({ @@ -54,3 +56,14 @@ class WorkspaceModel { 'url': workspaceUrl, }; } + +@freezed +class WorkspaceLiteModel with _$WorkspaceLiteModel { + const factory WorkspaceLiteModel({ + required final String id, + required final String name, + required final String slug, + }) = _WorkspaceLiteModel; + factory WorkspaceLiteModel.fromJson(Map json) => + _$WorkspaceLiteModelFromJson(json); +} diff --git a/lib/models/issues.dart b/lib/models/issues.dart index 55789269..8807b863 100644 --- a/lib/models/issues.dart +++ b/lib/models/issues.dart @@ -61,7 +61,7 @@ class Issues { required this.showSubIssues, required this.displayProperties}); List issues = []; - ProjectView projectView; + IssueLayout projectView; GroupBY groupBY = GroupBY.state; OrderBY orderBY = OrderBY.manual; bool showSubIssues = true; @@ -84,7 +84,7 @@ class Issues { return Issues( issues: [], showSubIssues: true, - projectView: ProjectView.kanban, + projectView: IssueLayout.kanban, groupBY: GroupBY.state, orderBY: OrderBY.lastCreated, issueType: IssueType.all, @@ -156,8 +156,8 @@ class Issues { switch (groupBY) { case "state": return GroupBY.state; - case "state_detail.group": - return GroupBY.stateGroups; + // case "state_detail.group": + // return GroupBY.stateGroups; case "priority": return GroupBY.priority; case "labels": diff --git a/lib/provider/cycles_provider.dart b/lib/provider/cycles_provider.dart index 8893aa36..0577d932 100644 --- a/lib/provider/cycles_provider.dart +++ b/lib/provider/cycles_provider.dart @@ -10,17 +10,17 @@ import 'package:plane/config/const.dart'; import 'package:plane/kanban/models/inputs.dart'; import 'package:plane/models/issues.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/services/dio_service.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/global_functions.dart'; +import 'package:plane/utils/issues_filter/issue_filter.helper.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/issue_card_widget.dart'; -import '../screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class CyclesProvider with ChangeNotifier { CyclesProvider(ChangeNotifierProviderRef this.ref); Ref? ref; @@ -33,6 +33,7 @@ class CyclesProvider with ChangeNotifier { StateEnum completedCyclesState = StateEnum.loading; StateEnum draftCyclesState = StateEnum.loading; StateEnum transferIssuesState = StateEnum.empty; + StateEnum cycleViewState = StateEnum.empty; List cyclesAllData = []; List cycleFavoriteData = []; List cycleUpcomingFavoriteData = []; @@ -46,18 +47,21 @@ class CyclesProvider with ChangeNotifier { List cyclesDraftData = []; Map cyclesDetailsData = {}; Map currentCycle = {}; + Map cycleView = {}; int cyclesTabIndex = 0; Issues issues = Issues.initialize(); List issuesResponse = []; List shrinkStates = []; Map filterIssues = {}; + List cycleIssuesList = []; Map issueProperty = {}; bool showEmptyStates = true; bool isIssuesEmpty = false; int cycleDetailSelectedIndex = 0; List queries = ['all', 'current', 'upcoming', 'completed', 'draft']; - List stateOrdering = []; List loadingCycleId = []; + Enum issueCategory = IssueCategory.cycleIssues; + void setState() { notifyListeners(); } @@ -125,6 +129,7 @@ class CyclesProvider with ChangeNotifier { required WidgetRef ref}) async { final workspaceProvider = ref.watch(ProviderList.workspaceProvider); final projectProvider = ref.watch(ProviderList.projectProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); if (query == 'all') { allCyclesState = StateEnum.loading; } else if (query == 'current') { @@ -159,10 +164,6 @@ class CyclesProvider with ChangeNotifier { data: data, ); - // log('CYCLES =========> ${response.data.toString()}'); - - // * RESPONSE FROM API CONVERTED TO MODEL IS THROWING ERRORS FOR VIEW PROPS ATTRIBUTE * // - // cyclesData = CyclesModel.fromJson(response.data); if (query == 'all') { cyclesAllData = []; cycleFavoriteData = []; @@ -242,7 +243,8 @@ class CyclesProvider with ChangeNotifier { 'PROJECT_NAME': projectProvider.projectDetailModel!.name, 'CYCLE_ID': response.data['id'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); WidgetsBinding.instance.addPostFrameCallback((_) { notifyListeners(); }); @@ -253,7 +255,7 @@ class CyclesProvider with ChangeNotifier { } } - void changeStateToLoading(StateEnum state) { + void changeState(StateEnum state) { state = StateEnum.loading; notifyListeners(); } @@ -267,10 +269,10 @@ class CyclesProvider with ChangeNotifier { Map? data, }) async { try { - if (!disableLoading) { - cyclesDetailState = StateEnum.loading; - notifyListeners(); - } + // if (!disableLoading) { + cyclesDetailState = StateEnum.loading; + notifyListeners(); + // } final url = '${APIs.cycles.replaceFirst('\$SLUG', slug).replaceFirst('\$PROJECTID', projectId)}$cycleId/'; final response = await DioConfig().dioServe( @@ -287,7 +289,6 @@ class CyclesProvider with ChangeNotifier { : HttpMethod.patch, ); if (method == CRUD.read) { - // log('CYCLE DETAILS =========> ${response.data.toString()}'); cyclesDetailsData = response.data; } if (method == CRUD.update) { @@ -381,18 +382,21 @@ class CyclesProvider with ChangeNotifier { List initializeBoard() { final themeProvider = ref!.read(ProviderList.themeProvider); final issuesProvider = ref!.read(ProviderList.issuesProvider); + final labelNotifier = ref!.read(ProviderList.labelProvider.notifier); + final statesProvider = ref!.read(ProviderList.statesProvider); int count = 0; // log(issues.groupBY.name); issues.issues = []; issuesResponse = []; - for (int j = 0; j < stateOrdering.length; j++) { - final List items = []; + final projectMembers = + ref!.read(ProviderList.projectProvider).projectMembers; - for (int i = 0; - filterIssues[stateOrdering[j]] != null && - i < filterIssues[stateOrdering[j]]!.length; - i++) { - issuesResponse.add(filterIssues[stateOrdering[j]]![i]); + for (int j = 0; j < filterIssues.length; j++) { + final List items = []; + final groupedIssues = filterIssues.values.toList()[j]; + final groupID = filterIssues.keys.elementAt(j); + for (int i = 0; i < groupedIssues.length; i++) { + issuesResponse.add(groupedIssues[i]); items.add( IssueCardWidget( from: PreviousScreen.cycles, @@ -402,51 +406,38 @@ class CyclesProvider with ChangeNotifier { ), ); } - Map label = {}; String userName = ''; - - bool labelFound = false; bool userFound = false; + final label = labelNotifier.getLabelById(groupID); - for (int i = 0; i < issuesProvider.labels.length; i++) { - if (stateOrdering[j] == issuesProvider.labels[i]['id']) { - label = issuesProvider.labels[i]; - labelFound = true; - break; - } - } - - for (int i = 0; i < issuesProvider.members.length; i++) { - if (stateOrdering[j] == issuesProvider.members[i]['member']['id']) { - userName = issuesProvider.members[i]['member']['first_name'] + + for (int i = 0; i < projectMembers.length; i++) { + if (groupID == projectMembers[i]['member']['id']) { + userName = projectMembers[i]['member']['first_name'] + ' ' + - issuesProvider.members[i]['member']['last_name']; + projectMembers[i]['member']['last_name']; userName = userName.trim().isEmpty - ? issuesProvider.members[i]['member']['email'] + ? projectMembers[i]['member']['email'] : userName; userFound = true; break; } } - //log('RESPONSE : ' + filterIssues.toString()); - // log('================================================================ ${stateOrdering[j]}'); var title = issues.groupBY == GroupBY.priority - ? stateOrdering[j] + ? groupID : issues.groupBY == GroupBY.state - ? issuesProvider.states[stateOrdering[j]]['name'] - : stateOrdering[j]; + ? statesProvider.projectStates[groupID]!.name + : groupID; issues.issues.add(BoardListsData( - id: stateOrdering[j], + id: groupID, items: items, shrink: shrinkStates[j], index: j, - width: issuesProvider.issues.projectView == ProjectView.list + width: issuesProvider.issues.projectView == IssueLayout.list ? MediaQuery.of(Const.globalKey.currentContext!).size.width : 300, // shrink: shrinkissuesProvider.states[count++], - title: issues.groupBY == GroupBY.labels && labelFound - ? label['name'][0].toString().toUpperCase() + - label['name'].toString().substring(1) + title: issues.groupBY == GroupBY.labels && label != null + ? label.name[0].toUpperCase() + label.name.toString().substring(1) : userFound && (issues.groupBY == GroupBY.createdBY || issues.groupBY == GroupBY.assignees) @@ -455,7 +446,7 @@ class CyclesProvider with ChangeNotifier { : title = title[0].toString().toUpperCase() + title.toString().substring(1), header: Text( - stateOrdering[j], + groupID, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -633,11 +624,14 @@ class CyclesProvider with ChangeNotifier { notifyListeners(); return; } - (filterIssues[stateOrdering[newListIndex]] as List).insert(newCardIndex, - filterIssues[stateOrdering[oldListIndex]].removeAt(oldCardIndex)); + + final List newList = filterIssues.values.toList()[newListIndex]; + final List oldList = filterIssues.values.toList()[oldListIndex]; + + newList.insert(newCardIndex, oldList.removeAt(oldCardIndex)); notifyListeners(); - final issue = filterIssues[stateOrdering[newListIndex]][newCardIndex]; + final issue = newList[newCardIndex]; // log(issue.toString()); final response = await DioConfig().dioServe( hasAuth: true, @@ -655,40 +649,21 @@ class CyclesProvider with ChangeNotifier { httpMethod: HttpMethod.patch, data: issues.groupBY == GroupBY.state ? { - 'state': stateOrdering[newListIndex], + 'state': filterIssues.keys.elementAt(newListIndex), 'priority': issue['priority'] } : { 'state': issue['state'], - 'priority': stateOrdering[newListIndex], + 'priority': filterIssues.keys.elementAt(newListIndex) }); - filterIssues[stateOrdering[newListIndex]][newCardIndex] = response.data; - - final List labelDetails = []; - final issuesProvider = ref!.read(ProviderList.issuesProvider); - filterIssues[stateOrdering[newListIndex]][newCardIndex]['labels'] - .forEach((element) { - for (int i = 0; i < issuesProvider.labels.length; i++) { - if (issuesProvider.labels[i]['id'] == element) { - labelDetails.add(issuesProvider.labels[i]); - break; - } - } - - // labelDetails.add(labels.firstWhere((e) => e['id'] == element)); - }); - - filterIssues[stateOrdering[newListIndex]][newCardIndex]['label_details'] = - labelDetails; - - log(response.data.toString()); + newList[newCardIndex] = response.data; if (issues.groupBY == GroupBY.priority) { - log(filterIssues[stateOrdering[newListIndex]][newCardIndex]['name']); - filterIssues[stateOrdering[newListIndex]][newCardIndex]['priority'] = - stateOrdering[newListIndex]; + log(newList[newCardIndex]['name']); + newList[newCardIndex]['priority'] = + filterIssues.keys.elementAt(newListIndex); } if (issues.orderBY != OrderBY.manual) { - (filterIssues[stateOrdering[newListIndex]] as List).sort((a, b) { + newList.sort((a, b) { if (issues.orderBY == OrderBY.priority) { return priorityParser(a['priority']) .compareTo(priorityParser(b['priority'])); @@ -703,11 +678,10 @@ class CyclesProvider with ChangeNotifier { } }); } - log("ISSUE REPOSITIONED"); notifyListeners(); } on DioException catch (err) { - (filterIssues[stateOrdering[oldListIndex]] as List).insert(oldCardIndex, - filterIssues[stateOrdering[newListIndex]].removeAt(newCardIndex)); + filterIssues.values.elementAt(oldListIndex).insert(oldCardIndex, + filterIssues.values.elementAt(newListIndex).removeAt(newCardIndex)); log(err.toString()); notifyListeners(); rethrow; @@ -802,25 +776,140 @@ class CyclesProvider with ChangeNotifier { } } - Future filterCycleIssues({ - required String slug, - required String projectId, - String? cycleID, - String? moduleID, - // required Map data, - }) async { + Future getCycleView({bool reset = false, required String cycleId}) async { + cycleViewState = StateEnum.loading; + if (reset) { + notifyListeners(); + } + try { + var url = APIs.cycleIssueView + .replaceAll( + "\$SLUG", + ref! + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug) + .replaceAll('\$PROJECTID', + ref!.read(ProviderList.projectProvider).currentProject['id']) + .replaceAll('\$CYCLEID', cycleId); + final response = await DioConfig().dioServe( + hasAuth: true, + url: url, + hasBody: false, + httpMethod: HttpMethod.get, + ); + cycleView = response.data; + issues.projectView = cycleView['display_filters']['layout'] == 'list' + ? IssueLayout.list + : cycleView['display_filters']['layout'] == 'calendar' + ? IssueLayout.calendar + : cycleView['display_filters']['layout'] == 'spreadsheet' + ? IssueLayout.spreadsheet + : IssueLayout.kanban; + issues.showSubIssues = cycleView['display_filters']['sub_issue'] ?? true; + issues.groupBY = + Issues.toGroupBY(cycleView["display_filters"]["group_by"]); + issues.orderBY = + Issues.toOrderBY(cycleView["display_filters"]["order_by"]); + issues.issueType = + Issues.toIssueType(cycleView["display_filters"]["type"]); + issues.filters.priorities = (cycleView["filters"]["priority"] == 'none' + ? [] + : cycleView["filters"]["priority"]) ?? + []; + issues.filters.states = cycleView["filters"]["state"] ?? []; + issues.filters.assignees = cycleView["filters"]["assignees"] ?? []; + issues.filters.createdBy = cycleView["filters"]["created_by"] ?? []; + issues.filters.labels = cycleView["filters"]["labels"] ?? []; + issues.filters.targetDate = cycleView["filters"]["target_date"] ?? []; + issues.filters.startDate = cycleView["filters"]["start_date"] ?? []; + issues.filters.subscriber = cycleView["filters"]["subscriber"] ?? []; + issues.filters.stateGroup = cycleView["filters"]["state_group"] ?? []; + showEmptyStates = cycleView["display_filters"]["show_empty_groups"]; + + if (issues.groupBY == GroupBY.none) { + issues.projectView = IssueLayout.list; + } + if (reset) { + updateCycleView(); + } + cycleViewState = StateEnum.success; + notifyListeners(); + } on DioException catch (e) { + log(e.response.toString()); + issues.projectView = IssueLayout.kanban; + cycleViewState = StateEnum.error; + notifyListeners(); + } + } + + void applyCycleIssuesView({required WidgetRef ref}) { + final projectProvider = ref.read(ProviderList.projectProvider); + final statesProvider = ref.read(ProviderList.statesProvider); + + final labelIds = + ref.read(ProviderList.labelProvider.notifier).getLabelIds(); + filterIssues = IssueFilterHelper.organizeIssues( + cycleIssuesList, issues.groupBY, issues.orderBY, + labelIDs: labelIds, + memberIDs: projectProvider.projectMembers + .map((e) => e['member']['id'].toString()) + .toList(), + states: statesProvider.projectStates); + notifyListeners(); + } + + Future filterCycleIssues( + {required String slug, + required String projectId, + String? cycleID, + required WidgetRef ref + // required Map data, + }) async { cyclesIssueState = StateEnum.loading; notifyListeners(); - try { - final issuesProvider = ref!.read(ProviderList.issuesProvider); - filterIssues = await issuesProvider.filterIssues( + final projectProvider = ref.read(ProviderList.projectProvider); + final statesProvider = ref.read(ProviderList.statesProvider.notifier); + final labelNotifier = ref.read(ProviderList.labelProvider.notifier); + final states = ref.read(ProviderList.statesProvider).projectStates; + + if (issues.groupBY == GroupBY.labels) { + labelNotifier.getProjectLabels(); + } else if (issues.groupBY == GroupBY.createdBY) { + projectProvider.getProjectMembers(slug: slug, projId: projectId); + } else if (issues.groupBY == GroupBY.state) { + statesProvider.getStates( slug: slug, - projID: projectId, - cycleId: cycleID, - moduleId: moduleID, - issueCategory: IssueCategory.cycleIssues, + projectId: projectId, ); + } + String url; + url = APIs.orderByGroupByCycleIssues + .replaceAll("\$SLUG", slug) + .replaceAll('\$PROJECTID', projectId) + .replaceAll('\$CYCLEID', cycleID!) + .replaceAll('\$TYPE', Issues.fromIssueType(issues.issueType)); + url = '$url${IssueFilterHelper.getFilterQueryParams(issues.filters)}'; + url = '$url&sub_issue=${issues.showSubIssues}&show_empty_groups=true'; + log(url.toString()); + try { + final response = await DioConfig().dioServe( + hasAuth: true, + url: url, + hasBody: false, + httpMethod: HttpMethod.get, + ); + // cycleIssuesList = []; + cycleIssuesList = response.data; + final organizedIssues = IssueFilterHelper.organizeIssues( + cycleIssuesList, issues.groupBY, issues.orderBY, + labelIDs: labelNotifier.getLabelIds(), + memberIDs: projectProvider.projectMembers + .map((e) => e['member']['id'].toString()) + .toList(), + states: states); + filterIssues = organizedIssues; issuesResponse = []; isIssuesEmpty = true; if (issues.groupBY != GroupBY.none) { @@ -834,17 +923,15 @@ class CyclesProvider with ChangeNotifier { isIssuesEmpty = filterIssues.values.first.isEmpty; } if (issues.groupBY == GroupBY.state) { - issuesProvider.states.forEach((key, value) { + states.forEach((key, value) { if (issues.filters.states.isEmpty && filterIssues[key] == null) { filterIssues[key] = []; } shrinkStates.add(false); }); } else { - stateOrdering = []; shrinkStates = []; filterIssues.forEach((key, value) { - stateOrdering.add(key); shrinkStates.add(false); }); } @@ -860,6 +947,80 @@ class CyclesProvider with ChangeNotifier { } } + Future updateCycleView( + {bool isArchive = false, bool setDefault = false}) async { + final Map view = { + "view_props": { + "calendarDateRange": "", + "collapsed": false, + "filterIssue": null, + "filters": { + // 'type': null, + // "priority": filterPriority, + if (issues.filters.priorities.isNotEmpty) + "priority": issues.filters.priorities, + if (issues.filters.states.isNotEmpty) "state": issues.filters.states, + if (issues.filters.assignees.isNotEmpty) + "assignees": issues.filters.assignees, + if (issues.filters.createdBy.isNotEmpty) + "created_by": issues.filters.createdBy, + if (issues.filters.labels.isNotEmpty) "labels": issues.filters.labels, + if (issues.filters.targetDate.isNotEmpty) + "target_date": issues.filters.targetDate, + if (issues.filters.startDate.isNotEmpty) + "start_date": issues.filters.startDate, + if (issues.filters.subscriber.isNotEmpty) + "subscriber": issues.filters.subscriber, + if (issues.filters.stateGroup.isNotEmpty) + "state_group": issues.filters.stateGroup, + }, + "display_filters": { + "group_by": Issues.fromGroupBY(issues.groupBY), + "order_by": Issues.fromOrderBY(issues.orderBY), + "type": Issues.fromIssueType(issues.issueType), + "show_empty_groups": showEmptyStates, + if (!isArchive) + "layout": issues.projectView == IssueLayout.kanban + ? 'kanban' + : issues.projectView == IssueLayout.list + ? 'list' + : issues.projectView == IssueLayout.calendar + ? 'calendar' + : 'spreadsheet', + "sub_issue": false, + }, + } + }; + if (setDefault) { + view['default_props'] = view['view_props']; + } + try { + await DioConfig().dioServe( + hasAuth: true, + url: APIs.cycleIssueView + .replaceAll( + "\$SLUG", + ref! + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug) + .replaceAll('\$PROJECTID', + ref!.read(ProviderList.projectProvider).currentProject['id']) + .replaceAll('\$CYCLEID', currentCycle['id']), + hasBody: true, + data: view, + httpMethod: HttpMethod.patch, + ); + cycleViewState = StateEnum.success; + notifyListeners(); + } on DioException catch (e) { + log(e.response.toString()); + cycleViewState = StateEnum.error; + notifyListeners(); + } + notifyListeners(); + } + bool isTagsEnabled() { return issues.displayProperties.assignee || issues.displayProperties.dueDate || diff --git a/lib/provider/issue_provider.dart b/lib/provider/issue_provider.dart index deebe07b..534e4739 100644 --- a/lib/provider/issue_provider.dart +++ b/lib/provider/issue_provider.dart @@ -10,18 +10,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/config/config_variables.dart'; import 'package:plane/config/const.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/user_profile.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; +import 'package:plane/screens/profile/user-profile/user_profile.dart'; +import 'package:plane/screens/project/cycles/cycle-detail/cycle_issues_page.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; +import 'package:plane/screens/project/modules/module-detail/module_issues_page.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/config/apis.dart'; import 'package:plane/services/dio_service.dart'; import 'package:url_launcher/url_launcher.dart'; - -import '../screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart'; import '../utils/global_functions.dart'; -// import 'package:webview_cookie_manager/webview_cookie_manager.dart'; class IssueProvider with ChangeNotifier { IssueProvider(ChangeNotifierProviderRef this.ref); @@ -199,6 +198,7 @@ class IssueProvider with ChangeNotifier { BuildContext? buildContext}) async { final workspaceProvider = refs.watch(ProviderList.workspaceProvider); final projectProvider = refs.watch(ProviderList.projectProvider); + final profileProvider = refs.watch(ProviderList.profileProvider); try { updateIssueState = StateEnum.loading; notifyListeners(); @@ -223,7 +223,8 @@ class IssueProvider with ChangeNotifier { .firstWhere((element) => element['id'] == projID)['name'], 'ISSUE_ID': issueID }, - ref: refs); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); await getIssueDetails(slug: slug, projID: projID, issueID: issueID); await getIssueActivity(slug: slug, projID: projID, issueID: issueID); await ref.read(ProviderList.myIssuesProvider.notifier).getMyIssues( @@ -617,9 +618,8 @@ class IssueProvider with ChangeNotifier { Navigator.push( context, MaterialPageRoute( - builder: (context) => CycleDetail( + builder: (context) => ModuleDetail( from: previousScreen, - fromModule: true, moduleId: data['module_id'], projId: data['project_id'], moduleName: data['module_name'], diff --git a/lib/provider/issues_provider.dart b/lib/provider/issues_provider.dart index 88fd6b64..5a302649 100644 --- a/lib/provider/issues_provider.dart +++ b/lib/provider/issues_provider.dart @@ -9,25 +9,23 @@ import 'package:plane/config/const.dart'; import 'package:plane/kanban/models/inputs.dart'; import 'package:plane/models/issues.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/constants.dart'; +import 'package:plane/utils/extensions/string_extensions.dart'; import 'package:plane/utils/global_functions.dart'; import 'package:plane/utils/custom_toast.dart'; +import 'package:plane/utils/issues_filter/issue_filter.helper.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/issue_card_widget.dart'; import 'package:plane/config/apis.dart'; import 'package:plane/services/dio_service.dart'; import 'package:plane/utils/enums.dart'; -import '../screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class IssuesProvider extends ChangeNotifier { IssuesProvider(ChangeNotifierProviderRef this.ref); Ref? ref; - StateEnum statesState = StateEnum.empty; - StateEnum membersState = StateEnum.empty; StateEnum issueState = StateEnum.empty; - StateEnum labelState = StateEnum.empty; StateEnum orderByState = StateEnum.empty; StateEnum projectViewState = StateEnum.empty; StateEnum issuePropertyState = StateEnum.empty; @@ -53,7 +51,7 @@ class IssuesProvider extends ChangeNotifier { subscriber: [], ); IssueType tempIssueType = IssueType.all; - ProjectView tempProjectView = ProjectView.kanban; + IssueLayout tempProjectView = IssueLayout.kanban; OrderBY tempOrderBy = OrderBY.lastCreated; Map stateIcons = {}; Map issueProperty = {}; @@ -61,10 +59,8 @@ class IssuesProvider extends ChangeNotifier { Map createIssueProjectData = {}; List issuesResponse = []; List issuesList = []; - List labels = []; - Map states = {}; Map statesData = {}; - List members = []; + // List members = []; Map projectView = {}; Map groupByResponse = {}; List shrinkStates = []; @@ -138,14 +134,12 @@ class IssuesProvider extends ChangeNotifier { 'completed', 'cancelled', ]; - List stateOrdering = []; - void clear() { issueView = {}; showEmptyStates = true; issues = Issues( issues: [], - projectView: ProjectView.kanban, + projectView: IssueLayout.kanban, groupBY: GroupBY.state, orderBY: OrderBY.manual, showSubIssues: true, @@ -172,10 +166,7 @@ class IssuesProvider extends ChangeNotifier { createIssueParentId = ''; issuesResponse = []; subIssuesIds = []; - labels = []; - states = {}; statesData = {}; - members = []; projectView = {}; groupByResponse = {}; @@ -214,16 +205,16 @@ class IssuesProvider extends ChangeNotifier { int count = 0; issuesResponse = []; issues.issues = []; - for (int j = 0; j < stateOrdering.length; j++) { + final projectMembers = + ref!.read(ProviderList.projectProvider).projectMembers; + final labelNotifier = ref!.read(ProviderList.labelProvider.notifier); + final statesProvider = ref!.read(ProviderList.statesProvider); + for (int j = 0; j < groupByResponse.length; j++) { final List items = []; - if (groupByResponse[stateOrdering[j]] == null) { - continue; - } - for (int i = 0; - groupByResponse[stateOrdering[j]] != null && - i < groupByResponse[stateOrdering[j]]!.length; - i++) { - issuesResponse.add(groupByResponse[stateOrdering[j]]![i]); + final groupedIssues = groupByResponse.values.elementAt(j); + final groupID = groupByResponse.keys.elementAt(j); + for (int i = 0; i < groupedIssues.length; i++) { + issuesResponse.add(groupedIssues[i]); items.add( IssueCardWidget( @@ -235,27 +226,15 @@ class IssuesProvider extends ChangeNotifier { ), ); } - Map label = {}; String userName = ''; - - bool labelFound = false; + final label = labelNotifier.getLabelById(groupID); bool userFound = false; - for (int i = 0; i < labels.length; i++) { - if (stateOrdering[j] == labels[i]['id']) { - label = labels[i]; - labelFound = true; - break; - } - } - - for (int i = 0; i < members.length; i++) { - if (stateOrdering[j] == members[i]['member']['id']) { - userName = members[i]['member']['first_name'] + - ' ' + - members[i]['member']['last_name']; + for (int i = 0; i < projectMembers.length; i++) { + if (groupID == projectMembers[i]['member']) { + userName = projectMembers[i]['member']['display_name']; userName = userName.trim().isEmpty - ? members[i]['member']['email'] + ? projectMembers[i]['member']['email'] : userName; userFound = true; break; @@ -268,22 +247,93 @@ class IssuesProvider extends ChangeNotifier { } String title = issues.groupBY == GroupBY.priority - ? stateOrdering[j] + ? groupID : issues.groupBY == GroupBY.state - ? states[stateOrdering[j]]['name'] - : stateOrdering[j]; + ? statesProvider.projectStates[groupID]!.name + : groupID; issues.issues.add(BoardListsData( - id: stateOrdering[j], + leading: issues.groupBY == GroupBY.priority + ? title == 'Urgent' + ? Icon( + Icons.error_outline, + size: 18, + color: Color(int.parse("FF${"#EF4444".replaceAll('#', '')}", + radix: 16)), + ) + : title == 'High' + ? Icon( + Icons.signal_cellular_alt, + size: 18, + color: Color(int.parse( + "FF${"#F59E0B".replaceAll('#', '')}", + radix: 16)), + ) + : title == 'Medium' + ? Icon( + Icons.signal_cellular_alt_2_bar, + color: Color(int.parse( + "FF${"#F59E0B".replaceAll('#', '')}", + radix: 16)), + size: 18, + ) + : title == 'Low' + ? Icon( + Icons.signal_cellular_alt_1_bar, + color: Color(int.parse( + "FF${"#22C55E".replaceAll('#', '')}", + radix: 16)), + size: 18, + ) + : Icon( + Icons.do_disturb_alt_outlined, + color: Color(int.parse( + "FF${"#A3A3A3".replaceAll('#', '')}", + radix: 16)), + size: 18, + ) + : issues.groupBY == GroupBY.createdBY || + issues.groupBY == GroupBY.assignees + ? Container( + height: 22, + alignment: Alignment.center, + width: 22, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color.fromRGBO(55, 65, 81, 1), + ), + child: CustomText( + title.toString().toUpperCase()[0], + fontSize: 12, + color: Colors.white, + fontWeight: FontWeightt.Medium, + ), + ) + : issues.groupBY == GroupBY.labels + ? Container( + margin: const EdgeInsets.only(top: 3), + height: 15, + alignment: Alignment.center, + width: 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: title == 'None' + ? Colors.black + : label?.color.toColor()), + ) + : issues.groupBY == GroupBY.stateGroups + ? defaultStatedetails[groupID]['icon'] + : stateIcons[groupID], + id: groupID, items: items, shrink: j >= shrinkStates.length ? false : shrinkStates[j], index: j, - width: issues.projectView == ProjectView.list - ? MediaQuery.of(Const.globalKey.currentContext!).size.width - : (width > 500 ? 400 : width * 0.8), + // width: issues.projectView == IssueLayout.list + // ? MediaQuery.of(Const.globalKey.currentContext!).size.width + // : (width > 500 ? 400 : width * 0.8), // shrink: shrinkStates[count++], - title: issues.groupBY == GroupBY.labels && labelFound - ? label['name'][0].toString().toUpperCase() + - label['name'].toString().substring(1) + title: issues.groupBY == GroupBY.labels && label != null + ? label.name[0].toString().toUpperCase() + + label.name.toString().substring(1) : userFound && (issues.groupBY == GroupBY.createdBY || issues.groupBY == GroupBY.assignees) @@ -292,7 +342,7 @@ class IssuesProvider extends ChangeNotifier { : title = title[0].toString().toUpperCase() + title.toString().substring(1), header: Text( - stateOrdering[j], + groupID, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -312,78 +362,6 @@ class IssuesProvider extends ChangeNotifier { } for (final element in issues.issues) { - element.leading = issues.groupBY == GroupBY.priority - ? element.title == 'Urgent' - ? Icon( - Icons.error_outline, - size: 18, - color: Color(int.parse("FF${"#EF4444".replaceAll('#', '')}", - radix: 16)), - ) - : element.title == 'High' - ? Icon( - Icons.signal_cellular_alt, - size: 18, - color: Color(int.parse( - "FF${"#F59E0B".replaceAll('#', '')}", - radix: 16)), - ) - : element.title == 'Medium' - ? Icon( - Icons.signal_cellular_alt_2_bar, - color: Color(int.parse( - "FF${"#F59E0B".replaceAll('#', '')}", - radix: 16)), - size: 18, - ) - : element.title == 'Low' - ? Icon( - Icons.signal_cellular_alt_1_bar, - color: Color(int.parse( - "FF${"#22C55E".replaceAll('#', '')}", - radix: 16)), - size: 18, - ) - : Icon( - Icons.do_disturb_alt_outlined, - color: Color(int.parse( - "FF${"#A3A3A3".replaceAll('#', '')}", - radix: 16)), - size: 18, - ) - : issues.groupBY == GroupBY.createdBY || - issues.groupBY == GroupBY.assignees - ? Container( - height: 22, - alignment: Alignment.center, - width: 22, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Color.fromRGBO(55, 65, 81, 1), - ), - child: CustomText( - element.title.toString().toUpperCase()[0], - fontSize: 12, - color: Colors.white, - fontWeight: FontWeightt.Medium, - ), - ) - : issues.groupBY == GroupBY.labels - ? Container( - margin: const EdgeInsets.only(top: 3), - height: 15, - alignment: Alignment.center, - width: 15, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: element.title == 'None' ? Colors.black : null - // color: Color(int.parse(element.title)), - ), - ) - : issues.groupBY == GroupBY.stateGroups - ? defaultStatedetails[element.id]['icon'] - : stateIcons[element.id]; - element.header = SizedBox( // margin: const EdgeInsets.only(bottom: 10), height: 50, @@ -395,7 +373,7 @@ class IssuesProvider extends ChangeNotifier { width: 10, ), SizedBox( - width: element.width - 150, + // width: element.width - 150, child: CustomText( element.title.toString(), type: FontStyle.Large, @@ -462,7 +440,6 @@ class IssuesProvider extends ChangeNotifier { ), ); } - return issues.issues; } @@ -481,17 +458,19 @@ class IssuesProvider extends ChangeNotifier { required int newListIndex, required int oldListIndex, }) async { + final newListID = groupByResponse.keys.elementAt(newListIndex); + final oldListID = groupByResponse.keys.elementAt(oldListIndex); try { if (oldListIndex == newListIndex) { notifyListeners(); return; } - (groupByResponse[stateOrdering[newListIndex]] as List).insert( - newCardIndex, - groupByResponse[stateOrdering[oldListIndex]].removeAt(oldCardIndex)); + + groupByResponse[newListID].insert( + newCardIndex, groupByResponse[oldListID].removeAt(oldCardIndex)); updateIssueState = StateEnum.loading; notifyListeners(); - final issue = groupByResponse[stateOrdering[newListIndex]][newCardIndex]; + final issue = groupByResponse[newListID][newCardIndex]; final response = await DioConfig().dioServe( hasAuth: true, url: APIs.issueDetails @@ -501,40 +480,19 @@ class IssuesProvider extends ChangeNotifier { hasBody: true, httpMethod: HttpMethod.patch, data: issues.groupBY == GroupBY.state - ? { - 'state': stateOrdering[newListIndex], - 'priority': issue['priority'] - } + ? {'state': newListID, 'priority': issue['priority']} : { 'state': issue['state'], - 'priority': stateOrdering[newListIndex], + 'priority': newListID, }); - groupByResponse[stateOrdering[newListIndex]][newCardIndex] = - response.data; - - final List labelDetails = []; + groupByResponse[newListID][newCardIndex] = response.data; - groupByResponse[stateOrdering[newListIndex]][newCardIndex]['labels'] - .forEach((element) { - for (int i = 0; i < labels.length; i++) { - if (labels[i]['id'] == element) { - labelDetails.add(labels[i]); - break; - } - } - - // labelDetails.add(labels.firstWhere((e) => e['id'] == element)); - }); - - groupByResponse[stateOrdering[newListIndex]][newCardIndex] - ['label_details'] = labelDetails; updateIssueState = StateEnum.success; if (issues.groupBY == GroupBY.priority) { - groupByResponse[stateOrdering[newListIndex]][newCardIndex]['priority'] = - stateOrdering[newListIndex]; + groupByResponse[newListID][newCardIndex]['priority'] = newListID; } if (issues.orderBY != OrderBY.manual) { - (groupByResponse[stateOrdering[newListIndex]] as List).sort((a, b) { + groupByResponse[newListID].sort((a, b) { if (issues.orderBY == OrderBY.priority) { return priorityParser(a['priority']) .compareTo(priorityParser(b['priority'])); @@ -552,9 +510,8 @@ class IssuesProvider extends ChangeNotifier { notifyListeners(); // ignore: unused_catch_clause } on DioException catch (err) { - (groupByResponse[stateOrdering[oldListIndex]] as List).insert( - oldCardIndex, - groupByResponse[stateOrdering[newListIndex]].removeAt(newCardIndex)); + (groupByResponse[oldListID] as List).insert( + oldCardIndex, groupByResponse[newListID].removeAt(newCardIndex)); // ignore: use_build_context_synchronously CustomToast.showToast(context, @@ -576,171 +533,6 @@ class IssuesProvider extends ChangeNotifier { issues.displayProperties.attachmentCount; } - Future getLabels({required String slug, required String projID}) async { - labelState = StateEnum.loading; - // notifyListeners(); - - try { - final response = await DioConfig().dioServe( - hasAuth: true, - // url: APIs.issueLabels - // .replaceAll("\$SLUG", slug) - // .replaceAll('\$PROJECTID', projID), - url: - '${APIs.baseApi}/api/workspaces/$slug/projects/$projID/issue-labels/', - hasBody: false, - httpMethod: HttpMethod.get, - ); - labels = response.data; - labelState = StateEnum.success; - - notifyListeners(); - } on DioException catch (e) { - log(e.error.toString()); - labelState = StateEnum.error; - notifyListeners(); - } - } - - Future issueLabels( - {required String slug, - required String projID, - required dynamic data, - CRUD? method, - String? labelId, - required WidgetRef ref}) async { - final workspaceProvider = ref.read(ProviderList.workspaceProvider); - final projectProvider = ref.read(ProviderList.projectProvider); - labelState = StateEnum.loading; - notifyListeners(); - final String url = method == CRUD.update || method == CRUD.delete - ? '${APIs.issueLabels.replaceAll("\$SLUG", slug).replaceAll('\$PROJECTID', projID)}$labelId/' - : APIs.issueLabels - .replaceAll("\$SLUG", slug) - .replaceAll('\$PROJECTID', projID); - - try { - final response = await DioConfig().dioServe( - hasAuth: true, - url: url, - hasBody: true, - httpMethod: method == CRUD.update - ? HttpMethod.patch - : method == CRUD.delete - ? HttpMethod.delete - : HttpMethod.post, - data: data); - method != CRUD.read - ? postHogService( - eventName: method == CRUD.create - ? 'ISSUE_LABEL_CREATE' - : method == CRUD.update - ? 'ISSUE_LABEL_UPDATE' - : method == CRUD.delete - ? 'ISSUE_LABEL_DELETE' - : '', - properties: method == CRUD.delete - ? {} - : { - 'WORKSPACE_ID': - workspaceProvider.selectedWorkspace.workspaceId, - 'WORKSPACE_SLUG': - workspaceProvider.selectedWorkspace.workspaceSlug, - 'WORKSPACE_NAME': - workspaceProvider.selectedWorkspace.workspaceName, - 'PROJECT_ID': projectProvider.projectDetailModel!.id, - 'PROJECT_NAME': projectProvider.projectDetailModel!.name, - 'LABEL_ID': response.data['id'] - }, - ref: ref) - : null; - await getLabels(slug: slug, projID: projID); - labelState = StateEnum.success; - notifyListeners(); - } on DioException catch (e) { - log(e.error.toString()); - labelState = StateEnum.error; - notifyListeners(); - } - } - - Future getStates({ - required String slug, - required String projID, - bool showLoading = true, - }) async { - if (showLoading) { - statesState = StateEnum.loading; - notifyListeners(); - } - try { - final response = await DioConfig().dioServe( - hasAuth: true, - url: APIs.states - .replaceAll("\$SLUG", slug) - .replaceAll('\$PROJECTID', projID), - hasBody: false, - httpMethod: HttpMethod.get, - ); - statesData = response.data; - states = {}; - for (int i = 0; i < response.data.length; i++) { - final String state = response.data.keys.elementAt(i); - for (int j = 0; j < response.data[state].length; j++) { - states[response.data[state][j]['id']] = response.data[state][j]; - stateIcons[response.data[state][j]['id']] = SvgPicture.asset( - state == 'backlog' - ? 'assets/svg_images/circle.svg' - : state == 'cancelled' - ? 'assets/svg_images/cancelled.svg' - : state == 'completed' - ? 'assets/svg_images/done.svg' - : state == 'started' - ? 'assets/svg_images/in_progress.svg' - : 'assets/svg_images/unstarted.svg', - height: 22, - width: 22, - colorFilter: int.tryParse( - "FF${response.data[state][j]['color'].toString().replaceAll('#', '')}", - radix: 16) != - null - ? ColorFilter.mode( - Color(int.parse( - "FF${response.data[state][j]['color'].toString().replaceAll('#', '')}", - radix: 16)), - BlendMode.srcIn) - : null); - - if (response.data[state][j]['default'] == true) { - defaultStatedetails[state] = { - 'name': response.data[state][j]['name'], - 'icon': stateIcons[response.data[state][j]['id']] - }; - } - } - } - stateOrdering = []; - for (final element in defaultStateGroups) { - if (statesData[element] != null) { - for (final element in (statesData[element] as List)) { - stateOrdering.add(element['id']); - } - } - } - statesState = StateEnum.success; - notifyListeners(); - } on DioException catch (e) { - log(e.response!.statusCode.toString()); - if (e.response!.statusCode == 403) { - statesState = StateEnum.restricted; - notifyListeners(); - } else { - statesState = StateEnum.error; - notifyListeners(); - } - } - } - Future createIssue( {required String slug, required String projID, @@ -748,6 +540,7 @@ class IssuesProvider extends ChangeNotifier { required WidgetRef ref}) async { createIssueState = StateEnum.loading; final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final profileProvider = ref.read(ProviderList.profileProvider); notifyListeners(); try { final response = await DioConfig().dioServe( @@ -812,7 +605,8 @@ class IssuesProvider extends ChangeNotifier { .firstWhere((element) => element['id'] == projID)['name'], 'ISSUE_ID': response.data['id'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); // issuesResponse.add(response.data); if (issueCategory == IssueCategory.moduleIssues) { await ref.read(ProviderList.modulesProvider).createModuleIssues( @@ -825,10 +619,10 @@ class IssuesProvider extends ChangeNotifier { ); ref.read(ProviderList.modulesProvider).filterModuleIssues( - slug: slug, - projectId: - ref.read(ProviderList.projectProvider).currentProject["id"], - ); + slug: slug, + projectId: + ref.read(ProviderList.projectProvider).currentProject["id"], + ref: ref); filterIssues( slug: ref .read(ProviderList.workspaceProvider) @@ -844,10 +638,9 @@ class IssuesProvider extends ChangeNotifier { projId: projID, issues: [response.data['id']], ); - ref.read(ProviderList.cyclesProvider).filterCycleIssues( - slug: slug, - projectId: projID, - ); + ref + .read(ProviderList.cyclesProvider) + .filterCycleIssues(slug: slug, projectId: projID, ref: ref); filterIssues( slug: slug, projID: projID, @@ -877,7 +670,6 @@ class IssuesProvider extends ChangeNotifier { } Future getIssues({required String slug, required String projID}) async { - // issueState = StateEnum.loading; try { final response = await DioConfig().dioServe( hasAuth: true, @@ -887,7 +679,6 @@ class IssuesProvider extends ChangeNotifier { hasBody: false, httpMethod: HttpMethod.get, ); - issuesResponse = response.data; issuesList = response.data; isISsuesEmpty = issuesResponse.isEmpty; @@ -905,72 +696,14 @@ class IssuesProvider extends ChangeNotifier { } } - Future createState( - {required String slug, - required String projID, - required dynamic data}) async { - statesState = StateEnum.loading; - notifyListeners(); - try { - await DioConfig().dioServe( - hasAuth: true, - url: APIs.states - .replaceAll("\$SLUG", slug) - .replaceAll('\$PROJECTID', projID), - hasBody: true, - httpMethod: HttpMethod.post, - data: data); - getStates(slug: slug, projID: projID); - statesState = StateEnum.success; - notifyListeners(); - } on DioException catch (e) { - log(e.response.toString()); - statesState = StateEnum.error; - notifyListeners(); - } - } - - Future getProjectMembers({ - required String slug, - required String projID, - }) async { - membersState = StateEnum.loading; - //notifyListeners(); - try { - final response = await DioConfig().dioServe( - hasAuth: true, - url: APIs.projectMembers - .replaceAll("\$SLUG", slug) - .replaceAll('\$PROJECTID', projID), - hasBody: false, - httpMethod: HttpMethod.get, - ); - members = response.data; - for (final element in members) { - if (element["member"]['id'] == - ref!.read(ProviderList.profileProvider).userProfile.id) { - ref!.read(ProviderList.projectProvider).role = - roleParser(role: element["role"]); - break; - } - } - membersState = StateEnum.success; - notifyListeners(); - } on DioException catch (e) { - log(e.response.toString()); - membersState = StateEnum.error; - notifyListeners(); - } - } - - Future getIssueProperties({required Enum issueCategory}) async { + Future getIssueDisplayProperties({required Enum issueCategory}) async { final cyclesProvider = ref!.read(ProviderList.cyclesProvider); final modulesProvider = ref!.read(ProviderList.modulesProvider); issueState = StateEnum.loading; try { var response = await DioConfig().dioServe( hasAuth: true, - url: APIs.issueProperties + url: APIs.issueDisplayProperties .replaceAll( "\$SLUG", ref! @@ -985,7 +718,7 @@ class IssuesProvider extends ChangeNotifier { if (response.data.isEmpty) { response = await DioConfig().dioServe( hasAuth: true, - url: APIs.issueProperties + url: APIs.issueDisplayProperties .replaceAll( "\$SLUG", ref! @@ -1023,119 +756,121 @@ class IssuesProvider extends ChangeNotifier { } else { issueProperty = response.data; issues.displayProperties.assignee = - issueProperty['properties']['assignee']; + issueProperty['display_properties']['assignee']; issues.displayProperties.dueDate = - issueProperty['properties']['due_date']; - issues.displayProperties.id = issueProperty['properties']['key']; + issueProperty['display_properties']['due_date']; + issues.displayProperties.id = + issueProperty['display_properties']['key']; issues.displayProperties.label = - issueProperty['properties']['labels']; - issues.displayProperties.state = issueProperty['properties']['state']; + issueProperty['display_properties']['labels']; + issues.displayProperties.state = + issueProperty['display_properties']['state']; issues.displayProperties.subIsseCount = - issueProperty['properties']['sub_issue_count']; + issueProperty['display_properties']['sub_issue_count']; issues.displayProperties.linkCount = - issueProperty['properties']['link']; + issueProperty['display_properties']['link']; issues.displayProperties.attachmentCount = - issueProperty['properties']['attachment_count']; + issueProperty['display_properties']['attachment_count']; issues.displayProperties.priority = - issueProperty['properties']['priority']; + issueProperty['display_properties']['priority']; issues.displayProperties.estimate = - issueProperty['properties']['estimate']; + issueProperty['display_properties']['estimate']; issues.displayProperties.startDate = - issueProperty['properties']['start_date']; + issueProperty['display_properties']['start_date']; issues.displayProperties.createdOn = - issueProperty['properties']['created_on']; + issueProperty['display_properties']['created_on']; issues.displayProperties.updatedOn = - issueProperty['properties']['updated_on']; + issueProperty['display_properties']['updated_on']; } } else { if (issueCategory == IssueCategory.cycleIssues) { cyclesProvider.issueProperty = response.data; issues.displayProperties.assignee = - cyclesProvider.issueProperty['properties']['assignee']; + cyclesProvider.issueProperty['display_properties']['assignee']; cyclesProvider.issues.displayProperties.dueDate = - cyclesProvider.issueProperty['properties']['due_date']; + cyclesProvider.issueProperty['display_properties']['due_date']; cyclesProvider.issues.displayProperties.id = - cyclesProvider.issueProperty['properties']['key']; + cyclesProvider.issueProperty['display_properties']['key']; cyclesProvider.issues.displayProperties.label = - cyclesProvider.issueProperty['properties']['labels']; + cyclesProvider.issueProperty['display_properties']['labels']; cyclesProvider.issues.displayProperties.state = - cyclesProvider.issueProperty['properties']['state']; - cyclesProvider.issues.displayProperties.subIsseCount = - cyclesProvider.issueProperty['properties']['sub_issue_count']; + cyclesProvider.issueProperty['display_properties']['state']; + cyclesProvider.issues.displayProperties.subIsseCount = cyclesProvider + .issueProperty['display_properties']['sub_issue_count']; cyclesProvider.issues.displayProperties.linkCount = - cyclesProvider.issueProperty['properties']['link']; + cyclesProvider.issueProperty['display_properties']['link']; cyclesProvider.issues.displayProperties.attachmentCount = - cyclesProvider.issueProperty['properties']['attachment_count']; + cyclesProvider.issueProperty['display_properties'] + ['attachment_count']; cyclesProvider.issues.displayProperties.priority = - cyclesProvider.issueProperty['properties']['priority']; + cyclesProvider.issueProperty['display_properties']['priority']; cyclesProvider.issues.displayProperties.estimate = - cyclesProvider.issueProperty['properties']['estimate']; + cyclesProvider.issueProperty['display_properties']['estimate']; cyclesProvider.issues.displayProperties.startDate = - cyclesProvider.issueProperty['properties']['start_date']; - cyclesProvider.issues.displayProperties.createdOn = - cyclesProvider.issueProperty['properties']?['created_on'] ?? - false; - cyclesProvider.issues.displayProperties.updatedOn = - cyclesProvider.issueProperty['properties']['updated_on']; + cyclesProvider.issueProperty['display_properties']['start_date']; ref!.read(ProviderList.cyclesProvider).issues.displayProperties = cyclesProvider.issues.displayProperties; } else if (issueCategory == IssueCategory.moduleIssues) { modulesProvider.issueProperty = response.data; issues.displayProperties.assignee = - modulesProvider.issueProperty['properties']['assignee']; + modulesProvider.issueProperty['display_properties']['assignee']; modulesProvider.issues.displayProperties.dueDate = - modulesProvider.issueProperty['properties']['due_date']; + modulesProvider.issueProperty['display_properties']['due_date']; modulesProvider.issues.displayProperties.id = - modulesProvider.issueProperty['properties']['key']; + modulesProvider.issueProperty['display_properties']['key']; issues.displayProperties.label = - issueProperty['properties']['labels']; + issueProperty['display_properties']['labels']; modulesProvider.issues.displayProperties.state = - modulesProvider.issueProperty['properties']['state']; + modulesProvider.issueProperty['display_properties']['state']; modulesProvider.issues.displayProperties.subIsseCount = - modulesProvider.issueProperty['properties']['sub_issue_count']; + modulesProvider.issueProperty['display_properties'] + ['sub_issue_count']; modulesProvider.issues.displayProperties.linkCount = - modulesProvider.issueProperty['properties']['link']; + modulesProvider.issueProperty['display_properties']['link']; modulesProvider.issues.displayProperties.attachmentCount = - modulesProvider.issueProperty['properties']['attachment_count']; + modulesProvider.issueProperty['display_properties'] + ['attachment_count']; modulesProvider.issues.displayProperties.priority = - modulesProvider.issueProperty['properties']['priority']; + modulesProvider.issueProperty['display_properties']['priority']; modulesProvider.issues.displayProperties.estimate = - modulesProvider.issueProperty['properties']['estimate']; + modulesProvider.issueProperty['display_properties']['estimate']; modulesProvider.issues.displayProperties.startDate = - modulesProvider.issueProperty['properties']['start_date']; - modulesProvider.issues.displayProperties.createdOn = - modulesProvider.issueProperty['properties']?['created_on'] ?? - false; + modulesProvider.issueProperty['display_properties']['start_date']; + modulesProvider.issues.displayProperties.createdOn = modulesProvider + .issueProperty['display_properties']?['created_on'] ?? + false; modulesProvider.issues.displayProperties.updatedOn = - modulesProvider.issueProperty['properties']['updated_on']; + modulesProvider.issueProperty['display_properties']['updated_on']; ref!.read(ProviderList.modulesProvider).issues.displayProperties = modulesProvider.issues.displayProperties; } else { issueProperty = response.data; issues.displayProperties.assignee = - issueProperty['properties']['assignee']; + issueProperty['display_properties']['assignee']; issues.displayProperties.dueDate = - issueProperty['properties']['due_date']; - issues.displayProperties.id = issueProperty['properties']['key']; + issueProperty['display_properties']['due_date']; + issues.displayProperties.id = + issueProperty['display_properties']['key']; issues.displayProperties.label = - issueProperty['properties']['labels']; - issues.displayProperties.state = issueProperty['properties']['state']; + issueProperty['display_properties']['labels']; + issues.displayProperties.state = + issueProperty['display_properties']['state']; issues.displayProperties.subIsseCount = - issueProperty['properties']['sub_issue_count']; + issueProperty['display_properties']['sub_issue_count']; issues.displayProperties.linkCount = - issueProperty['properties']['link']; + issueProperty['display_properties']['link']; issues.displayProperties.attachmentCount = - issueProperty['properties']['attachment_count']; + issueProperty['display_properties']['attachment_count']; issues.displayProperties.priority = - issueProperty['properties']['priority']; + issueProperty['display_properties']['priority']; issues.displayProperties.estimate = - issueProperty['properties']['estimate']; + issueProperty['display_properties']['estimate']; issues.displayProperties.startDate = - issueProperty['properties']['start_date'] ?? false; + issueProperty['display_properties']['start_date'] ?? false; issues.displayProperties.createdOn = - issueProperty['properties']['created_on'] ?? false; + issueProperty['display_properties']['created_on'] ?? false; issues.displayProperties.updatedOn = - issueProperty['properties']['updated_on'] ?? false; + issueProperty['display_properties']['updated_on'] ?? false; // ref!.read(ProviderList.cyclesProvider).issues.displayProperties = issues.displayProperties; } @@ -1156,13 +891,11 @@ class IssuesProvider extends ChangeNotifier { }) async { final cyclesProvider = ref!.read(ProviderList.cyclesProvider); final modulesProvider = ref!.read(ProviderList.modulesProvider); - issuePropertyState = StateEnum.loading; - notifyListeners(); try { final response = await DioConfig().dioServe( hasAuth: true, url: - ("${APIs.issueProperties}${issueCategory == IssueCategory.cycleIssues ? cyclesProvider.issueProperty['id'] : issueCategory == IssueCategory.moduleIssues ? modulesProvider.issueProperty['id'] : issueProperty['id']}/") + ("${APIs.issueDisplayProperties}${issueCategory == IssueCategory.cycleIssues ? cyclesProvider.issueProperty['id'] : issueCategory == IssueCategory.moduleIssues ? modulesProvider.issueProperty['id'] : issueProperty['id']}/") .replaceAll( "\$SLUG", ref! @@ -1245,11 +978,11 @@ class IssuesProvider extends ChangeNotifier { "type": Issues.fromIssueType(issues.issueType), "show_empty_groups": showEmptyStates, if (!isArchive) - "layout": issues.projectView == ProjectView.kanban + "layout": issues.projectView == IssueLayout.kanban ? 'kanban' - : issues.projectView == ProjectView.list + : issues.projectView == IssueLayout.list ? 'list' - : issues.projectView == ProjectView.calendar + : issues.projectView == IssueLayout.calendar ? 'calendar' : 'spreadsheet', "sub_issue": false, @@ -1307,12 +1040,12 @@ class IssuesProvider extends ChangeNotifier { issueView = reset ? response.data["default_props"] : response.data["view_props"]; issues.projectView = issueView['display_filters']['layout'] == 'list' - ? ProjectView.list + ? IssueLayout.list : issueView['display_filters']['layout'] == 'calendar' - ? ProjectView.calendar + ? IssueLayout.calendar : issueView['display_filters']['layout'] == 'spreadsheet' - ? ProjectView.spreadsheet - : ProjectView.kanban; + ? IssueLayout.spreadsheet + : IssueLayout.kanban; issues.showSubIssues = issueView['display_filters']['sub_issue'] ?? true; issues.groupBY = Issues.toGroupBY(issueView["display_filters"]["group_by"]); @@ -1335,7 +1068,7 @@ class IssuesProvider extends ChangeNotifier { showEmptyStates = issueView["display_filters"]["show_empty_groups"]; if (issues.groupBY == GroupBY.none) { - issues.projectView = ProjectView.list; + issues.projectView = IssueLayout.list; } if (reset) { updateProjectView(); @@ -1344,52 +1077,51 @@ class IssuesProvider extends ChangeNotifier { notifyListeners(); } on DioException catch (e) { log(e.response.toString()); - issues.projectView = ProjectView.kanban; + issues.projectView = IssueLayout.kanban; projectViewState = StateEnum.error; notifyListeners(); } } + void applyIssueView() { + final projectMembers = + ref!.read(ProviderList.projectProvider).projectMembers; + final labelIds = + ref!.read(ProviderList.labelProvider.notifier).getLabelIds(); + final states = ref!.read(ProviderList.statesProvider).projectStates; + groupByResponse = IssueFilterHelper.organizeIssues( + issuesList, issues.groupBY, issues.orderBY, + labelIDs: labelIds, + memberIDs: + projectMembers.map((e) => e['member']['id'].toString()).toList(), + states: states); + } + Future filterIssues({ required String slug, required String projID, bool fromViews = false, bool isArchived = false, - String? cycleId, - String? moduleId, Enum issueCategory = IssueCategory.issues, }) async { - final cyclesProvider = ref!.read(ProviderList.cyclesProvider); - final modulesProvider = ref!.read(ProviderList.modulesProvider); - if (issueCategory == IssueCategory.issues) { - orderByState = StateEnum.loading; - notifyListeners(); - } - - // if(cycleIssues){ - // issues.groupBY = cyclesProvider.issues.groupBY; - // issues.orderBY = cyclesProvider.issues.orderBY; - // issues.issueType = cyclesProvider.issues.issueType; - // } - // else { - cyclesProvider.issues.groupBY = issues.groupBY; - cyclesProvider.issues.orderBY = issues.orderBY; - cyclesProvider.issues.issueType = issues.issueType; + final projectProvider = ref!.read(ProviderList.projectProvider); + final labelProvider = ref!.read(ProviderList.labelProvider.notifier); + final statesProvider = ref!.read(ProviderList.statesProvider); + orderByState = StateEnum.loading; + notifyListeners(); - modulesProvider.issues.groupBY = issues.groupBY; - modulesProvider.issues.orderBY = issues.orderBY; - modulesProvider.issues.issueType = issues.issueType; - // } if (issues.groupBY == GroupBY.labels) { - getLabels(slug: slug, projID: projID); + labelProvider.getProjectLabels(); } else if (issues.groupBY == GroupBY.createdBY) { - getProjectMembers(slug: slug, projID: projID); - } else if (issues.groupBY == GroupBY.state) { - getStates( + projectProvider.getProjectMembers( slug: slug, - projID: projID, - showLoading: false, + projId: projID, ); + } else if (issues.groupBY == GroupBY.state) { + ref!.read(ProviderList.statesProvider.notifier).getStates( + slug: slug, + projectId: projID, + ); } String url; @@ -1400,111 +1132,13 @@ class IssuesProvider extends ChangeNotifier { tempIssueType = issues.issueType; tempProjectView = issues.projectView; } - if (issues.issueType != IssueType.all) { - url = (issueCategory == IssueCategory.cycleIssues - ? APIs.orderByGroupByCycleIssues - : issueCategory == IssueCategory.moduleIssues - ? APIs.orderByGroupByModuleIssues - : isArchived - ? APIs.orderByGroupByTypeArchivedIssues - : APIs.orderByGroupByTypeIssues) - .replaceAll("\$SLUG", slug) - .replaceAll('\$PROJECTID', projID) - .replaceAll( - '\$CYCLEID', cycleId ?? cyclesProvider.currentCycle['id'] ?? '') - .replaceAll('\$MODULEID', - moduleId ?? modulesProvider.currentModule['id'] ?? '') - .replaceAll('\$ORDERBY', Issues.fromOrderBY(issues.orderBY)) - .replaceAll('\$GROUPBY', Issues.fromGroupBY(issues.groupBY)) - .replaceAll('\$TYPE', Issues.fromIssueType(issues.issueType)); - if (issues.filters.priorities.isNotEmpty) { - url = - '$url&priority=${issues.filters.priorities.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.states.isNotEmpty) { - url = - '$url&state=${issues.filters.states.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - // print(url); - } - if (issues.filters.assignees.isNotEmpty) { - url = - '$url&assignees=${issues.filters.assignees.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - // print(url); - } - if (issues.filters.createdBy.isNotEmpty) { - url = - '$url&created_by=${issues.filters.createdBy.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.labels.isNotEmpty) { - url = - '$url&labels=${issues.filters.labels.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - // print(url); - } - if (issues.filters.targetDate.isNotEmpty) { - url = - '$url&target_date=${issues.filters.targetDate.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.startDate.isNotEmpty) { - url = - '$url&start_date=${issues.filters.startDate.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } else { - url = url; - } - } else { - url = (issueCategory == IssueCategory.cycleIssues - ? APIs.orderByGroupByCycleIssues - : issueCategory == IssueCategory.moduleIssues - ? APIs.orderByGroupByModuleIssues - : isArchived - ? APIs.orderByGroupByTypeArchivedIssues - : APIs.orderByGroupByTypeIssues) - .replaceAll("\$SLUG", slug) - .replaceAll('\$PROJECTID', projID) - .replaceAll( - '\$CYCLEID', cycleId ?? cyclesProvider.currentCycle['id'] ?? '') - .replaceAll('\$MODULEID', - moduleId ?? modulesProvider.currentModule['id'] ?? '') - .replaceAll('\$ORDERBY', Issues.fromOrderBY(issues.orderBY)) - .replaceAll('\$GROUPBY', Issues.fromGroupBY(issues.groupBY)) - .replaceAll('\$TYPE', Issues.fromIssueType(issues.issueType)); - if (issues.filters.priorities.isNotEmpty) { - url = - '$url&priority=${issues.filters.priorities.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.states.isNotEmpty) { - url = - '$url&state=${issues.filters.states.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.assignees.isNotEmpty) { - url = - '$url&assignees=${issues.filters.assignees.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.createdBy.isNotEmpty) { - url = - '$url&created_by=${issues.filters.createdBy.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.labels.isNotEmpty) { - url = - '$url&labels=${issues.filters.labels.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.targetDate.isNotEmpty) { - url = - '$url&target_date=${issues.filters.targetDate.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } - if (issues.filters.startDate.isNotEmpty) { - url = - '$url&start_date=${issues.filters.startDate.toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', '')}'; - } else { - url = url; - } - } - url = '$url&sub_issue=${issues.showSubIssues}'; - if (issues.groupBY == GroupBY.none) { - url = url.replaceAll('&group_by=none', ''); - stateOrdering = ['All Issues']; - } - dynamic temp; + url = APIs.orderByGroupByTypeIssues + .replaceAll("\$SLUG", slug) + .replaceAll('\$PROJECTID', projID) + .replaceAll('\$TYPE', Issues.fromIssueType(issues.issueType)); + url = '$url${IssueFilterHelper.getFilterQueryParams(issues.filters)}'; + url = '$url&sub_issue=${issues.showSubIssues}'; try { final response = await DioConfig().dioServe( hasAuth: true, @@ -1512,102 +1146,20 @@ class IssuesProvider extends ChangeNotifier { hasBody: false, httpMethod: HttpMethod.get, ); - if (issueCategory == IssueCategory.cycleIssues) { - issuesList = []; - - if (issues.groupBY != GroupBY.none) { - temp = response.data; - for (final key in response.data.keys) { - issuesList.addAll(response.data[key]); - } - } else { - temp = {'All Issues': response.data}; - } - } else if (issueCategory == IssueCategory.moduleIssues) { - issuesList = []; - if (issues.groupBY != GroupBY.none) { - temp = response.data; - for (final key in response.data.keys) { - issuesList.addAll(response.data[key]); - } - } else { - temp = {'All Issues': response.data}; - } - } - - if (issueCategory == IssueCategory.issues) { - issuesResponse = []; - isISsuesEmpty = true; - shrinkStates = []; - issuesList = []; - if (issues.groupBY == GroupBY.none) { - if (response.data.isNotEmpty) { - isISsuesEmpty = false; - } - issuesList = response.data; - } else { - for (final key in response.data.keys) { - if (response.data[key].isNotEmpty) { - isISsuesEmpty = false; - } - issuesList.addAll(response.data[key]); - } - } - } - - // if(issueCategory == IssueCategory.archivedIssues){ - // archivedIssueResponse = []; - // isArchivedIssuesEmpty = true; - // archivedIssuesList = []; - - // for (final key in response.data.keys) { - // // log("KEY=$key"); - // if (response.data[key].isNotEmpty) { - // isArchivedIssuesEmpty = false; - // } - - // archivedIssuesList.addAll(response.data[key]); - // } - - // } - - //log("shrink states=${shrinkStates.toString()}"); + issuesList = response.data; + final projectLabelIds = labelProvider.getLabelIds(); + final organizedIssues = IssueFilterHelper.organizeIssues( + issuesList, issues.groupBY, issues.orderBY, + labelIDs: projectLabelIds, + memberIDs: projectProvider.projectMembers + .map((e) => e['member']['id'].toString()) + .toList(), + states: statesProvider.projectStates); if (issueCategory == IssueCategory.issues) { - if (issues.groupBY == GroupBY.state) { - groupByResponse = {}; - - if (issues.filters.states.isNotEmpty) { - for (final element in issues.filters.states) { - if (response.data[element] == null) { - groupByResponse[element] = []; - } else { - groupByResponse[element] = response.data[element]; - } - } - } else { - for (final element in stateOrdering) { - groupByResponse[element] = response.data[element] ?? []; - } - } - shrinkStates = List.generate(stateOrdering.length, (index) => false); - } else if (issues.groupBY == GroupBY.none) { - stateOrdering = ['All Issues']; - groupByResponse['All Issues'] = response.data; - } else { - stateOrdering = []; - response.data.forEach((key, value) { - stateOrdering.add(key); - }); - groupByResponse = response.data; - shrinkStates = List.generate(stateOrdering.length, (index) => false); - } - } - - if (issueCategory == IssueCategory.cycleIssues || + groupByResponse = organizedIssues; + } else if (issueCategory == IssueCategory.cycleIssues || issueCategory == IssueCategory.moduleIssues) { - cyclesProvider.stateOrdering = stateOrdering; - modulesProvider.stateOrdering = stateOrdering; - return temp; + return organizedIssues; } orderByState = StateEnum.success; notifyListeners(); diff --git a/lib/provider/label_provider.dart b/lib/provider/label_provider.dart new file mode 100644 index 00000000..fa519481 --- /dev/null +++ b/lib/provider/label_provider.dart @@ -0,0 +1,194 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/models/Project/Label/label.model.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/repository/labels.service.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/utils/global_functions.dart'; + +class LabelState { + // Properties + StateEnum labelState = StateEnum.empty; + Map projectLabels = {}; + Map workspaceLabels = {}; + + // Constructor + LabelState({ + required this.projectLabels, + required this.workspaceLabels, + required this.labelState, + }); + + // CopyWith + LabelState copyWith( + {StateEnum? labelState, + Map? projectLabels, + Map? workspaceLabels}) { + return LabelState( + labelState: labelState ?? this.labelState, + projectLabels: projectLabels ?? this.projectLabels, + workspaceLabels: workspaceLabels ?? this.workspaceLabels); + } + + // Initialize with empty states + factory LabelState.initialize() { + return LabelState( + projectLabels: {}, workspaceLabels: {}, labelState: StateEnum.empty); + } +} + +class LabelNotifier extends StateNotifier { + LabelNotifier(this.ref, this._labelsService) : super(LabelState.initialize()); + // Ref for accessing other providers + final Ref ref; + // Labels Service + final LabelsService _labelsService; + + // Get Label by Id + LabelModel? getLabelById(String id) { + return state.projectLabels[id]; + } + + // Get List of Label IDs I + List getLabelIds() { + return state.projectLabels.keys.toList(); + } + + // Get Project Labels + Future getProjectLabels() async { + state = state.copyWith(labelState: StateEnum.loading); + final projectID = + ref.read(ProviderList.projectProvider).currentProject['id']; + final slug = ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug; + final response = await _labelsService.getProjectLabels(slug, projectID); + response.fold( + (labels) { + state = state.copyWith( + projectLabels: labels, labelState: StateEnum.success); + }, + (err) { + state = state.copyWith(labelState: StateEnum.error); + }, + ); + } + + // Get Workspace Labels + Future getWorkspaceLabels() async { + state = state.copyWith(labelState: StateEnum.loading); + final slug = ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug; + final response = await _labelsService.getProjectLabels(slug, ''); + response.fold( + (labels) { + state = state.copyWith( + workspaceLabels: labels, labelState: StateEnum.success); + }, + (err) { + state = state.copyWith(labelState: StateEnum.error); + }, + ); + } + + // Create Label + Future createLabel(Map payload) async { + state = state.copyWith(labelState: StateEnum.loading); + final slug = ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug; + final projectID = + ref.read(ProviderList.projectProvider).currentProject['id']; + final response = await _labelsService.createLabel(slug, projectID, payload); + response.fold( + (label) { + state = state.copyWith(projectLabels: { + ...state.projectLabels, + label.id: label, + }, labelState: StateEnum.success); + sendPostHogEvent(method: CRUD.create, labelID: label.id); + }, + (err) { + state = state.copyWith(labelState: StateEnum.error); + }, + ); + } + + // Update Label + Future updateLabel(Map payload) async { + state = state.copyWith(labelState: StateEnum.loading); + final slug = ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug; + final projectID = + ref.read(ProviderList.projectProvider).currentProject['id']; + final response = await _labelsService.updateLabel(slug, projectID, payload); + response.fold( + (label) { + final projectLabels = state.projectLabels; + projectLabels[label.id] = LabelModel.fromJson({ + ...projectLabels[label.id]!.toJson(), + ...label.toJson(), + }); + state = state.copyWith( + projectLabels: projectLabels, labelState: StateEnum.success); + sendPostHogEvent(method: CRUD.update, labelID: label.id); + }, + (err) { + state = state.copyWith(labelState: StateEnum.error); + }, + ); + } + + // Delete Label + Future deleteLabel(String labelID) async { + state = state.copyWith(labelState: StateEnum.loading); + final slug = ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug; + final projectID = + ref.read(ProviderList.projectProvider).currentProject['id']; + final response = await _labelsService.deleteLabel(slug, projectID, labelID); + response.fold( + (label) { + state = state.copyWith( + projectLabels: state.projectLabels..remove(labelID), + labelState: StateEnum.success); + }, + (err) { + state = state.copyWith(labelState: StateEnum.error); + }, + ); + sendPostHogEvent(method: CRUD.delete, labelID: labelID); + } + + // SEND POSTHOG EVENT + void sendPostHogEvent({required CRUD method, required String labelID}) { + final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + final profileProvider = ref.read(ProviderList.profileProvider); + postHogService( + eventName: method == CRUD.create + ? 'ISSUE_LABEL_CREATE' + : method == CRUD.update + ? 'ISSUE_LABEL_UPDATE' + : method == CRUD.delete + ? 'ISSUE_LABEL_DELETE' + : '', + properties: { + 'WORKSPACE_ID': workspaceProvider.selectedWorkspace.workspaceId, + 'WORKSPACE_SLUG': workspaceProvider.selectedWorkspace.workspaceSlug, + 'WORKSPACE_NAME': workspaceProvider.selectedWorkspace.workspaceName, + 'PROJECT_ID': projectProvider.projectDetailModel!.id, + 'PROJECT_NAME': projectProvider.projectDetailModel!.name, + 'LABEL_ID': labelID + }, + userEmail: profileProvider.userProfile.email ?? '', + userID: profileProvider.userProfile.id ?? ''); + } +} diff --git a/lib/provider/modules_provider.dart b/lib/provider/modules_provider.dart index bec6bf48..0e70d79e 100644 --- a/lib/provider/modules_provider.dart +++ b/lib/provider/modules_provider.dart @@ -1,7 +1,5 @@ // ignore_for_file: use_build_context_synchronously - import 'dart:developer'; - import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,25 +7,24 @@ import 'package:plane/config/apis.dart'; import 'package:plane/config/const.dart'; import 'package:plane/kanban/models/inputs.dart'; import 'package:plane/models/issues.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/services/dio_service.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/global_functions.dart'; +import 'package:plane/utils/issues_filter/issue_filter.helper.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/issue_card_widget.dart'; -import '../screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class ModuleProvider with ChangeNotifier { ModuleProvider(ChangeNotifierProviderRef this.ref); Ref? ref; final modules = []; final favModules = []; Map createModule = {}; - int cycleDetailSelectedIndex = 0; Map moduleDetailsData = {}; List selectedIssues = []; int statusIndex = -1; @@ -70,7 +67,9 @@ class ModuleProvider with ChangeNotifier { List shrinkStates = []; List stateOrdering = []; Map filterIssues = {}; + List moduleIssuesList = []; Map issueProperty = {}; + Map moduleView = {}; bool showEmptyStates = true; bool isIssuesEmpty = false; int moduleDetailSelectedIndex = 0; @@ -81,6 +80,7 @@ class ModuleProvider with ChangeNotifier { StateEnum moduleIssueState = StateEnum.loading; StateEnum deleteModuleState = StateEnum.empty; StateEnum moduleLinkState = StateEnum.empty; + StateEnum moduleViewState = StateEnum.empty; void changeIndex(int index) { statusIndex = index; @@ -107,6 +107,11 @@ class ModuleProvider with ChangeNotifier { notifyListeners(); } + void changeState(StateEnum state) { + state = StateEnum.loading; + notifyListeners(); + } + Future getModules( {required String slug, required String projId, @@ -150,8 +155,9 @@ class ModuleProvider with ChangeNotifier { String? moduleId, required WidgetRef ref}) async { createModuleState = StateEnum.loading; - final workspaceProvider = ref.watch(ProviderList.workspaceProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); + final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + final profileProvider = ref.read(ProviderList.profileProvider); notifyListeners(); try { final response = await DioConfig().dioServe( @@ -174,7 +180,8 @@ class ModuleProvider with ChangeNotifier { 'PROJECT_NAME': projectProvider.projectDetailModel!.name, 'MODULE_ID': response.data['id'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); getModules(slug: slug, projId: projId); createModule.clear(); notifyListeners(); @@ -193,8 +200,9 @@ class ModuleProvider with ChangeNotifier { required Map data, bool disableLoading = false, required WidgetRef ref}) async { - final workspaceProvider = ref.watch(ProviderList.workspaceProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); + final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + final profileProvider = ref.read(ProviderList.profileProvider); if (!disableLoading) { moduleDetailState = StateEnum.loading; notifyListeners(); @@ -220,7 +228,8 @@ class ModuleProvider with ChangeNotifier { 'PROJECT_NAME': projectProvider.projectDetailModel!.name, 'MODULE_ID': response.data['id'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); getModuleDetails( slug: slug, projId: projId, @@ -297,7 +306,6 @@ class ModuleProvider with ChangeNotifier { required String moduleId, bool disableLoading = false, }) async { - log('${APIs.listModules.replaceAll('\$SLUG', slug).replaceAll('\$PROJECTID', projId)}$moduleId/'); if (!disableLoading) { moduleDetailState = StateEnum.loading; notifyListeners(); @@ -315,7 +323,6 @@ class ModuleProvider with ChangeNotifier { moduleDetailState = StateEnum.success; currentModule = response.data; notifyListeners(); - log('Module Details ===> ${response.data.toString()}'); } on DioException catch (e) { log(e.error.toString()); moduleDetailState = StateEnum.error; @@ -401,21 +408,212 @@ class ModuleProvider with ChangeNotifier { } } - Future filterModuleIssues({ - required String slug, - required String projectId, - // required Map data, - }) async { + Future getModuleView({bool reset = false, required String moduleId}) async { + moduleViewState = StateEnum.loading; + if (reset) { + notifyListeners(); + } + try { + var url = APIs.moduleIssueView + .replaceAll( + "\$SLUG", + ref! + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug) + .replaceAll('\$PROJECTID', + ref!.read(ProviderList.projectProvider).currentProject['id']) + .replaceAll('\$MODULEID', moduleId); + final response = await DioConfig().dioServe( + hasAuth: true, + url: url, + hasBody: false, + httpMethod: HttpMethod.get, + ); + moduleView = response.data; + issues.projectView = moduleView['display_filters']['layout'] == 'list' + ? IssueLayout.list + : moduleView['display_filters']['layout'] == 'calendar' + ? IssueLayout.calendar + : moduleView['display_filters']['layout'] == 'spreadsheet' + ? IssueLayout.spreadsheet + : IssueLayout.kanban; + issues.showSubIssues = moduleView['display_filters']['sub_issue'] ?? true; + issues.groupBY = + Issues.toGroupBY(moduleView["display_filters"]["group_by"]); + issues.orderBY = + Issues.toOrderBY(moduleView["display_filters"]["order_by"]); + issues.issueType = + Issues.toIssueType(moduleView["display_filters"]["type"]); + issues.filters.priorities = (moduleView["filters"]["priority"] == 'none' + ? [] + : moduleView["filters"]["priority"]) ?? + []; + issues.filters.states = moduleView["filters"]["state"] ?? []; + issues.filters.assignees = moduleView["filters"]["assignees"] ?? []; + issues.filters.createdBy = moduleView["filters"]["created_by"] ?? []; + issues.filters.labels = moduleView["filters"]["labels"] ?? []; + issues.filters.targetDate = moduleView["filters"]["target_date"] ?? []; + issues.filters.startDate = moduleView["filters"]["start_date"] ?? []; + issues.filters.subscriber = moduleView["filters"]["subscriber"] ?? []; + issues.filters.stateGroup = moduleView["filters"]["state_group"] ?? []; + showEmptyStates = moduleView["display_filters"]["show_empty_groups"]; + + if (issues.groupBY == GroupBY.none) { + issues.projectView = IssueLayout.list; + } + if (reset) { + updateModuleView(); + } + moduleViewState = StateEnum.success; + notifyListeners(); + } on DioException catch (e) { + log(e.response.toString()); + issues.projectView = IssueLayout.kanban; + moduleViewState = StateEnum.error; + notifyListeners(); + } + } + + Future updateModuleView( + {bool isArchive = false, bool setDefault = false}) async { + final Map view = { + "view_props": { + "calendarDateRange": "", + "collapsed": false, + "filterIssue": null, + "filters": { + // 'type': null, + // "priority": filterPriority, + if (issues.filters.priorities.isNotEmpty) + "priority": issues.filters.priorities, + if (issues.filters.states.isNotEmpty) "state": issues.filters.states, + if (issues.filters.assignees.isNotEmpty) + "assignees": issues.filters.assignees, + if (issues.filters.createdBy.isNotEmpty) + "created_by": issues.filters.createdBy, + if (issues.filters.labels.isNotEmpty) "labels": issues.filters.labels, + if (issues.filters.targetDate.isNotEmpty) + "target_date": issues.filters.targetDate, + if (issues.filters.startDate.isNotEmpty) + "start_date": issues.filters.startDate, + if (issues.filters.subscriber.isNotEmpty) + "subscriber": issues.filters.subscriber, + if (issues.filters.stateGroup.isNotEmpty) + "state_group": issues.filters.stateGroup, + }, + "display_filters": { + "group_by": Issues.fromGroupBY(issues.groupBY), + "order_by": Issues.fromOrderBY(issues.orderBY), + "type": Issues.fromIssueType(issues.issueType), + "show_empty_groups": showEmptyStates, + if (!isArchive) + "layout": issues.projectView == IssueLayout.kanban + ? 'kanban' + : issues.projectView == IssueLayout.list + ? 'list' + : issues.projectView == IssueLayout.calendar + ? 'calendar' + : 'spreadsheet', + "sub_issue": false, + }, + } + }; + if (setDefault) { + view['default_props'] = view['view_props']; + } + try { + await DioConfig().dioServe( + hasAuth: true, + url: APIs.moduleIssueView + .replaceAll( + "\$SLUG", + ref! + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug) + .replaceAll('\$PROJECTID', + ref!.read(ProviderList.projectProvider).currentProject['id']) + .replaceAll('\$MODULEID', currentModule['id']), + hasBody: true, + data: view, + httpMethod: HttpMethod.patch, + ); + moduleViewState = StateEnum.success; + notifyListeners(); + } on DioException catch (e) { + log(e.response.toString()); + moduleViewState = StateEnum.error; + notifyListeners(); + } + notifyListeners(); + } + + void applyModuleIssuesView({required WidgetRef ref}) { + final labelIds = + ref.read(ProviderList.labelProvider.notifier).getLabelIds(); + final projectMembers = + ref.read(ProviderList.projectProvider).projectMembers; + + final states = ref.read(ProviderList.statesProvider).projectStates; + filterIssues = IssueFilterHelper.organizeIssues( + moduleIssuesList, issues.groupBY, issues.orderBY, + labelIDs: labelIds, + memberIDs: + projectMembers.map((e) => e['member']['id'].toString()).toList(), + states: states); + notifyListeners(); + } + + Future filterModuleIssues( + {required String slug, + required String projectId, + String? moduleID, + required WidgetRef ref + // required Map data, + }) async { moduleIssueState = StateEnum.loading; notifyListeners(); - try { - final issuesProvider = ref!.read(ProviderList.issuesProvider); - filterIssues = await issuesProvider.filterIssues( + final projectProvider = ref.read(ProviderList.projectProvider); + final statesProvider = ref.read(ProviderList.statesProvider.notifier); + final labelNotifier = ref.read(ProviderList.labelProvider.notifier); + + final states = ref.read(ProviderList.statesProvider).projectStates; + if (issues.groupBY == GroupBY.labels) { + labelNotifier.getProjectLabels(); + } else if (issues.groupBY == GroupBY.createdBY) { + projectProvider.getProjectMembers(slug: slug, projId: projectId); + } else if (issues.groupBY == GroupBY.state) { + statesProvider.getStates( slug: slug, - projID: projectId, - issueCategory: IssueCategory.moduleIssues, + projectId: projectId, ); + } + String url; + url = APIs.orderByGroupByModuleIssues + .replaceAll("\$SLUG", slug) + .replaceAll('\$PROJECTID', projectId) + .replaceAll('\$MODULEID', moduleID!) + .replaceAll('\$TYPE', Issues.fromIssueType(issues.issueType)); + url = '$url${IssueFilterHelper.getFilterQueryParams(issues.filters)}'; + url = '$url&sub_issue=${issues.showSubIssues}&show_empty_groups=true'; + try { + final response = await DioConfig().dioServe( + hasAuth: true, + url: url, + hasBody: false, + httpMethod: HttpMethod.get, + ); + moduleIssuesList = response.data; + final organizedIssues = IssueFilterHelper.organizeIssues( + moduleIssuesList, issues.groupBY, issues.orderBY, + labelIDs: labelNotifier.getLabelIds(), + memberIDs: projectProvider.projectMembers + .map((e) => e['member']['id'].toString()) + .toList(), + states: states); + filterIssues = organizedIssues; issuesResponse = []; isIssuesEmpty = true; if (issues.groupBY != GroupBY.none) { @@ -428,22 +626,20 @@ class ModuleProvider with ChangeNotifier { } else { isIssuesEmpty = filterIssues.values.first.isEmpty; } - if (issues.groupBY == GroupBY.state) { - issuesProvider.states.forEach((key, value) { + states.forEach((key, value) { if (issues.filters.states.isEmpty && filterIssues[key] == null) { filterIssues[key] = []; } shrinkStates.add(false); }); - } else if (issues.groupBY != GroupBY.none) { - stateOrdering = []; + } else { shrinkStates = []; filterIssues.forEach((key, value) { - stateOrdering.add(key); shrinkStates.add(false); }); } + initializeBoard(); moduleIssueState = StateEnum.success; notifyListeners(); @@ -457,18 +653,18 @@ class ModuleProvider with ChangeNotifier { List initializeBoard() { final themeProvider = ref!.read(ProviderList.themeProvider); final issuesProvider = ref!.read(ProviderList.issuesProvider); + final projectProvider = ref!.read(ProviderList.projectProvider); + final states = ref!.read(ProviderList.statesProvider).projectStates; int count = 0; - issuesResponse = []; + // log(issues.groupBY.name); issues.issues = []; + issuesResponse = []; for (int j = 0; j < filterIssues.length; j++) { final List items = []; - - for (int i = 0; - filterIssues[stateOrdering[j]] != null && - i < filterIssues[stateOrdering[j]]!.length; - i++) { - issuesResponse.add(filterIssues[stateOrdering[j]]![i]); - + final groupedIssues = filterIssues.values.toList()[j]; + final groupID = filterIssues.keys.elementAt(j); + for (int i = 0; i < groupedIssues.length; i++) { + issuesResponse.add(groupedIssues[i]); items.add( IssueCardWidget( from: PreviousScreen.modules, @@ -478,49 +674,39 @@ class ModuleProvider with ChangeNotifier { ), ); } - Map label = {}; String userName = ''; - - bool labelFound = false; bool userFound = false; + final label = + ref!.read(ProviderList.labelProvider.notifier).getLabelById(groupID); - for (int i = 0; i < issuesProvider.labels.length; i++) { - if (stateOrdering[j] == issuesProvider.labels[i]['id']) { - label = issuesProvider.labels[i]; - labelFound = true; - break; - } - } - - for (int i = 0; i < issuesProvider.members.length; i++) { - if (stateOrdering[j] == issuesProvider.members[i]['member']['id']) { - userName = issuesProvider.members[i]['member']['first_name'] + + for (int i = 0; i < projectProvider.projectMembers.length; i++) { + if (groupID == projectProvider.projectMembers[i]['member']['id']) { + userName = projectProvider.projectMembers[i]['member']['first_name'] + ' ' + - issuesProvider.members[i]['member']['last_name']; + projectProvider.projectMembers[i]['member']['last_name']; userName = userName.trim().isEmpty - ? issuesProvider.members[i]['member']['email'] + ? projectProvider.projectMembers[i]['member']['email'] : userName; userFound = true; break; } } var title = issues.groupBY == GroupBY.priority - ? stateOrdering[j] + ? groupID : issues.groupBY == GroupBY.state - ? issuesProvider.states[stateOrdering[j]]['name'] - : stateOrdering[j]; + ? states[groupID]!.name + : groupID; issues.issues.add(BoardListsData( - id: stateOrdering[j], + id: groupID, items: items, shrink: shrinkStates[j], index: j, - width: issuesProvider.issues.projectView == ProjectView.list + width: issuesProvider.issues.projectView == IssueLayout.list ? MediaQuery.of(Const.globalKey.currentContext!).size.width : 300, // shrink: shrinkissuesProvider.states[count++], - title: issues.groupBY == GroupBY.labels && labelFound - ? label['name'][0].toString().toUpperCase() + - label['name'].toString().substring(1) + title: issues.groupBY == GroupBY.labels && label != null + ? label.name[0].toUpperCase() + label.name.substring(1) : userFound && (issues.groupBY == GroupBY.createdBY || issues.groupBY == GroupBY.assignees) @@ -529,7 +715,7 @@ class ModuleProvider with ChangeNotifier { : title = title[0].toString().toUpperCase() + title.toString().substring(1), header: Text( - stateOrdering[j], + groupID, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -549,6 +735,8 @@ class ModuleProvider with ChangeNotifier { } for (final element in issues.issues) { + // log(issues.groupBY.toString()); + element.leading = issues.groupBY == GroupBY.priority ? element.title == 'Urgent' ? Icon( @@ -602,7 +790,7 @@ class ModuleProvider with ChangeNotifier { element.title.toString().toUpperCase()[0], fontSize: 12, color: Colors.white, - fontWeight: FontWeightt.Semibold, + fontWeight: FontWeightt.Medium, ), ) : issues.groupBY == GroupBY.labels @@ -679,7 +867,7 @@ class ModuleProvider with ChangeNotifier { Const.globalKey.currentContext!, MaterialPageRoute( builder: (ctx) => CreateIssue( - moduleId: currentModule['id'], + cycleId: currentModule['id'], ))); }, child: Icon( @@ -735,24 +923,6 @@ class ModuleProvider with ChangeNotifier { }); filterIssues[stateOrdering[newListIndex]][newCardIndex] = response.data; - final List labelDetails = []; - final issuesProvider = ref!.read(ProviderList.issuesProvider); - filterIssues[stateOrdering[newListIndex]][newCardIndex]['labels'] - .forEach((element) { - for (int i = 0; i < issuesProvider.labels.length; i++) { - if (issuesProvider.labels[i]['id'] == element) { - labelDetails.add(issuesProvider.labels[i]); - break; - } - } - - // labelDetails.add(labels.firstWhere((e) => e['id'] == element)); - }); - - filterIssues[stateOrdering[newListIndex]][newCardIndex]['label_details'] = - labelDetails; - - log(response.data.toString()); if (issues.groupBY == GroupBY.priority) { log(filterIssues[stateOrdering[newListIndex]][newCardIndex]['name']); filterIssues[stateOrdering[newListIndex]][newCardIndex]['priority'] = @@ -774,7 +944,6 @@ class ModuleProvider with ChangeNotifier { } }); } - log("ISSUE REPOSITIONED"); notifyListeners(); } on DioException catch (err) { (filterIssues[stateOrdering[oldListIndex]] as List).insert(oldCardIndex, diff --git a/lib/provider/my_issues_provider.dart b/lib/provider/my_issues_provider.dart index 1e61c576..14925a4f 100644 --- a/lib/provider/my_issues_provider.dart +++ b/lib/provider/my_issues_provider.dart @@ -9,16 +9,14 @@ import 'package:plane/kanban/models/inputs.dart'; import 'package:plane/models/issues.dart'; import 'package:plane/provider/profile_provider.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; -//import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/states_pages.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/services/dio_service.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/issue_card_widget.dart'; -import '../screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class MyIssuesProvider extends ChangeNotifier { MyIssuesProvider(ChangeNotifierProviderRef this.ref); Ref? ref; @@ -73,7 +71,7 @@ class MyIssuesProvider extends ChangeNotifier { issues = Issues( showSubIssues: true, issues: [], - projectView: ProjectView.kanban, + projectView: IssueLayout.kanban, groupBY: GroupBY.state, orderBY: OrderBY.manual, issueType: IssueType.all, @@ -141,12 +139,12 @@ class MyIssuesProvider extends ChangeNotifier { ); myIssueView = response.data["view_props"]; issues.projectView = myIssueView["display_filters"]['layout'] == 'list' - ? ProjectView.list + ? IssueLayout.list : myIssueView["display_filters"]['layout'] == 'calendar' - ? ProjectView.calendar + ? IssueLayout.calendar : myIssueView["display_filters"]['layout'] == 'spreadsheet' - ? ProjectView.spreadsheet - : ProjectView.kanban; + ? IssueLayout.spreadsheet + : IssueLayout.kanban; issues.groupBY = Issues.toGroupBY(myIssueView["display_filters"]['group_by']); issues.orderBY = @@ -195,7 +193,7 @@ class MyIssuesProvider extends ChangeNotifier { notifyListeners(); } on DioException catch (e) { log("MY ISSUES:${e.response}"); - issues.projectView = ProjectView.kanban; + issues.projectView = IssueLayout.kanban; myIssuesViewState = StateEnum.error; notifyListeners(); } @@ -519,7 +517,7 @@ class MyIssuesProvider extends ChangeNotifier { items: items, shrink: j >= shrinkStates.length ? false : shrinkStates[j], index: j, - width: issues.projectView == ProjectView.list + width: issues.projectView == IssueLayout.list ? MediaQuery.of(Const.globalKey.currentContext!).size.width : (width > 500 ? 400 : width * 0.8), // shrink: shrinkStates[count++], @@ -792,7 +790,6 @@ class MyIssuesProvider extends ChangeNotifier { 'priority': stateOrdering[newListIndex].toString().toLowerCase(), }); - // log(response.data.toString()); if (issues.groupBY == GroupBY.priority) { log(groupByResponse[stateOrdering[newListIndex]][newCardIndex]['name']); groupByResponse[stateOrdering[newListIndex]][newCardIndex]['priority'] = @@ -816,7 +813,6 @@ class MyIssuesProvider extends ChangeNotifier { }); } - log("ISSUE REPOSITIONED"); notifyListeners(); } on DioException catch (err) { (groupByResponse[stateOrdering[oldListIndex]] as List).insert( @@ -862,11 +858,11 @@ class MyIssuesProvider extends ChangeNotifier { "start_date": issues.filters.startDate, }, "display_filters": { - "layout": issues.projectView == ProjectView.kanban + "layout": issues.projectView == IssueLayout.kanban ? 'kanban' - : issues.projectView == ProjectView.list + : issues.projectView == IssueLayout.list ? 'list' - : issues.projectView == ProjectView.calendar + : issues.projectView == IssueLayout.calendar ? 'calendar' : 'spreadsheet', "group_by": Issues.fromGroupBY(issues.groupBY), diff --git a/lib/provider/page_provider.dart b/lib/provider/page_provider.dart index 8bef8380..2a0565e8 100644 --- a/lib/provider/page_provider.dart +++ b/lib/provider/page_provider.dart @@ -69,6 +69,7 @@ class PageProvider with ChangeNotifier { required String projectId}) async { final workspaceProvider = ref.watch(ProviderList.workspaceProvider); final projectProvider = ref.watch(ProviderList.projectProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); try { if (httpMethod != HttpMethod.get || httpMethod == HttpMethod.delete) { if (httpMethod == HttpMethod.delete) { @@ -130,7 +131,8 @@ class PageProvider with ChangeNotifier { 'PAGE_ID': pageID, 'BLOCK_ID': res.data['id'] }, - ref: ref) + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!) : null; if (httpMethod == HttpMethod.delete) { blocks.removeWhere((element) => element['id'] == blockID); @@ -241,8 +243,9 @@ class PageProvider with ChangeNotifier { bool? fromDispose = false}) async { blockSheetState = StateEnum.loading; notifyListeners(); - final workspaceProvider = ref.watch(ProviderList.workspaceProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); + final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + final profileProvider = ref.read(ProviderList.profileProvider); try { final res = await DioConfig().dioServe( httpMethod: HttpMethod.patch, @@ -261,7 +264,8 @@ class PageProvider with ChangeNotifier { 'PROJECT_NAME': projectProvider.projectDetailModel!.name, 'PAGE_ID': res.data['id'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); final int index = pages[selectedFilter]! .indexWhere((element) => element["id"] == pageId); pagesListState = StateEnum.success; @@ -338,8 +342,9 @@ class PageProvider with ChangeNotifier { required WidgetRef ref}) async { pagesListState = StateEnum.loading; setState(); - final workspaceProvider = ref.watch(ProviderList.workspaceProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); + final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + final profileProvider = ref.read(ProviderList.profileProvider); try { final response = await DioConfig().dioServe( httpMethod: HttpMethod.post, @@ -358,7 +363,8 @@ class PageProvider with ChangeNotifier { 'PROJECT_NAME': projectProvider.projectDetailModel!.name, 'PAGE_ID': response.data['id'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); updatepageList( slug: slug, projectId: projectId, diff --git a/lib/provider/project_state_provider.dart b/lib/provider/project_state_provider.dart new file mode 100644 index 00000000..8fb30d3b --- /dev/null +++ b/lib/provider/project_state_provider.dart @@ -0,0 +1,190 @@ +import 'dart:developer'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/models/project/state/state_model.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/repository/project_state_service.dart'; +import 'package:plane/utils/enums.dart'; + +final defaultStateGroups = [ + 'backlog', + 'unstarted', + 'started', + 'completed', + 'cancelled', +]; + +class StatesData { + StatesData( + {required this.projectStates, + required this.statesState, + required this.createStateLoading, + required this.updateState, + required this.deleteState, + required this.stateGroups}); + + factory StatesData.initialize() { + return StatesData( + projectStates: {}, + statesState: StateEnum.empty, + createStateLoading: StateEnum.empty, + updateState: StateEnum.empty, + deleteState: StateEnum.empty, + stateGroups: {}); + } + + StatesData copyWith( + {Map? states, + StateEnum? statesState, + StateEnum? createStateLoading, + StateEnum? updateState, + StateEnum? deleteState, + Map>? stateGroups}) { + return StatesData( + projectStates: states ?? projectStates, + statesState: statesState ?? this.statesState, + stateGroups: stateGroups ?? this.stateGroups, + createStateLoading: createStateLoading ?? this.createStateLoading, + updateState: updateState ?? this.updateState, + deleteState: deleteState ?? this.deleteState); + } + + Map projectStates = {}; + Map> stateGroups = {}; + StateEnum statesState = StateEnum.empty; + StateEnum createStateLoading = StateEnum.empty; + StateEnum updateState = StateEnum.empty; + StateEnum deleteState = StateEnum.empty; +} + +class StatesProvider extends StateNotifier { + StatesProvider(this.ref, this.statesService) : super(StatesData.initialize()); + Ref ref; + StatesService statesService; + + Future getStates({required String slug, required String projectId}) async { + state = state.copyWith(statesState: StateEnum.loading); + final states = + await statesService.getStates(slug: slug, projectId: projectId); + states.fold( + (states) { + state = state.copyWith(states: states, statesState: StateEnum.success); + getGroupedStates(); + }, + (err) { + if (err.response!.statusCode == 403) { + state = state.copyWith(statesState: StateEnum.restricted); + } else { + state = state.copyWith(statesState: StateEnum.error); + } + }, + ); + } + + void getGroupedStates() { + state = state.copyWith(stateGroups: { + for (final stateGroup in defaultStateGroups) + stateGroup: state.projectStates.values.where((state) { + return state.group == stateGroup; + }).toList() + }); + } + + Future createState({required Map data}) async { + final projectId = + ref.read(ProviderList.projectProvider).currentProject['id']; + final slug = ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug; + state = state.copyWith(createStateLoading: StateEnum.loading); + final newState = await statesService.createState( + data: data, slug: slug, projectId: projectId); + newState.fold((addedState) { + final projectStates = state.projectStates; + final newStateGroupData = state.stateGroups; + projectStates.addEntries({addedState.id: addedState}.entries); + newStateGroupData.update( + addedState.group, + (existingStates) => [...existingStates, addedState], + ifAbsent: () => [addedState], + ); + state = state.copyWith( + stateGroups: newStateGroupData, + states: projectStates, + createStateLoading: StateEnum.success); + }, (err) { + log(err.response.toString()); + state = state.copyWith(createStateLoading: StateEnum.error); + }); + } + + Future updateState({ + required Map data, + required String slug, + required String projectId, + required String stateId, + }) async { + state = state.copyWith(updateState: StateEnum.loading); + final response = await statesService.updateState( + data: data, slug: slug, projectId: projectId, stateId: stateId); + response.fold((updateState) { + final projectStates = state.projectStates; + projectStates[updateState.id] = updateState; + final Map> updatedGroups = { + ...state.stateGroups + }; + final String groupId = updateState.group; + final List? groupStates = updatedGroups[groupId]; + if (groupStates != null) { + final int indexToUpdate = + groupStates.indexWhere((s) => s.id == updateState.id); + if (indexToUpdate != -1) { + groupStates[indexToUpdate] = updateState; + updatedGroups[groupId] = groupStates; + state = state.copyWith( + states: projectStates, + stateGroups: updatedGroups, + updateState: StateEnum.success); + } + } + }, (err) { + state = state.copyWith(updateState: StateEnum.error); + log(err.response.toString()); + }); + } + + Future deleteState({required String stateId}) async { + state = state.copyWith(deleteState: StateEnum.loading); + final delete = await statesService.deleteState( + slug: ref + .watch(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: ref.watch(ProviderList.projectProvider).currentProject['id'], + stateId: stateId, + ); + + delete.fold((deleted) { + final projectStates = state.projectStates; + projectStates.removeWhere((key, value) => key == stateId); + final Map> updatedGroups = { + ...state.stateGroups + }; + updatedGroups.forEach((groupId, groupStates) { + final int indexToRemove = + groupStates.indexWhere((s) => s.id == stateId); + if (indexToRemove != -1) { + groupStates.removeAt(indexToRemove); + updatedGroups[groupId] = groupStates; + } + }); + state = state.copyWith( + states: projectStates, + stateGroups: updatedGroups, + deleteState: StateEnum.success); + }, (err) { + state = state.copyWith(deleteState: StateEnum.error); + log(err.response.toString()); + }); + } +} diff --git a/lib/provider/projects_provider.dart b/lib/provider/projects_provider.dart index dccaf289..de1b1497 100644 --- a/lib/provider/projects_provider.dart +++ b/lib/provider/projects_provider.dart @@ -7,17 +7,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/kanban/models/project_detail_model.dart'; import 'package:plane/provider/provider_list.dart'; +import 'package:plane/repository/projects_service.dart'; +import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/config/apis.dart'; import 'package:plane/services/dio_service.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/global_functions.dart'; - import '../models/issues.dart'; class ProjectsProvider extends ChangeNotifier { - ProjectsProvider(ChangeNotifierProviderRef? this.ref); - final Ref? ref; + ProjectsProvider(ChangeNotifierProviderRef this.ref); + final Ref ref; List projects = []; List starredProjects = []; StateEnum joinprojectState = StateEnum.empty; @@ -70,8 +71,7 @@ class ProjectsProvider extends ChangeNotifier { currentProject = {}; loadingProjectIndex = []; - coverUrl = - "https://app.plane.so/_next/image?url=https%3A%2F%2Fimages.unsplash.com%2Fphoto-1575116464504-9e7652fddcb3%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMnwyODUyNTV8MHwxfHNlYXJjaHwxOHx8cGxhbmV8ZW58MHx8fHwxNjgxNDY4NTY5%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D1080&w=1920&q=75"; + coverUrl = ""; projectDetailModel = null; memberCount = 0; lead.clear(); @@ -86,12 +86,13 @@ class ProjectsProvider extends ChangeNotifier { {Filters? filters, bool fromViews = false, required WidgetRef ref}) async { - final prov = ref.read(ProviderList.issuesProvider); + final issuesProv = ref.read(ProviderList.issuesProvider); final moduleProv = ref.read(ProviderList.modulesProvider); final viewsProvider = ref.read(ProviderList.viewsProvider.notifier); final intergrationProvider = ref.read(ProviderList.integrationProvider); final workspaceProvider = ref.read(ProviderList.workspaceProvider); - final pageProv = ref.read(ProviderList.pageProvider); + final statesProvider = ref.watch(ProviderList.statesProvider.notifier); + // final pageProv = ref.read(ProviderList.pageProvider); if (currentProject['estimate'] != null && currentProject['estimate'] != '') { // prov.issues.displayProperties.estimate = true; @@ -101,21 +102,22 @@ class ProjectsProvider extends ChangeNotifier { .selectedWorkspace .workspaceSlug; - prov.getStates(slug: workspaceSlug, projID: currentProject['id']); - prov.getProjectMembers( + statesProvider.getStates( + slug: workspaceSlug, projectId: currentProject['id']); + getProjectMembers( slug: workspaceSlug, - projID: currentProject['id'], + projId: currentProject['id'], ); ref.read(ProviderList.estimatesProvider).getEstimates( slug: workspaceSlug, projID: currentProject['id'], ); - prov.getIssueProperties(issueCategory: IssueCategory.issues); - prov.getProjectView().then((value) { + issuesProv.getIssueDisplayProperties(issueCategory: IssueCategory.issues); + issuesProv.getProjectView().then((value) { if (filters != null) { - prov.issues.filters = filters; + issuesProv.issues.filters = filters; } - prov.filterIssues( + issuesProv.filterIssues( fromViews: fromViews, slug: workspaceSlug, projID: currentProject['id'], @@ -128,7 +130,8 @@ class ProjectsProvider extends ChangeNotifier { ); viewsProvider.getViews(); - prov.getLabels(slug: workspaceSlug, projID: currentProject['id']); + // Fetching labels + ref.read(ProviderList.labelProvider.notifier).getProjectLabels(); getProjectDetails(slug: workspaceSlug, projId: currentProject['id']); @@ -172,10 +175,10 @@ class ProjectsProvider extends ChangeNotifier { cycleId: '', ref: ref); - pageProv.updatepageList( - slug: workspaceSlug, - projectId: projectID, - ); + // pageProv.updatepageList( + // slug: workspaceSlug, + // projectId: projectID, + // ); if (workspaceProvider.githubIntegration != null) { intergrationProvider.getSlackIntegration( @@ -343,7 +346,8 @@ class ProjectsProvider extends ChangeNotifier { WidgetRef? ref, BuildContext? context}) async { createProjectState = StateEnum.loading; - final workspaceProvider = ref!.watch(ProviderList.workspaceProvider); + final workspaceProvider = ref!.read(ProviderList.workspaceProvider); + final profileProvider = ref.read(ProviderList.profileProvider); notifyListeners(); try { final response = await DioConfig().dioServe( @@ -362,7 +366,8 @@ class ProjectsProvider extends ChangeNotifier { 'PROJECT_ID': response.data['id'], 'PROJECT_NAME': response.data['name'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); await getProjects(slug: slug); createProjectState = StateEnum.success; notifyListeners(); @@ -437,7 +442,8 @@ class ProjectsProvider extends ChangeNotifier { required Map data, required WidgetRef ref}) async { updateProjectState = StateEnum.loading; - final workspaceProvider = ref.watch(ProviderList.workspaceProvider); + final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final profileProvider = ref.read(ProviderList.profileProvider); notifyListeners(); log("${APIs.listProjects.replaceAll('\$SLUG', slug)}$projId/"); try { @@ -454,8 +460,6 @@ class ProjectsProvider extends ChangeNotifier { projectDetailModel!.projectLead = projectLead; final int index = currentProject["index"]; currentProject = projectDetailModel!.toJson(); - log('CURRENT PROJECT'); - log(currentProject.toString()); currentProject["index"] = index; projects[index]["name"] = currentProject["name"]; projects[index]["description"] = currentProject["description"]; @@ -479,7 +483,8 @@ class ProjectsProvider extends ChangeNotifier { 'PROJECT_ID': response.data['id'], 'PROJECT_NAME': response.data['name'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); notifyListeners(); } on DioException catch (e) { log(e.toString()); @@ -545,8 +550,10 @@ class ProjectsProvider extends ChangeNotifier { } } - Future getProjectMembers( - {required String slug, required String projId}) async { + Future getProjectMembers({ + required String slug, + required String projId, + }) async { // projectDetailState = AuthStateEnum.loading; // notifyListeners(); try { @@ -558,8 +565,21 @@ class ProjectsProvider extends ChangeNotifier { hasBody: false, httpMethod: HttpMethod.get, ); - projectMembers = response.data; + + for (final element in projectMembers) { + for (final workspace + in ref.watch(ProviderList.workspaceProvider).workspaceMembers) { + if (element['member'] == workspace['member']['id']) { + // Replace the map in projectMembers with the one from workspaceMembers + projectMembers[projectMembers.indexOf(element)] = workspace; + } + if (element["member"] == + ref.read(ProviderList.profileProvider).userProfile.id) { + role = roleParser(role: element["role"]); + } + } + } projectMembersState = StateEnum.success; notifyListeners(); } on DioException catch (e) { @@ -657,73 +677,17 @@ class ProjectsProvider extends ChangeNotifier { } } - Future stateCrud( - {required String slug, - required String projId, - required String stateId, - required CRUD method, - required BuildContext context, - required WidgetRef ref, - required Map data}) async { - final workspaceProvider = ref.watch(ProviderList.workspaceProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); - try { - final url = method == CRUD.update || method == CRUD.delete - ? '${APIs.states.replaceFirst('\$SLUG', slug).replaceFirst('\$PROJECTID', projId)}$stateId/' - : APIs.states - .replaceFirst('\$SLUG', slug) - .replaceFirst('\$PROJECTID', projId); - stateCrudState = StateEnum.loading; + Future getUnsplashImages() async { + unsplashImageState = StateEnum.loading; + notifyListeners(); + final response = await ProjectsService().getUnspalshImages(); + response.fold((images) { + // unsplashImages = images; + unsplashImageState = StateEnum.success; notifyListeners(); - final response = await DioConfig().dioServe( - hasAuth: true, - url: url, - hasBody: true, - httpMethod: method == CRUD.create - ? HttpMethod.post - : method == CRUD.update - ? HttpMethod.put - : method == CRUD.delete - ? HttpMethod.delete - : HttpMethod.patch, - data: data); - stateCrudState = StateEnum.success; - method != CRUD.read - ? postHogService( - eventName: method == CRUD.create - ? 'STATE_CREATE' - : method == CRUD.update - ? 'STATE_UPDATE' - : method == CRUD.delete - ? 'STATE_DELETE' - : '', - properties: method == CRUD.delete - ? {} - : { - 'WORKSPACE_ID': - workspaceProvider.selectedWorkspace.workspaceId, - 'WORKSPACE_SLUG': - workspaceProvider.selectedWorkspace.workspaceSlug, - 'WORKSPACE_NAME': - workspaceProvider.selectedWorkspace.workspaceName, - 'PROJECT_ID': projectProvider.projectDetailModel!.id, - 'PROJECT_NAME': projectProvider.projectDetailModel!.name, - 'STATE_ID': - method == CRUD.create ? response.data['id'] : stateId - }, - ref: ref) - : null; + }, (err) { + unsplashImageState = StateEnum.error; notifyListeners(); - } catch (e) { - if (e is DioException) { - log(e.message.toString()); - } - CustomToast.showToast(context, - message: 'Something went wrong, Please try again.', - toastType: ToastType.failure); - log(e.toString()); - stateCrudState = StateEnum.error; - notifyListeners(); - } + }); } } diff --git a/lib/provider/provider_list.dart b/lib/provider/provider_list.dart index d77f8582..7b702c5f 100644 --- a/lib/provider/provider_list.dart +++ b/lib/provider/provider_list.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/Authentication/google_sign_in.dart'; +import 'package:plane/authentication/google_sign_in.dart'; import 'package:plane/config/const.dart'; import 'package:plane/provider/activity_provider.dart'; import 'package:plane/provider/auth_provider.dart'; @@ -10,16 +10,19 @@ import 'package:plane/provider/dashboard_provider.dart'; import 'package:plane/provider/file_upload_provider.dart'; import 'package:plane/provider/global_search_provider.dart'; import 'package:plane/provider/issue_provider.dart'; +import 'package:plane/provider/label_provider.dart'; import 'package:plane/provider/my_issues_provider.dart'; import 'package:plane/provider/page_provider.dart'; import 'package:plane/provider/notification_provider.dart'; import 'package:plane/provider/profile_provider.dart'; import 'package:plane/provider/projects_provider.dart'; import 'package:plane/provider/search_issue_provider.dart'; +import 'package:plane/provider/project_state_provider.dart'; import 'package:plane/provider/whats_new_provider.dart'; import 'package:plane/provider/workspace_provider.dart'; +import 'package:plane/repository/project_state_service.dart'; +import 'package:plane/repository/labels.service.dart'; import 'package:plane/services/shared_preference_service.dart'; - import '../repository/dashboard_service.dart'; import '../repository/profile_provider_service.dart'; import '../repository/workspace_service.dart'; @@ -33,71 +36,99 @@ import 'theme_provider.dart'; import 'views_provider.dart'; class ProviderList { + // Auth Provider static final authProvider = ChangeNotifierProvider((ref) => AuthProvider(ref)); + // Profile Provider static ChangeNotifierProvider profileProvider = ChangeNotifierProvider( (_) => ProfileProvider(profileService: ProfileService(DioConfig()))); + // Workspace Provider static ChangeNotifierProvider workspaceProvider = ChangeNotifierProvider((ref) => WorkspaceProvider( ref: ref, workspaceService: WorkspaceService(DioConfig()))); + // Theme Provider static ChangeNotifierProvider themeProvider = ChangeNotifierProvider((ref) => ThemeProvider(ref)); + // Project Provider static ChangeNotifierProvider projectProvider = ChangeNotifierProvider((ref) => ProjectsProvider(ref)); + // File Upload Provider static ChangeNotifierProvider fileUploadProvider = ChangeNotifierProvider( (ref) => FileUploadProvider(ref)); + // Issues Provider static ChangeNotifierProvider issuesProvider = ChangeNotifierProvider((ref) => IssuesProvider(ref)); + // Issue Provider static ChangeNotifierProvider issueProvider = ChangeNotifierProvider((ref) => IssueProvider(ref)); + // Search Issue Provider static ChangeNotifierProvider searchIssueProvider = ChangeNotifierProvider((_) => SearchIssueProvider()); + // Bottom Nav Provider static ChangeNotifierProvider bottomNavProvider = ChangeNotifierProvider((_) => BottomNavProvider()); + // Cycles Provider static ChangeNotifierProvider cyclesProvider = ChangeNotifierProvider((ref) => CyclesProvider(ref)); - + // Modules Provider static ChangeNotifierProvider modulesProvider = ChangeNotifierProvider((ref) => ModuleProvider(ref)); + // Estimates Provider static ChangeNotifierProvider estimatesProvider = ChangeNotifierProvider((_) => EstimatesProvider()); + // My Issues Provider static ChangeNotifierProvider myIssuesProvider = ChangeNotifierProvider((ref) => MyIssuesProvider(ref)); + // Activity Provider static ChangeNotifierProvider activityProvider = ChangeNotifierProvider((_) => ActivityProvider()); + // Dashboard Provider static ChangeNotifierProvider dashboardProvider = ChangeNotifierProvider((ref) => DashBoardProvider( ref: ref, dashboardService: DashboardService(DioConfig()))); + // Integration Provider static ChangeNotifierProvider integrationProvider = ChangeNotifierProvider( (ref) => IntegrationProvider(ref)); + // Views Provider static StateNotifierProvider viewsProvider = StateNotifierProvider( (ref) => ViewsNotifier(ref)); + // Global Search Provider static StateNotifierProvider globalSearchProvider = StateNotifierProvider( (ref) => GlobalSearchProvider(ref)); + // Page Provider static ChangeNotifierProvider pageProvider = ChangeNotifierProvider((ref) => PageProvider()); - + // Notification Provider static ChangeNotifierProvider notificationProvider = ChangeNotifierProvider( (ref) => NotificationProvider(ref)); + // Member Profile Provider static StateNotifierProvider memberProfileProvider = StateNotifierProvider( (ref) => MemberProfileProvider(ref)); - + // Whats New Provider static StateNotifierProvider whatsNewProvider = StateNotifierProvider( (ref) => WhatsNewNotifier(WhatsNew(null))); - + // Config Provider static StateNotifierProvider configProvider = StateNotifierProvider( (ref) => ConfigProvider(ref)); + + static StateNotifierProvider statesProvider = + StateNotifierProvider( + (ref) => StatesProvider(ref, StatesService())); + // Label Provider + static StateNotifierProvider labelProvider = + StateNotifierProvider( + (ref) => LabelNotifier(ref, LabelsService())); static void clear({required WidgetRef ref}) { ref.read(issueProvider).clear(); diff --git a/lib/provider/views_provider.dart b/lib/provider/views_provider.dart index 5271dd6e..ff804321 100644 --- a/lib/provider/views_provider.dart +++ b/lib/provider/views_provider.dart @@ -63,8 +63,9 @@ class ViewsNotifier extends StateNotifier { {required dynamic data, required WidgetRef ref, required BuildContext context}) async { - final workspaceProvider = ref.watch(ProviderList.workspaceProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); + final workspaceProvider = ref.read(ProviderList.workspaceProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + final profileProvider = ref.read(ProviderList.profileProvider); try { final response = await DioConfig().dioServe( hasAuth: true, @@ -97,7 +98,8 @@ class ViewsNotifier extends StateNotifier { 'PROJECT_NAME': projectProvider.projectDetailModel!.name, 'VIEW_ID': response.data['id'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); } on DioException catch (e) { log(e.error.toString()); state = state.copyWith(viewsState: StateEnum.error); diff --git a/lib/provider/workspace_provider.dart b/lib/provider/workspace_provider.dart index 3b052a02..fa2c177f 100644 --- a/lib/provider/workspace_provider.dart +++ b/lib/provider/workspace_provider.dart @@ -6,7 +6,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/config/const.dart'; -import 'package:plane/models/workspace_model.dart'; +import 'package:plane/models/Workspace/workspace_model.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; @@ -88,7 +88,7 @@ class WorkspaceProvider extends ChangeNotifier { try { final response = await DioConfig().dioServe( hasAuth: true, - url: APIs.baseApi + APIs.listWorkspaceInvitaion, + url: APIs.listWorkspaceInvitaion, hasBody: false, httpMethod: HttpMethod.get, ); @@ -111,7 +111,7 @@ class WorkspaceProvider extends ChangeNotifier { try { await DioConfig().dioServe( hasAuth: true, - url: (APIs.joinWorkspace), + url: APIs.listWorkspaceInvitaion, hasBody: true, data: {"invitations": data}, httpMethod: HttpMethod.post, @@ -163,7 +163,8 @@ class WorkspaceProvider extends ChangeNotifier { 'WORKSPACE_NAME': response.data['name'], 'WORKSPACE_SLUG': response.data['slug'] }, - ref: refs); + userEmail: profileProv.userProfile.email!, + userID: profileProv.userProfile.id!); await profileProv.updateProfile(data: { "last_workspace_id": response.data['id'], }); @@ -318,7 +319,6 @@ class WorkspaceProvider extends ChangeNotifier { } selectedWorkspace = WorkspaceModel.fromJson(workspaces[0]); final slug = selectedWorkspace.workspaceSlug; - log('AFTER DELETE WORKSPACE ${selectedWorkspace.workspaceName} }'); ref!.read(ProviderList.dashboardProvider).getDashboard(); projectProv.projects = []; projectProv.getProjects(slug: slug); @@ -481,6 +481,7 @@ class WorkspaceProvider extends ChangeNotifier { } Future updateWorkspace({required Map data, required WidgetRef ref}) async { + final profileProvider = ref.read(ProviderList.profileProvider); updateWorkspaceState = StateEnum.loading; notifyListeners(); try { @@ -502,7 +503,8 @@ class WorkspaceProvider extends ChangeNotifier { 'WORKSPACE_NAME': response.data['name'], 'WORKSPACE_SLUG': response.data['slug'] }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); selectedWorkspace = WorkspaceModel.fromJson(response.data); tempLogo = selectedWorkspace.workspaceLogo; diff --git a/lib/repository/labels.service.dart b/lib/repository/labels.service.dart new file mode 100644 index 00000000..02de22cc --- /dev/null +++ b/lib/repository/labels.service.dart @@ -0,0 +1,108 @@ +import 'dart:developer'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:plane/config/apis.dart'; +import 'package:plane/models/Project/Label/label.model.dart'; +import 'package:plane/services/dio_service.dart'; +import 'package:plane/utils/enums.dart'; + +class LabelsService { + final DioConfig dio = DioConfig(); + + // GET Project Labels + Future, DioException>> getProjectLabels( + String slug, String projectID) async { + try { + final response = await dio.dioServe( + hasAuth: true, + url: + '${APIs.baseApi}/api/workspaces/$slug/projects/$projectID/issue-labels/', + hasBody: false, + httpMethod: HttpMethod.get, + ); + return Left({ + for (final label in response.data!) + label['id']: LabelModel.fromJson(label) + }); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } + + // GET Workspace Labels + Future, DioException>> getWorkspaceLabels( + String slug, String projectID) async { + try { + final response = await dio.dioServe( + hasAuth: true, + url: '${APIs.baseApi}/api/workspaces/labels/', + hasBody: false, + httpMethod: HttpMethod.get, + ); + return Left({ + for (final label in response.data!) + label['id']: LabelModel.fromJson(label) + }); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } + + // CREATE Label + Future> createLabel( + String slug, String projectID, Map payload) async { + try { + final response = await dio.dioServe( + hasAuth: true, + url: + '${APIs.baseApi}/api/workspaces/$slug/projects/$projectID/issue-labels/', + hasBody: true, + data: payload, + httpMethod: HttpMethod.post, + ); + return Left(LabelModel.fromJson(response.data)); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } + + // UPDATE Label + Future> updateLabel( + String slug, String projectID, Map payload) async { + try { + final response = await dio.dioServe( + hasAuth: true, + url: + '${APIs.baseApi}/api/workspaces/$slug/projects/$projectID/issue-labels/${payload['id']}/', + hasBody: true, + data: payload, + httpMethod: HttpMethod.patch, + ); + return Left(LabelModel.fromJson(response.data)); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } + + // DELETE Label + Future> deleteLabel( + String slug, String projectID, String labelID) async { + try { + await dio.dioServe( + hasAuth: true, + url: + '${APIs.baseApi}/api/workspaces/$slug/projects/$projectID/issue-labels/$labelID/', + hasBody: false, + httpMethod: HttpMethod.delete, + ); + return const Left(null); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } +} diff --git a/lib/repository/profile_provider_service.dart b/lib/repository/profile_provider_service.dart index 73aa806c..0a126430 100644 --- a/lib/repository/profile_provider_service.dart +++ b/lib/repository/profile_provider_service.dart @@ -17,7 +17,7 @@ class ProfileService { try { final response = await dio.dioServe( hasAuth: true, - url: '${APIs.baseApi}${APIs.profile}', + url: APIs.profile, hasBody: false, httpMethod: HttpMethod.get, ); @@ -32,7 +32,7 @@ class ProfileService { try { final response = await dio.dioServe( hasAuth: true, - url: '${APIs.baseApi}${APIs.profile}settings/', + url: '${APIs.profile}settings/', hasBody: false, httpMethod: HttpMethod.get, ); @@ -48,7 +48,7 @@ class ProfileService { try { final response = await dio.dioServe( hasAuth: true, - url: APIs.baseApi + APIs.profile, + url: APIs.profile, hasBody: true, httpMethod: HttpMethod.patch, data: data); diff --git a/lib/repository/project_state_service.dart b/lib/repository/project_state_service.dart new file mode 100644 index 00000000..dec09bc3 --- /dev/null +++ b/lib/repository/project_state_service.dart @@ -0,0 +1,122 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:plane/config/apis.dart'; +import 'package:plane/models/project/state/state_model.dart'; +import 'package:plane/services/dio_service.dart'; +import 'package:plane/utils/enums.dart'; + +class StatesService { + final dio = DioConfig(); + + Future, DioException>> getStates( + {required String slug, required String projectId}) async { + try { + final response = await dio.dioServe( + hasAuth: true, + url: APIs.states + .replaceAll("\$SLUG", slug) + .replaceAll('\$PROJECTID', projectId), + hasBody: false, + httpMethod: HttpMethod.get, + ); + // for (final element in (response.data as List)) { + // final statesModel = StatesModel.fromJson(element); + // statesModel.stateIcon = SvgPicture.asset( + // element['id'] == 'backlog' + // ? 'assets/svg_images/circle.svg' + // : element['id'] == 'cancelled' + // ? 'assets/svg_images/cancelled.svg' + // : element['id'] == 'completed' + // ? 'assets/svg_images/done.svg' + // : element['id'] == 'started' + // ? 'assets/svg_images/in_progress.svg' + // : 'assets/svg_images/unstarted.svg', + // height: 22, + // width: 22, + // colorFilter: int.tryParse( + // "FF${element['color'].toString().replaceAll('#', '')}", + // radix: 16) != + // null + // ? ColorFilter.mode( + // Color( + // int.parse( + // "FF${element['color'].toString().replaceAll('#', '')}", + // radix: 16, + // ), + // ), + // BlendMode.srcIn) + // : null, + // ); + // } + final states = { + for (final state in response.data) + state['id'].toString(): StateModel.fromJson(state) + }; + return Left(states); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } + + Future> createState( + {required Map data, + required String slug, + required String projectId}) async { + try { + final response = await DioConfig().dioServe( + hasAuth: true, + url: APIs.states + .replaceFirst('\$SLUG', slug) + .replaceFirst('\$PROJECTID', projectId), + hasBody: true, + httpMethod: HttpMethod.post, + data: data); + return Left(StateModel.fromJson(response.data)); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } + + Future> updateState( + {required Map data, + required String slug, + required String projectId, + required String stateId}) async { + try { + final response = await DioConfig().dioServe( + hasAuth: true, + url: + '${APIs.states.replaceFirst('\$SLUG', slug).replaceFirst('\$PROJECTID', projectId)}$stateId/', + hasBody: true, + httpMethod: HttpMethod.patch, + data: data); + return Left(StateModel.fromJson(response.data)); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } + + Future> deleteState( + {required String slug, + required String projectId, + required String stateId}) async { + try { + final response = await DioConfig().dioServe( + hasAuth: true, + url: + '${APIs.states.replaceFirst('\$SLUG', slug).replaceFirst('\$PROJECTID', projectId)}$stateId/', + hasBody: true, + httpMethod: HttpMethod.delete, + ); + return Left(response.data); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } +} diff --git a/lib/repository/projects_service.dart b/lib/repository/projects_service.dart new file mode 100644 index 00000000..478e6c69 --- /dev/null +++ b/lib/repository/projects_service.dart @@ -0,0 +1,27 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:plane/config/apis.dart'; +import 'package:plane/services/dio_service.dart'; +import 'package:plane/utils/enums.dart'; + +class ProjectsService { + final dio = DioConfig(); + + Future> getUnspalshImages() async { + try { + final response = await dio.dioServe( + hasAuth: true, + url: APIs.unsplashApi, + hasBody: false, + httpMethod: HttpMethod.get, + ); + log('UNSPLASH IMAGES: ${response.data}'); + return Left(response.data); + } on DioException catch (err) { + log(err.response.toString()); + return Right(err); + } + } +} diff --git a/lib/routers/generated_routes.dart b/lib/routers/generated_routes.dart index 0ceb7b89..c4fc0a0c 100644 --- a/lib/routers/generated_routes.dart +++ b/lib/routers/generated_routes.dart @@ -1,16 +1,16 @@ import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; -import 'package:plane/screens/MainScreens/Activity/activity.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/create_cycle.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ModulesTab/create_module.dart'; -import 'package:plane/screens/MainScreens/Projects/create_project_screen.dart'; +import 'package:plane/screens/Activity/activity.dart'; +import 'package:plane/screens/project/create_project_screen.dart'; +import 'package:plane/screens/project/cycles/create_cycle.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/modules/create_module.dart'; import 'package:plane/screens/create_state.dart'; import 'package:plane/screens/home_screen.dart'; -import 'package:plane/screens/on_boarding/on_boarding_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_profile_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_workspace.dart'; -import 'package:plane/screens/on_boarding/auth/sign_in.dart'; +import 'package:plane/screens/onboarding/on_boarding_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_profile_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; +import 'package:plane/screens/onboarding/auth/sign_in.dart'; import 'package:plane/utils/page_route_builder.dart'; import 'routes_path.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart b/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart deleted file mode 100644 index 01620956..00000000 --- a/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart +++ /dev/null @@ -1,2455 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'dart:developer'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:intl/intl.dart'; -import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/assignee_sheet.dart'; -import 'package:plane/bottom_sheets/filters/filter_sheet.dart'; -import 'package:plane/bottom_sheets/lead_sheet.dart'; -import 'package:plane/bottom_sheets/type_sheet.dart'; -import 'package:plane/bottom_sheets/views_sheet.dart'; -import 'package:plane/kanban/custom/board.dart'; -import 'package:plane/kanban/models/inputs.dart'; -import 'package:plane/models/chart_model.dart'; -import 'package:plane/models/issues.dart'; -import 'package:plane/provider/modules_provider.dart'; -import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/calender_view.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/spreadsheet_view.dart'; -import 'package:plane/utils/constants.dart'; -import 'package:plane/utils/custom_toast.dart'; -import 'package:plane/utils/enums.dart'; -import 'package:plane/utils/extensions/string_extensions.dart'; -import 'package:plane/widgets/completion_percentage.dart'; -import 'package:plane/widgets/custom_app_bar.dart'; -import 'package:plane/widgets/custom_text.dart'; -import 'package:plane/widgets/empty.dart'; -import 'package:plane/widgets/member_logo_widget.dart'; -import 'package:plane/widgets/square_avatar_widget.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import '../../../../../bottom_sheets/add_link_sheet.dart'; -import '../../../../../bottom_sheets/select_cycle_sheet.dart'; -import '../IssuesTab/issue_detail.dart'; - -class CycleDetail extends ConsumerStatefulWidget { - const CycleDetail( - {super.key, - this.cycleId, - this.cycleName, - this.moduleId, - this.moduleName, - this.fromModule = false, - this.projId, - this.from}); - final String? cycleName; - final String? cycleId; - final String? moduleName; - final String? moduleId; - final String? projId; - final bool fromModule; - final PreviousScreen? from; - - @override - ConsumerState createState() => _CycleDetailState(); -} - -class _CycleDetailState extends ConsumerState { - List chartData = []; - PageController? pageController = PageController(); - List tempIssuesList = []; - - DateTime? dueDate; - DateTime? startDate; - - @override - void initState() { - final issuesProvider = ref.read(ProviderList.issuesProvider); - tempIssuesList = issuesProvider.issuesList; - issuesProvider.tempProjectView = issuesProvider.issues.projectView; - issuesProvider.tempGroupBy = issuesProvider.issues.groupBY; - issuesProvider.tempOrderBy = issuesProvider.issues.orderBY; - issuesProvider.tempIssueType = issuesProvider.issues.issueType; - issuesProvider.tempFilters = issuesProvider.issues.filters; - - issuesProvider.issues.projectView = ProjectView.kanban; - issuesProvider.issues.groupBY = GroupBY.state; - - issuesProvider.issues.orderBY = OrderBY.lastCreated; - issuesProvider.issues.issueType = IssueType.all; - issuesProvider.showEmptyStates = true; - issuesProvider.issues.filters = Filters( - assignees: [], - createdBy: [], - labels: [], - priorities: [], - states: [], - targetDate: [], - startDate: [], - stateGroup: [], - subscriber: [], - ); - issuesProvider.filterIssues( - cycleId: widget.cycleId, - moduleId: widget.moduleId, - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - issueCategory: widget.fromModule - ? IssueCategory.moduleIssues - : IssueCategory.cycleIssues, - projID: widget.projId ?? - ref.read(ProviderList.projectProvider).currentProject['id']); - widget.fromModule ? getModuleData() : getCycleData(); - super.initState(); - } - - Future getModuleData() async { - final modulesProvider = ref.read(ProviderList.modulesProvider); - final issuesProvider = ref.read(ProviderList.issuesProvider); - - pageController = PageController( - initialPage: modulesProvider.moduleDetailSelectedIndex, - keepPage: true, - viewportFraction: 1); - - await modulesProvider - .getModuleDetails( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projId: ref.read(ProviderList.projectProvider).currentProject['id'], - moduleId: widget.moduleId!, - disableLoading: true) - .then((value) => getChartData(modulesProvider - .moduleDetailsData['distribution']['completion_chart'])); - - issuesProvider.getIssueProperties( - issueCategory: IssueCategory.moduleIssues); - - modulesProvider - .filterModuleIssues( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: ref.read(ProviderList.projectProvider).currentProject['id'], - ) - .then((value) { - if (modulesProvider.issues.projectView == ProjectView.list) { - modulesProvider.initializeBoard(); - } - }); - } - - Future getCycleData() async { - final cyclesProvider = ref.read(ProviderList.cyclesProvider); - final issuesProvider = ref.read(ProviderList.issuesProvider); - cyclesProvider.cyclesDetailState = StateEnum.loading; - pageController = PageController( - initialPage: cyclesProvider.cycleDetailSelectedIndex, - keepPage: true, - viewportFraction: 1); - - await cyclesProvider - .cycleDetailsCrud( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: widget.projId ?? - ref.read(ProviderList.projectProvider).currentProject['id'], - method: CRUD.read, - disableLoading: true, - cycleId: widget.cycleId!) - .then((value) => getChartData(cyclesProvider - .cyclesDetailsData['distribution']['completion_chart'])); - - ref - .read(ProviderList.issuesProvider) - .getIssueProperties(issueCategory: IssueCategory.cycleIssues); - cyclesProvider - .filterCycleIssues( - cycleID: widget.cycleId!, - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: widget.projId ?? - ref.read(ProviderList.projectProvider).currentProject['id'], - ) - .then((value) { - if (issuesProvider.issues.projectView == ProjectView.list) { - cyclesProvider.initializeBoard(); - } - }); - } - - Future getChartData(Map data) async { - data.forEach((key, value) { - chartData.add(ChartData(DateTime.parse(key), value.toDouble())); - }); - } - - @override - Widget build(BuildContext context) { - final themeProvider = ref.watch(ProviderList.themeProvider); - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final cyclesProviderRead = ref.read(ProviderList.cyclesProvider); - final issueProvider = ref.watch(ProviderList.issuesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final projectProvider = ref.read(ProviderList.projectProvider); - final bool isLoading = widget.fromModule - ? modulesProvider.moduleState == StateEnum.loading - : cyclesProvider.cyclesState == StateEnum.loading; - - return WillPopScope( - onWillPop: () async { - if (widget.from == PreviousScreen.myIssues) return true; - issueProvider.getIssues( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: projectProvider.currentProject['id'], - ); - modulesProvider.selectedIssues = []; - cyclesProvider.selectedIssues = []; - issueProvider.issues.projectView = issueProvider.tempProjectView; - issueProvider.issues.groupBY = issueProvider.tempGroupBy; - - issueProvider.issues.orderBY = issueProvider.tempOrderBy; - issueProvider.issues.issueType = issueProvider.tempIssueType; - - issueProvider.issues.filters = issueProvider.tempFilters; - - issueProvider.showEmptyStates = - issueProvider.issueView['display_filters']["show_empty_groups"]; - - issueProvider.setsState(); - issueProvider.filterIssues( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: projectProvider.currentProject['id']); - return true; - }, - child: Scaffold( - floatingActionButton: isLoading && - !widget.fromModule && - DateTime.parse(cyclesProvider.cyclesDetailsData['end_date']) - .isBefore(DateTime.now()) && - (cyclesProvider.cyclesDetailsData['backlog_issues'] != 0 && - cyclesProvider.cyclesDetailsData['started_issues'] != 0 && - cyclesProvider.cyclesDetailsData['unstarted_issues'] != 0) - ? FloatingActionButton( - backgroundColor: themeProvider.themeManager.primaryColour, - onPressed: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: height * 0.8, minHeight: 250), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), - )), - context: context, - builder: (ctx) { - return const SelectCycleSheet(); - }); - }, - child: Container( - height: 50, - width: 50, - decoration: BoxDecoration( - color: themeProvider.themeManager.primaryColour, - borderRadius: BorderRadius.circular(50), - ), - child: const Icon( - Icons.error_outline_outlined, - color: Colors.white, - ), - ), - ) - : null, - appBar: CustomAppBar( - centerTitle: true, - onPressed: () { - if (widget.from == PreviousScreen.myIssues) { - Navigator.pop(context); - return; - } - issueProvider.getIssues( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: projectProvider.currentProject['id'], - ); - modulesProvider.selectedIssues = []; - cyclesProvider.selectedIssues = []; - issueProvider.issues.projectView = issueProvider.tempProjectView; - issueProvider.issues.groupBY = issueProvider.tempGroupBy; - - issueProvider.issues.orderBY = issueProvider.tempOrderBy; - issueProvider.issues.issueType = issueProvider.tempIssueType; - - issueProvider.issues.filters = issueProvider.tempFilters; - issueProvider.showEmptyStates = - issueProvider.issueView['display_filters'] != null - ? issueProvider.issueView['display_filters'] - ["show_empty_groups"] - : false; - - issueProvider.setsState(); - issueProvider.filterIssues( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: projectProvider.currentProject['id']); - Navigator.pop(context); - }, - text: widget.projId == null - ? projectProvider.currentProject['name'] - : projectProvider.projects.firstWhere( - (element) => element['id'] == widget.projId)['name'], - ), - body: isLoading - ? Center( - child: SizedBox( - width: 30, - height: 30, - child: LoadingIndicator( - indicatorType: Indicator.lineSpinFadeLoader, - colors: [themeProvider.themeManager.primaryTextColor], - strokeWidth: 1.0, - backgroundColor: Colors.transparent, - )), - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 25, top: 20), - child: CustomText( - widget.fromModule - ? widget.moduleName! - : widget.cycleName!, - maxLines: 1, - type: FontStyle.H5, - fontWeight: FontWeightt.Semibold, - ), - ), - ), - ], - ), - SizedBox( - width: MediaQuery.of(context).size.width, - child: Row( - children: [ - Expanded( - child: InkWell( - onTap: () { - widget.fromModule - ? { - modulesProvider.changeTabIndex(0), - pageController!.animateToPage(0, - duration: - const Duration(milliseconds: 100), - curve: Curves.easeIn) - } - : { - cyclesProvider.changeTabIndex(0), - pageController!.animateToPage(0, - duration: - const Duration(milliseconds: 100), - curve: Curves.easeIn) - }; - }, - child: Column( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(vertical: 10), - child: CustomText( - overrride: true, - 'Issues', - type: FontStyle.Large, - fontWeight: FontWeightt.Medium, - color: (widget.fromModule - ? modulesProvider - .moduleDetailSelectedIndex == - 1 - : cyclesProviderRead - .cycleDetailSelectedIndex == - 1) - ? themeProvider - .themeManager.placeholderTextColor - : (widget.fromModule - ? modulesProvider - .moduleDetailSelectedIndex == - 1 - : cyclesProviderRead - .cycleDetailSelectedIndex == - 1) - ? themeProvider - .themeManager.placeholderTextColor - : themeProvider - .themeManager.primaryColour, - ), - ), - Container( - height: 7, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: (widget.fromModule - ? modulesProvider - .moduleDetailSelectedIndex == - 0 - : cyclesProviderRead - .cycleDetailSelectedIndex == - 0) - ? themeProvider.themeManager.primaryColour - : Colors.transparent, - ), - ), - ], - ), - )), - Expanded( - child: InkWell( - onTap: () { - widget.fromModule - ? { - modulesProvider.changeTabIndex(1), - pageController!.animateToPage(1, - duration: - const Duration(milliseconds: 100), - curve: Curves.easeIn) - } - : { - cyclesProvider.changeTabIndex(1), - pageController!.animateToPage(1, - duration: - const Duration(milliseconds: 100), - curve: Curves.easeIn) - }; - }, - child: Column( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(vertical: 10), - child: CustomText('Details', - overrride: true, - type: FontStyle.Large, - fontWeight: FontWeightt.Medium, - color: (widget.fromModule - ? modulesProvider - .moduleDetailSelectedIndex == - 0 - : cyclesProviderRead - .cycleDetailSelectedIndex == - 0) - ? themeProvider - .themeManager.placeholderTextColor - : (widget.fromModule - ? modulesProvider - .moduleDetailSelectedIndex == - 0 - : cyclesProviderRead - .cycleDetailSelectedIndex == - 0) - ? themeProvider.themeManager - .placeholderTextColor - : themeProvider - .themeManager.primaryColour), - ), - Container( - height: 7, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: (widget.fromModule - ? modulesProvider - .moduleDetailSelectedIndex == - 1 - : cyclesProviderRead - .cycleDetailSelectedIndex == - 1) - ? themeProvider.themeManager.primaryColour - : Colors.transparent, - ), - ), - ], - ), - )), - ], - ), - ), - Container( - height: 2, - width: MediaQuery.of(context).size.width, - color: themeProvider.themeManager.borderSubtle01Color, - ), - Expanded( - child: PageView( - controller: pageController, - onPageChanged: (value) { - if (value == 0) { - widget.fromModule - ? modulesProvider.changeTabIndex(0) - : cyclesProvider.changeTabIndex(0); - } else { - widget.fromModule - ? modulesProvider.changeTabIndex(1) - : cyclesProvider.changeTabIndex(1); - } - }, - children: [ - Column( - children: [ - Expanded( - child: - ((widget.fromModule && (modulesProvider.moduleIssueState == StateEnum.loading || modulesProvider.moduleDetailState == StateEnum.loading)) || - (!widget.fromModule && - (cyclesProvider.cyclesIssueState == - StateEnum.loading || - cyclesProvider.cyclesDetailState == - StateEnum - .loading))) || - issueProvider.projectViewState == - StateEnum.loading - ? Center( - child: SizedBox( - width: 30, - height: 30, - child: LoadingIndicator( - indicatorType: Indicator - .lineSpinFadeLoader, - colors: [ - themeProvider.themeManager - .primaryTextColor - ], - strokeWidth: 1.0, - backgroundColor: themeProvider - .themeManager - .primaryBackgroundDefaultColor, - )), - ) - : ((!widget.fromModule && cyclesProvider.isIssuesEmpty) || - (widget.fromModule && - modulesProvider - .isIssuesEmpty)) - ? EmptyPlaceholder.emptyIssues(context, - cycleId: widget.cycleId, - moduleId: widget.moduleId, - projectId: widget.projId, - type: IssueCategory.cycleIssues, - ref: ref) - : ((!widget.fromModule && issueProvider.issues.projectView == ProjectView.list) || - (widget.fromModule && - issueProvider.issues.projectView == - ProjectView.list)) - ? Container( - color: themeProvider - .themeManager - .secondaryBackgroundDefaultColor, - margin: const EdgeInsets.only( - top: 5), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - children: (widget - .fromModule - ? modulesProvider - .issues - .issues - : cyclesProvider - .issues - .issues) - .map((state) => state - .items - .isEmpty && - (widget - .fromModule - ? !modulesProvider - .showEmptyStates - : !cyclesProvider - .showEmptyStates) - ? Container() - : SizedBox( - child: - Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - children: [ - Container( - padding: const EdgeInsets - .only( - left: 15), - margin: const EdgeInsets - .only( - bottom: 10), - child: - Row( - children: [ - state.leading ?? Container(), - Container( - padding: const EdgeInsets.only( - left: 10, - ), - width: MediaQuery.of(context).size.width * 0.6, - child: CustomText( - state.title!, - overflow: TextOverflow.ellipsis, - maxLines: 1, - type: FontStyle.Small, - fontWeight: FontWeightt.Medium, - ), - ), - Container( - alignment: Alignment.center, - margin: const EdgeInsets.only( - left: 15, - ), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: themeProvider.themeManager.secondaryBackgroundDefaultColor), - height: 25, - width: 30, - child: CustomText( - state.items.length.toString(), - type: FontStyle.Medium, - ), - ), - const Spacer(), - IconButton( - onPressed: () { - if (issueProvider.issues.groupBY == GroupBY.state) { - issueProvider.createIssuedata['state'] = state.id; - } else { - issueProvider.createIssuedata['priority'] = 'de3c90cd-25cd-42ec-ac6c-a66caf8029bc'; - } - Navigator.of(context).push(MaterialPageRoute( - builder: (ctx) => CreateIssue( - moduleId: widget.moduleId, - cycleId: widget.cycleId, - ))); - }, - icon: Icon( - Icons.add, - color: themeProvider.themeManager.primaryColour, - )), - const SizedBox( - width: 10, - ), - ], - ), - ), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: state.items.map((e) => e).toList()), - state.items.isEmpty - ? Container( - margin: const EdgeInsets.only(bottom: 10), - width: MediaQuery.of(context).size.width, - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - padding: const EdgeInsets.only(top: 15, bottom: 15, left: 15), - child: const CustomText( - 'No issues.', - type: FontStyle.Small, - maxLines: 10, - textAlign: TextAlign.start, - ), - ) - : Container( - margin: const EdgeInsets.only(bottom: 10), - ) - ], - ), - )) - .toList()), - ), - ) - : ((!widget.fromModule && issueProvider.issues.projectView == ProjectView.kanban) || - (widget.fromModule && - issueProvider.issues.projectView == - ProjectView.kanban)) - ? Padding( - padding: - const EdgeInsets.only( - left: 8), - child: KanbanBoard( - widget.fromModule - ? modulesProvider - .initializeBoard() - : cyclesProvider - .initializeBoard(), - boardID: - widget.fromModule - ? 'module-board' - : 'cycle-board', - onItemReorder: ( - {newCardIndex, - newListIndex, - oldCardIndex, - oldListIndex}) { - if (widget - .fromModule) { - modulesProvider - .reorderIssue( - newCardIndex: - newCardIndex!, - newListIndex: - newListIndex!, - oldCardIndex: - oldCardIndex!, - oldListIndex: - oldListIndex!, - ) - .catchError( - (err) { - CustomToast.showToast( - context, - message: - 'Failed to update issue', - toastType: - ToastType - .failure); - }); - } else { - cyclesProvider - .reorderIssue( - newCardIndex: - newCardIndex!, - newListIndex: - newListIndex!, - oldCardIndex: - oldCardIndex!, - oldListIndex: - oldListIndex!, - ) - .catchError( - (err) { - log(err - .toString()); - CustomToast.showToast( - context, - message: - 'Failed to update issue', - toastType: - ToastType - .failure); - }); - } - }, - isCardsDraggable: - issueProvider - .checkIsCardsDaraggable(), - groupEmptyStates: !(widget - .fromModule - ? modulesProvider - .showEmptyStates - : issueProvider - .showEmptyStates), - cardPlaceHolderColor: - themeProvider - .themeManager - .primaryBackgroundDefaultColor, - cardPlaceHolderDecoration: - BoxDecoration( - color: themeProvider - .themeManager - .primaryBackgroundDefaultColor, - boxShadow: [ - BoxShadow( - blurRadius: 2, - color: themeProvider - .themeManager - .borderSubtle01Color, - spreadRadius: 0, - ) - ]), - backgroundColor: - themeProvider - .themeManager - .secondaryBackgroundDefaultColor, - listScrollConfig: ScrollConfig( - offset: 65, - duration: - const Duration( - milliseconds: - 100), - curve: - Curves.linear), - listTransitionDuration: - const Duration( - milliseconds: - 200), - cardTransitionDuration: - const Duration( - milliseconds: - 400), - textStyle: TextStyle( - fontSize: 19, - height: 1.3, - color: Colors - .grey.shade800, - fontWeight: - FontWeight - .w500), - ), - ) - : ((!widget.fromModule && issueProvider.issues.projectView == ProjectView.calendar) || (widget.fromModule && issueProvider.issues.projectView == ProjectView.calendar)) - ? const CalendarView() - : SpreadSheetView( - issueCategory: - widget.fromModule - ? IssueCategory - .moduleIssues - : IssueCategory - .cycleIssues, - ), - ), - SafeArea( - child: Container( - height: 50, - width: MediaQuery.of(context).size.width, - decoration: BoxDecoration( - color: themeProvider.themeManager - .primaryBackgroundDefaultColor, - boxShadow: themeProvider.themeManager - .shadowBottomControlButtons), - child: Row( - children: [ - projectProvider.role == Role.admin || - projectProvider.role == Role.member - ? Expanded( - child: GestureDetector( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => - CreateIssue( - projectId: widget - .projId ?? - projectProvider - .currentProject[ - 'id'], - fromMyIssues: true, - moduleId: widget.moduleId, - cycleId: widget.cycleId, - ), - ), - ); - }, - child: SizedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon( - Icons.add, - color: themeProvider - .themeManager - .primaryTextColor, - size: 20, - ), - const CustomText( - ' Issue', - type: FontStyle.Medium, - ) - ], - ), - ), - ), - ) - : Container(), - Container( - height: 50, - width: 0.5, - color: themeProvider - .themeManager.borderSubtle01Color, - ), - Expanded( - child: GestureDetector( - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context) - .size - .height * - 0.85), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), - )), - context: context, - builder: (ctx) { - return TypeSheet( - issueCategory: widget.fromModule - ? IssueCategory.moduleIssues - : IssueCategory.cycleIssues, - ); - }); - }, - child: SizedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon( - Icons.menu, - color: themeProvider.themeManager - .primaryTextColor, - size: 19, - ), - const CustomText( - ' Layout', - type: FontStyle.Medium, - ) - ], - ), - ), - )), - Container( - height: 50, - width: 0.5, - color: themeProvider - .themeManager.borderSubtle01Color, - ), - issueProvider.issues.projectView == - ProjectView.calendar - ? Container() - : Expanded( - child: GestureDetector( - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context) - .size - .height * - 0.9), - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius.only( - topLeft: - Radius.circular(30), - topRight: - Radius.circular(30), - )), - context: context, - builder: (ctx) { - return ViewsSheet( - projectView: issueProvider - .issues.projectView, - issueCategory: - widget.fromModule - ? IssueCategory - .moduleIssues - : IssueCategory - .cycleIssues, - cycleId: widget.cycleId, - ); - }); - }, - child: SizedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon( - Icons.wysiwyg_outlined, - color: themeProvider - .themeManager - .primaryTextColor, - size: 19, - ), - const CustomText( - ' Display', - type: FontStyle.Medium, - ) - ], - ), - ), - )), - Container( - height: 50, - width: 0.5, - color: themeProvider - .themeManager.borderSubtle01Color, - ), - Expanded( - child: GestureDetector( - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context) - .size - .height * - 0.85), - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), - )), - context: context, - builder: (ctx) { - return FilterSheet( - issueCategory: - widget.fromModule - ? IssueCategory - .moduleIssues - : IssueCategory - .cycleIssues, - ); - }); - }, - child: SizedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon( - Icons.filter_list_outlined, - color: themeProvider - .themeManager - .primaryTextColor, - size: 19, - ), - const CustomText( - ' Filters', - type: FontStyle.Medium, - ) - ], - ), - ), - ), - ), - ], - ), - ), - ), - ], - ), - widget.fromModule - ? Container( - color: themeProvider.themeManager - .secondaryBackgroundDefaultColor, - padding: - const EdgeInsets.only(left: 25, right: 25), - child: activeCycleDetails(fromModule: true), - ) - : Container( - color: themeProvider.themeManager - .secondaryBackgroundDefaultColor, - padding: - const EdgeInsets.only(left: 25, right: 25), - child: activeCycleDetails(), - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget activeCycleDetails({bool fromModule = false}) { - final themeProvider = ref.watch(ProviderList.themeProvider); - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - - if (widget.fromModule - ? modulesProvider.moduleDetailState == StateEnum.loading - : cyclesProvider.cyclesDetailState == StateEnum.loading) { - return Center( - child: SizedBox( - width: 30, - height: 30, - child: LoadingIndicator( - indicatorType: Indicator.lineSpinFadeLoader, - colors: [themeProvider.themeManager.primaryTextColor], - strokeWidth: 1.0, - backgroundColor: Colors.transparent, - ), - ), - ); - } else { - return ListView( - children: [ - const SizedBox(height: 30), - datePart(), - const SizedBox(height: 30), - detailsPart(), - const SizedBox(height: 30), - progressPart(), - const SizedBox(height: 30), - assigneesPart(fromModule: widget.fromModule), - const SizedBox(height: 30), - statesPart(), - const SizedBox(height: 30), - labelsPart(fromModule: widget.fromModule), - const SizedBox(height: 30), - widget.fromModule ? links() : Container() - ], - ); - } - } - - Widget links() { - final themeProvider = ref.watch(ProviderList.themeProvider); - final ModuleProvider moduleProvider = - ref.watch(ProviderList.modulesProvider); - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomText( - 'Links', - type: FontStyle.Medium, - fontWeight: FontWeightt.Medium, - color: themeProvider.themeManager.primaryTextColor, - ), - GestureDetector( - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.85), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), - )), - context: context, - builder: (ctx) { - return SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom), - child: const AddLinkSheet(), - ), - ); - }, - ); - }, - child: Icon( - Icons.add, - color: themeProvider.themeManager.primaryTextColor, - )) - ], - ), - const SizedBox(height: 10), - moduleProvider.moduleDetailsData['link_module'].isNotEmpty - ? Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: ListView.builder( - shrinkWrap: true, - itemCount: - moduleProvider.moduleDetailsData['link_module'].length, - itemBuilder: (ctx, index) { - return SizedBox( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.only(top: 5), - child: Transform.rotate( - angle: -20, - child: Icon( - Icons.link, - color: - themeProvider.themeManager.primaryTextColor, - size: 20, - ), - ), - ), - const SizedBox( - width: 10, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - moduleProvider.moduleDetailsData['link_module'] - [index]['title'] != - null - ? CustomText( - moduleProvider - .moduleDetailsData['link_module'] - [index]['title'] - .toString(), - type: FontStyle.Medium, - ) - : Container(), - CustomText( - 'by ${moduleProvider.moduleDetailsData['link_module'][index]['created_by_detail']['display_name']}', - type: FontStyle.Small, - color: themeProvider - .themeManager.placeholderTextColor, - ) - ], - ), - const Spacer(), - GestureDetector( - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height * - 0.85), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), - )), - context: context, - builder: (ctx) { - return SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context) - .viewInsets - .bottom), - child: AddLinkSheet( - id: moduleProvider.moduleDetailsData[ - 'link_module'][index]['id'], - ), - ), - ); - }, - ); - }, - child: Icon( - Icons.edit_outlined, - color: themeProvider - .themeManager.placeholderTextColor, - size: 20, - ), - ), - const SizedBox( - width: 10, - ), - GestureDetector( - onTap: () { - moduleProvider.handleLinks( - linkID: moduleProvider - .moduleDetailsData['link_module'] - [index]['id'], - data: {}, - method: HttpMethod.delete, - context: context); - }, - child: Icon( - Icons.delete_outline, - color: themeProvider - .themeManager.placeholderTextColor, - size: 20, - ), - ), - ], - ), - ); - }), - ) - : Container() - ]); - } - - Widget datePart() { - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final themeProvider = ref.watch(ProviderList.themeProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); - final detailData = widget.fromModule - ? modulesProvider.moduleDetailsData - : cyclesProvider.cyclesDetailsData; - - startDate = DateFormat('yyyy-MM-dd').parse( - detailData['start_date'] == null || detailData['start_date'] == '' - ? DateTime.now().toString() - : detailData['start_date']!); - - dueDate = DateFormat('yyyy-MM-dd').parse( - detailData[widget.fromModule ? 'target_date' : 'end_date'] == null || - detailData[widget.fromModule ? 'target_date' : 'end_date'] == '' - ? DateTime.now().toString() - : detailData[widget.fromModule ? 'target_date' : 'end_date']!); - - return Wrap( - runSpacing: 20, - children: [ - (detailData['start_date'] == null || detailData['start_date'] == '') || - (detailData[widget.fromModule ? 'target_date' : 'end_date'] == - null || - detailData[ - widget.fromModule ? 'target_date' : 'end_date'] == - '') - ? Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - border: Border.all( - width: 1, - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: const CustomText( - 'Draft', - type: FontStyle.Small, - ), - ) - : Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: checkDate( - startDate: detailData['start_date'], - endDate: detailData[widget.fromModule - ? 'target_date' - : 'end_date']) == - 'Draft' - ? themeProvider - .themeManager.tertiaryBackgroundDefaultColor - : checkDate( - startDate: detailData['start_date'], - endDate: detailData[widget.fromModule - ? 'target_date' - : 'end_date']) == - 'Completed' - ? themeProvider - .themeManager.secondaryBackgroundActiveColor - : themeProvider.themeManager.successBackgroundColor, - borderRadius: BorderRadius.circular(5)), - child: CustomText( - checkDate( - startDate: detailData['start_date'], - endDate: detailData[ - widget.fromModule ? 'target_date' : 'end_date'], - ), - type: FontStyle.Small, - color: checkDate( - startDate: detailData['start_date'], - endDate: detailData[ - widget.fromModule ? 'target_date' : 'end_date'], - ) == - 'Draft' - ? greyColor - : checkDate( - startDate: detailData['start_date'], - endDate: detailData[widget.fromModule - ? 'target_date' - : 'end_date'], - ) == - 'Completed' - ? themeProvider.themeManager.primaryColour - : greenHighLight, - ), - ), - const SizedBox(width: 20), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onTap: () async { - if (projectProvider.role != Role.admin && - projectProvider.role != Role.member) { - CustomToast.showToast(context, - message: accessRestrictedMSG, - toastType: ToastType.failure); - return; - } - final date = await showDatePicker( - builder: (context, child) => Theme( - data: themeProvider.themeManager.datePickerThemeData, - child: child!, - ), - context: context, - initialDate: startDate!, - firstDate: DateTime(2020), - lastDate: DateTime(2025), - ); - if (date != null) { - final bool dateNotConflicted = dueDate == null - ? true - : widget.fromModule - ? true - : await cyclesProvider.dateCheck( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - data: { - "cycle_id": widget.cycleId!, - "start_date": - DateFormat('yyyy-MM-dd').format(date), - "end_date": - DateFormat('yyyy-MM-dd').format(dueDate!), - }, - ); - if (dateNotConflicted) { - if (dueDate != null && date.isAfter(dueDate!)) { - CustomToast.showToast(context, - message: 'Start date cannot be after end date', - toastType: ToastType.failure); - return; - } - setState(() { - startDate = date; - }); - } else { - CustomToast.showToast(context, - message: 'Date is conflicted with other cycle', - toastType: ToastType.failure); - return; - } - } - - if (date != null) { - if (widget.fromModule) { - modulesProvider.updateModules( - disableLoading: true, - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - moduleId: widget.moduleId!, - data: { - 'start_date': DateFormat('yyyy-MM-dd').format(date) - }, - ref: ref); - } else { - cyclesProvider.cycleDetailsCrud( - disableLoading: true, - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - method: CRUD.update, - cycleId: widget.cycleId!, - data: { - 'start_date': DateFormat('yyyy-MM-dd').format(date) - }, - ); - } - } - }, - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: - themeProvider.themeManager.primaryBackgroundDefaultColor, - border: Border.all( - width: 1, - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: (detailData['start_date'] == null || - detailData['start_date'] == '') || - (detailData[widget.fromModule - ? 'target_date' - : 'end_date'] == - null || - detailData[widget.fromModule - ? 'target_date' - : 'end_date'] == - '') - ? CustomText( - 'Start Date', - type: FontStyle.Small, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.secondaryTextColor, - ) - : Row( - children: [ - Icon(Icons.calendar_today_outlined, - size: 15, - color: themeProvider - .themeManager.placeholderTextColor), - const SizedBox(width: 7), - CustomText( - '${dateFormating(detailData['start_date'])} ', - type: FontStyle.Small, - fontWeight: FontWeightt.Regular, - color: - themeProvider.themeManager.secondaryTextColor, - ), - ], - ), - ), - ), - //arrow - const SizedBox(width: 5), - Icon( - Icons.arrow_forward, - size: 15, - color: themeProvider.themeManager.placeholderTextColor, - ), - const SizedBox(width: 5), - GestureDetector( - onTap: () async { - if (projectProvider.role != Role.admin && - projectProvider.role != Role.member) { - CustomToast.showToast(context, - message: accessRestrictedMSG, - toastType: ToastType.failure); - return; - } - final date = await showDatePicker( - builder: (context, child) => Theme( - data: themeProvider.themeManager.datePickerThemeData, - child: child!, - ), - context: context, - initialDate: dueDate!, - firstDate: startDate ?? DateTime.now(), - lastDate: DateTime(2025), - ); - - if (date != null) { - if (!date.isAfter(DateTime.now())) { - CustomToast.showToast(context, - message: 'Due date not valid ', - toastType: ToastType.failure); - return; - } - if (date.isAfter(startDate!)) { - final bool dateNotConflicted = widget.fromModule - ? true - : await cyclesProvider.dateCheck( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - data: { - "cycle_id": widget.cycleId!, - "start_date": - DateFormat('yyyy-MM-dd').format(startDate!), - "end_date": DateFormat('yyyy-MM-dd').format(date), - }, - ); - - if (dateNotConflicted) { - setState(() { - dueDate = date; - }); - if (widget.fromModule) { - modulesProvider.updateModules( - disableLoading: true, - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - moduleId: widget.moduleId!, - data: { - 'target_date': - DateFormat('yyyy-MM-dd').format(date) - }, - ref: ref); - modulesProvider.changeTabIndex(1); - } else { - cyclesProvider.cycleDetailsCrud( - disableLoading: true, - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projectId: ref - .read(ProviderList.projectProvider) - .currentProject["id"], - method: CRUD.update, - cycleId: widget.cycleId!, - data: { - 'end_date': DateFormat('yyyy-MM-dd').format(date) - }, - ); - cyclesProvider.changeTabIndex(1); - } - } else { - CustomToast.showToast(context, - message: 'Date is conflicted with other cycle ', - toastType: ToastType.failure); - } - } else { - CustomToast.showToast(context, - message: 'Start date cannot be after end date ', - toastType: ToastType.failure); - } - } - }, - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: - themeProvider.themeManager.primaryBackgroundDefaultColor, - border: Border.all( - width: 1, - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: (detailData['start_date'] == null || - detailData['start_date'] == '') || - (detailData[widget.fromModule - ? 'target_date' - : 'end_date'] == - null || - detailData[widget.fromModule - ? 'target_date' - : 'end_date'] == - '') - ? CustomText('End Date', - type: FontStyle.Small, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.secondaryTextColor) - : Row( - children: [ - Icon(Icons.calendar_today_outlined, - size: 15, - color: themeProvider - .themeManager.placeholderTextColor), - const SizedBox(width: 5), - CustomText( - '${dateFormating(detailData[widget.fromModule ? 'target_date' : 'end_date'])} ', - type: FontStyle.Small, - fontWeight: FontWeightt.Regular, - color: themeProvider - .themeManager.secondaryTextColor), - ], - ), - ), - ), - ], - ), - ], - ); - } - - Widget detailsPart() { - final themeProvider = ref.watch(ProviderList.themeProvider); - return Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: CustomText( - 'Details', - type: FontStyle.Medium, - fontWeight: FontWeightt.Medium, - color: themeProvider.themeManager.primaryTextColor, - )), - const SizedBox(height: 10), - stateWidget(), - const SizedBox(height: 10), - assigneeWidget(), - widget.fromModule - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - membersWidget(), - ], - ) - : Container(), - ], - ); - } - - Widget progressPart() { - final themeProvider = ref.watch(ProviderList.themeProvider); - return Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: CustomText( - 'Progress', - type: FontStyle.Medium, - fontWeight: FontWeightt.Medium, - color: themeProvider.themeManager.primaryTextColor, - )), - const SizedBox(height: 10), - Container( - padding: - const EdgeInsets.only(left: 20, right: 30, top: 35, bottom: 20), - decoration: BoxDecoration( - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: SizedBox( - height: 200, - child: SfCartesianChart( - plotAreaBorderColor: Colors.transparent, - margin: EdgeInsets.zero, - primaryYAxis: NumericAxis( - majorGridLines: - const MajorGridLines(width: 0), // Remove major grid lines - ), - primaryXAxis: CategoryAxis( - labelPlacement: - LabelPlacement.betweenTicks, // Adjust label placement - interval: chartData.length > 5 ? 3 : 1, - majorGridLines: const MajorGridLines( - width: 0, - ), // Remove major grid lines - minorGridLines: const MinorGridLines(width: 0), - axisLabelFormatter: (axisLabelRenderArgs) { - return ChartAxisLabel( - DateFormat('dd MMM') - .format(DateTime.parse(axisLabelRenderArgs.text)), - const TextStyle(fontWeight: FontWeight.normal)); - }, - ), - series: [ - // Renders area chart - AreaSeries( - gradient: LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [ - Colors.transparent, - themeProvider.themeManager.primaryColour - .withOpacity(0.2), - themeProvider.themeManager.primaryColour - .withOpacity(0.3), - ]), - dataSource: chartData, - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y, - ), - LineSeries( - dashArray: [5.0, 5.0], - dataSource: chartData.isNotEmpty - ? [ - ChartData(chartData.first.x, - chartData.first.y), // First data point - ChartData(chartData.last.x, - 0.0), // Data point at current time with Y-value of last data point - ] - : [], - xValueMapper: (ChartData data, _) => data.x, - yValueMapper: (ChartData data, _) => data.y, - ), - ], - ), - ), - ), - ], - ); - } - - Widget assigneesPart({bool fromModule = false}) { - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); - final themeProvider = ref.watch(ProviderList.themeProvider); - - final detailData = widget.fromModule - ? modulesProvider.moduleDetailsData - : cyclesProvider.cyclesDetailsData; - - return Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: CustomText( - 'Assignees', - type: FontStyle.Medium, - fontWeight: FontWeightt.Medium, - color: themeProvider.themeManager.primaryTextColor, - )), - const SizedBox(height: 10), - detailData['distribution']['assignees'].length == 0 - ? const CustomText('No data found.') - : Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: - themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: Column( - children: [ - ...List.generate( - detailData['distribution']['assignees'].length, - (idx) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 8), - margin: const EdgeInsets.symmetric(vertical: 2), - decoration: BoxDecoration( - color: (issuesProvider.issues.filters.assignees - .contains(detailData['distribution'] - ['assignees'][idx]['assignee_id']) - ? themeProvider.themeManager - .secondaryBackgroundDefaultColor - : themeProvider.themeManager - .primaryBackgroundDefaultColor), - borderRadius: BorderRadius.circular(5), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 10), - child: detailData['distribution'] - ['assignees'][idx] - ['avatar'] != - null && - detailData['distribution'] - ['assignees'][idx] - ['avatar'] != - '' - ? CircleAvatar( - radius: 10, - backgroundImage: NetworkImage( - detailData['distribution'] - ['assignees'][idx] - ['avatar']), - ) - : CircleAvatar( - radius: 10, - backgroundColor: themeProvider - .themeManager - .tertiaryBackgroundDefaultColor, - child: Center( - child: CustomText( - detailData['distribution'][ - 'assignees'] - [idx] - ['first_name'] != - null - ? detailData['distribution'] - [ - 'assignees'] - [idx] - ['first_name'][0] - .toString() - .toUpperCase() - : '', - type: FontStyle.Small, - ), - ), - )), - CustomText( - detailData['distribution']['assignees'][idx] - ['display_name'] ?? - 'No Assignees', - color: themeProvider - .themeManager.secondaryTextColor, - ), - ], - ), - CompletionPercentage( - value: detailData['distribution']['assignees'] - [idx]['completed_issues'], - totalValue: detailData['distribution'] - ['assignees'][idx]['total_issues']) - ], - ), - ); - }, - ), - ], - ), - ), - ], - ); - } - - Widget statesPart() { - final List states = [ - "Backlog", - "Unstarted", - "Started", - "Cancelled", - "Completed", - ]; - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final themeProvider = ref.watch(ProviderList.themeProvider); - - final detailData = widget.fromModule - ? modulesProvider.moduleDetailsData - : cyclesProvider.cyclesDetailsData; - - return Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: CustomText( - 'States', - type: FontStyle.Medium, - fontWeight: FontWeightt.Medium, - color: themeProvider.themeManager.primaryTextColor, - )), - const SizedBox(height: 10), - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: Column( - children: [ - ...List.generate( - states.length, - (index) => Row( - children: [ - const SizedBox(height: 50), - Padding( - padding: const EdgeInsets.only(right: 10), - child: SvgPicture.asset( - states[index] == 'Backlog' - ? 'assets/svg_images/circle.svg' - : states[index] == 'Unstarted' - ? 'assets/svg_images/in_progress.svg' - : states[index] == 'Started' - ? 'assets/svg_images/done.svg' - : states[index] == 'Cancelled' - ? 'assets/svg_images/cancelled.svg' - : 'assets/svg_images/circle.svg', - height: 22, - width: 22, - colorFilter: ColorFilter.mode( - index == 0 - ? const Color(0xFFCED4DA) - : index == 1 - ? const Color(0xFF26B5CE) - : index == 2 - ? const Color(0xFFF7AE59) - : index == 3 - ? const Color(0xFFD687FF) - : greenHighLight, - BlendMode.srcIn)), - ), - CustomText( - states[index], - type: FontStyle.Large, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.secondaryTextColor, - ), - const Spacer(), - index == 0 - ? CompletionPercentage( - value: detailData['backlog_issues'], - totalValue: detailData['total_issues']) - : index == 1 - ? CompletionPercentage( - value: detailData['unstarted_issues'], - totalValue: detailData['total_issues']) - : index == 2 - ? CompletionPercentage( - value: detailData['started_issues'], - totalValue: detailData['total_issues']) - : index == 3 - ? CompletionPercentage( - value: detailData['cancelled_issues'], - totalValue: detailData['total_issues']) - : CompletionPercentage( - value: detailData['completed_issues'], - totalValue: detailData['total_issues'], - ), - ], - ), - ), - ], - ), - ), - ], - ); - } - - Widget labelsPart({bool fromModule = false}) { - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final themeProvider = ref.watch(ProviderList.themeProvider); - - final detailData = widget.fromModule - ? modulesProvider.moduleDetailsData - : cyclesProvider.cyclesDetailsData; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Align( - alignment: Alignment.centerLeft, - child: CustomText( - 'Labels', - type: FontStyle.Medium, - fontWeight: FontWeightt.Medium, - color: themeProvider.themeManager.primaryTextColor, - )), - const SizedBox(height: 10), - detailData['distribution']['labels'].isNotEmpty - ? Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: - themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: ListView.builder( - shrinkWrap: true, - primary: false, - itemCount: detailData['distribution']['labels'].length, - itemBuilder: (context, index) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 8), - margin: const EdgeInsets.symmetric(vertical: 2), - decoration: BoxDecoration( - color: issuesProvider.issues.filters.labels.contains( - detailData['distribution']['labels'][index] - ['label_id']) - ? themeProvider - .themeManager.secondaryBackgroundDefaultColor - : themeProvider - .themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Icon( - Icons.circle, - size: 20, - color: detailData['distribution']['labels'] - [index]['label_name'] == - null - ? themeProvider.themeManager - .tertiaryBackgroundDefaultColor - : detailData['distribution']['labels'] - [index]['color'] == - '' || - detailData['distribution'] - ['labels'][index] - ['color'] == - null - ? themeProvider - .themeManager.placeholderTextColor - : detailData['distribution']['labels'] - [index]['color'] - .toString() - .toColor(), - ), - const SizedBox( - width: 10, - ), - SizedBox( - width: width * 0.4, - child: CustomText( - detailData['distribution']['labels'][index] - ['label_name'] ?? - 'No Label', - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - Row( - children: [ - CompletionPercentage( - value: detailData['distribution']['labels'] - [index]['completed_issues'], - totalValue: detailData['distribution'] - ['labels'][index]['total_issues']) - ], - ) - ], - ), - ); - }), - ) - : const Align( - alignment: Alignment.center, - child: CustomText('No data found'), - ) - ], - ); - } - - Widget stateWidget({bool fromModule = false}) { - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final themeProvider = ref.watch(ProviderList.themeProvider); - return Container( - height: 45, - width: double.infinity, - decoration: BoxDecoration( - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: Row( - children: [ - //icon - Icon( - //four squares icon - Icons.timelapse_rounded, - color: themeProvider.themeManager.placeholderTextColor), - const SizedBox(width: 15), - CustomText( - 'Progress', - type: FontStyle.Medium, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.placeholderTextColor, - ), - Expanded(child: Container()), - - CompletionPercentage( - value: widget.fromModule - ? modulesProvider.moduleDetailsData['completed_issues'] - : cyclesProvider.cyclesDetailsData['completed_issues'], - totalValue: widget.fromModule - ? modulesProvider.moduleDetailsData['total_issues'] - : cyclesProvider.cyclesDetailsData['total_issues']) - ], - ), - ), - ); - } - - Widget membersWidget() { - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final themeProvider = ref.watch(ProviderList.themeProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); - return GestureDetector( - onTap: () { - if (projectProvider.role != Role.admin && - projectProvider.role != Role.member) { - CustomToast.showToast(context, - message: accessRestrictedMSG, toastType: ToastType.failure); - return; - } - showModalBottomSheet( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.5, - ), - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - context: context, - builder: (context) => AssigneeSheet( - fromModuleDetail: widget.fromModule, - ), - ); - }, - child: Container( - height: 45, - width: double.infinity, - decoration: BoxDecoration( - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: Row( - children: [ - //icon - Icon( - //two people icon - Icons.people_alt_rounded, - color: themeProvider.themeManager.placeholderTextColor, - ), - const SizedBox(width: 15), - CustomText( - 'Members', - type: FontStyle.Medium, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.placeholderTextColor, - ), - Expanded(child: Container()), - (modulesProvider.currentModule['members_detail'] == null || - modulesProvider.currentModule['members_detail'].isEmpty) - ? Row( - children: [ - CustomText( - 'No members', - type: FontStyle.Medium, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.primaryTextColor, - ), - const SizedBox( - width: 5, - ), - Icon( - Icons.keyboard_arrow_down, - color: themeProvider.themeManager.primaryTextColor, - ), - ], - ) - : SizedBox( - height: 27, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.center, - child: SizedBox( - height: 30, - child: SquareAvatarWidget( - borderRadius: 50, - details: modulesProvider - .currentModule['members_detail']), - ), - ), - ) - ], - ), - ), - ), - ); - } - - Widget assigneeWidget() { - final cyclesProvider = ref.watch(ProviderList.cyclesProvider); - final modulesProvider = ref.watch(ProviderList.modulesProvider); - final themeProvider = ref.watch(ProviderList.themeProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); - final detailData = widget.fromModule - ? modulesProvider.moduleDetailsData - : cyclesProvider.cyclesDetailsData; - - return GestureDetector( - onTap: widget.fromModule - ? () { - if (projectProvider.role != Role.admin && - projectProvider.role != Role.member) { - CustomToast.showToast(context, - message: accessRestrictedMSG, toastType: ToastType.failure); - return; - } - showModalBottomSheet( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.5, - ), - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - context: context, - builder: (context) => LeadSheet( - fromModuleDetail: widget.fromModule, - ), - ); - } - : () {}, - child: Container( - height: 45, - width: double.infinity, - decoration: BoxDecoration( - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: themeProvider.themeManager.borderSubtle01Color, - ), - ), - child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - //icon - Icon( - //two people icon - Icons.people_outline_outlined, - color: themeProvider.themeManager.placeholderTextColor, - ), - const SizedBox(width: 15), - CustomText( - 'Lead', - type: FontStyle.Medium, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.placeholderTextColor, - ), - const Spacer(), - detailData[widget.fromModule ? 'lead_detail' : 'owned_by'] == null - ? CustomText( - 'No lead', - type: FontStyle.Medium, - fontWeight: FontWeightt.Regular, - color: themeProvider.themeManager.primaryTextColor, - ) - : Row( - children: [ - MemberLogoWidget( - fontType: FontStyle.Small, - boarderRadius: 50, - padding: EdgeInsets.zero, - size: 25, - imageUrl: (detailData[widget.fromModule - ? 'lead_detail' - : 'owned_by']['avatar']), - colorForErrorWidget: - const Color.fromRGBO(55, 65, 81, 1), - memberNameFirstLetterForErrorWidget: detailData[ - widget.fromModule - ? 'lead_detail' - : 'owned_by']['display_name'][0] - .toString()), - const SizedBox(width: 5), - Container( - constraints: BoxConstraints(maxWidth: width * 0.4), - child: CustomText( - (detailData[widget.fromModule - ? 'lead_detail' - : 'owned_by'] != - null && - detailData[widget.fromModule - ? 'lead_detail' - : 'owned_by']['display_name'] != - null) - ? detailData[widget.fromModule - ? 'lead_detail' - : 'owned_by']['display_name'] ?? - '' - : '', - type: FontStyle.Small, - maxLines: 1, - ), - ), - ], - ), - ], - ), - ), - ), - ); - } - - String dateFormating(String date) { - final DateTime formatedDate = DateTime.parse(date); - final String finalDate = DateFormat('dd MMM').format(formatedDate); - return finalDate; - } - - String checkDate({required String startDate, required String endDate}) { - final DateTime now = DateTime.now(); - if ((startDate.isEmpty) || (endDate.isEmpty)) { - return 'Draft'; - } else { - if (DateTime.parse(startDate).isAfter(now)) { - final Duration difference = - DateTime.parse(startDate.split('+').first).difference(now); - if (difference.inDays == 0) { - return 'Today'; - } else { - return '${difference.inDays.abs() + 1} Days Left'; - } - } - if (DateTime.parse(startDate).isBefore(now) && - DateTime.parse(endDate).isAfter(now)) { - final Duration difference = DateTime.parse(endDate).difference(now); - if (difference.inDays == 0) { - return 'Today'; - } else { - return '${difference.inDays.abs() + 1} Days Left'; - } - } else { - return 'Completed'; - } - } - } - - String checkTimeDifferenc(String dateTime) { - final DateTime now = DateTime.now(); - final Duration difference = now.difference(DateTime.parse(dateTime)); - String? format; - - if (difference.inDays > 0) { - format = '${difference.inDays} days ago'; - } else if (difference.inHours > 0) { - format = '${difference.inHours} hours ago'; - } else if (difference.inMinutes > 0) { - format = '${difference.inMinutes} minutes ago'; - } else { - format = '${difference.inSeconds} seconds ago'; - } - - return format; - } -} diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/project_detail.dart b/lib/screens/MainScreens/Projects/ProjectDetail/project_detail.dart deleted file mode 100644 index 705c61f7..00000000 --- a/lib/screens/MainScreens/Projects/ProjectDetail/project_detail.dart +++ /dev/null @@ -1,1120 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/filters/filter_sheet.dart'; -import 'package:plane/bottom_sheets/page_filter_sheet.dart'; -import 'package:plane/bottom_sheets/type_sheet.dart'; -import 'package:plane/bottom_sheets/views_sheet.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/archived_issues.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/calender_view.dart'; -import 'package:plane/kanban/custom/board.dart'; -// import 'package:google_fonts/google_fonts.dart'; -import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/project_details_cycles.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ModulesTab/module_screen.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/spreadsheet_view.dart'; -import 'package:plane/screens/MainScreens/Projects/create_page_screen.dart'; -import 'package:plane/screens/settings_screen.dart'; -import 'package:plane/utils/constants.dart'; -import 'package:plane/utils/enums.dart'; -import 'package:plane/widgets/custom_app_bar.dart'; -import 'package:plane/widgets/custom_text.dart'; -import 'package:plane/widgets/empty.dart'; -import 'package:plane/widgets/error_state.dart'; -import 'package:plane/widgets/loading_widget.dart'; - -import '../../../../kanban/models/inputs.dart'; -import '../../../../utils/custom_toast.dart'; -import '../../../create_view_screen.dart'; -import 'CyclesTab/create_cycle.dart'; -import 'ModulesTab/create_module.dart'; - -class ProjectDetail extends ConsumerStatefulWidget { - const ProjectDetail({super.key, required this.index}); - final int index; - - @override - ConsumerState createState() => _ProjectDetailState(); -} - -class _ProjectDetailState extends ConsumerState { - // final tabs = [ - // {'title': 'Issues', 'width': 60}, - // {'title': 'Cycles', 'width': 60}, - // {'title': 'Modules', 'width': 75}, - // {'title': 'Views', 'width': 60}, - // {'title': 'Pages', 'width': 50}, - // ]; - final controller = PageController(initialPage: 0); - - int selected = 0; - List pages = []; - int numberOfTabs = 0; - - @override - void initState() { - final issueProvider = ref.read(ProviderList.issuesProvider); - issueProvider.orderByState = StateEnum.loading; - issueProvider.statesState = StateEnum.restricted; - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - issueProvider.setsState(); - issueProvider.statesState = StateEnum.restricted; - - ref.read(ProviderList.projectProvider).initializeProject(ref: ref); - //issueProvider.statesState = StateEnum.restricted; - }); - - // pages = [ - // cycles(), - // cycles(), - // const ModuleScreen(), - // const Views(), - // //const PageScreen() - // ]; - - super.initState(); - } - - void setPages() { - final projectProvider = ref.watch(ProviderList.projectProvider); - - pages = [ - { - 'title': 'Issues', - 'width': 60, - 'show': true, - 'page': issues(context, ref) - }, - ]; - if (projectProvider.features[1]['show'] == true) { - pages.add( - {'title': 'Cycles', 'width': 60, 'show': true, 'page': cycles()}); - } - if (projectProvider.features[2]['show'] == true) { - pages.add({ - 'title': 'Modules', - 'width': 75, - 'show': true, - 'page': const ModuleScreen() - }); - } - if (projectProvider.features[3]['show'] == true) { - pages.add( - {'title': 'Views', 'width': 60, 'show': true, 'page': const Views()}); - } - } - - @override - Widget build(BuildContext context) { - final themeProvider = ref.watch(ProviderList.themeProvider); - final issueProvider = ref.watch(ProviderList.issuesProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); - final cycleProvider = ref.watch(ProviderList.cyclesProvider); - final moduleProvider = ref.watch(ProviderList.modulesProvider); - final viewsProvider = ref.watch(ProviderList.viewsProvider); - final pageProvider = ref.watch(ProviderList.pageProvider); - - setPages(); - - // log(issueProvider.issues.groupBY.name); - - return Scaffold( - appBar: CustomAppBar( - elevation: false, - onPressed: () { - Navigator.pop(context); - }, - text: ref.read(ProviderList.projectProvider).currentProject['name'], - actions: [ - (projectProvider.currentProject['archive_in'] > 0 && - (projectProvider.role == Role.admin || - projectProvider.role == Role.member) && - (issueProvider.statesState == StateEnum.success)) - ? IconButton( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => const ArchivedIssues(), - ), - ); - }, - icon: Icon( - Icons.archive_outlined, - color: themeProvider.themeManager.placeholderTextColor, - )) - : Container(), - (issueProvider.statesState == StateEnum.success) - ? IconButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SettingScreen(), - ), - ).then((value) { - int count = 0; - for (int i = 0; - i < projectProvider.features.length; - i++) { - if (projectProvider.features[i]['show']) { - count++; - } - } - if (count < selected + 1) { - setState(() { - selected = count - 1; - }); - } - }); - }, - icon: issueProvider.statesState == StateEnum.restricted - ? Container() - : Icon( - Icons.settings_outlined, - color: - themeProvider.themeManager.placeholderTextColor, - ), - ) - : Container(), - ], - ), - floatingActionButton: selected != 0 && - (projectProvider.role == Role.admin || - projectProvider.role == Role.member) && - ((selected == 1 && cycleProvider.showAddFloatingButton()) || - (selected == 2 && - moduleProvider.moduleState != StateEnum.loading && - (moduleProvider.modules.isNotEmpty || - moduleProvider.favModules.isNotEmpty)) || - (selected == 3 && - viewsProvider.viewsState != StateEnum.loading && - viewsProvider.views.isNotEmpty)) - ? FloatingActionButton( - backgroundColor: themeProvider.themeManager.primaryColour, - child: Icon( - Icons.add, - color: themeProvider.themeManager.textonColor, - ), - onPressed: () { - if (selected == 1 && projectProvider.features[1]['show']) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => const CreateCycle(), - ), - ); - } - - if (selected == 1 && - !projectProvider.features[1]['show'] && - projectProvider.features[2]['show']) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => const CreateModule(), - ), - ); - } - - if (selected == 1 && - !projectProvider.features[1]['show'] && - !projectProvider.features[2]['show']) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => const CreateView(), - ), - ); - } - - if (selected == 2 && - projectProvider.features[2]['show'] && - projectProvider.features[1]['show']) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => const CreateModule(), - ), - ); - } - - if ((selected == 2 && - projectProvider.features[2]['show'] && - !projectProvider.features[1]['show']) || - (selected == 2 && - !projectProvider.features[2]['show'] && - projectProvider.features[1]['show'])) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => const CreateView(), - ), - ); - } - if (selected == 3) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => const CreateView(), - ), - ); - } - // if (selected == 4) { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (ctx) => const CreatePage(), - // ), - // ); - // } - }, - ) - : Container(), - body: SafeArea( - child: issueProvider.statesState == StateEnum.restricted - ? EmptyPlaceholder.joinProject( - context, - ref, - projectProvider.currentProject['id'], - ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug) - : Container( - // color: themeProvider.backgroundColor, - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - child: projectProvider.projectDetailState == StateEnum.error - ? errorState( - context: context, - ontap: () { - ref - .read(ProviderList.projectProvider) - .initializeProject(ref: ref); - }) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - issueProvider.statesState != StateEnum.loading - ? Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: pages.map((e) { - return e['show'] - ? Expanded( - child: InkWell( - onTap: () { - controller.jumpToPage( - pages.indexOf(e)); - setState(() { - selected = pages.indexOf(e); - }); - }, - child: Column( - children: [ - Container( - margin: const EdgeInsets - .symmetric(vertical: 8), - child: CustomText( - e['title'].toString(), - color: pages.indexOf(e) == - selected - ? themeProvider - .themeManager - .primaryColour - : themeProvider - .themeManager - .placeholderTextColor, - overrride: true, - type: FontStyle.Medium, - fontWeight: pages - .indexOf(e) == - selected - ? FontWeightt.Medium - : null, - ), - ), - selected == - pages - .indexOf(e) && - (pages.elementAt(pages - .indexOf( - e))[ - 'show'] == - true || - projectProvider - .features - .elementAt( - pages.indexOf( - e))['show'] == - true) - ? Container( - height: 6, - decoration: - BoxDecoration( - borderRadius: - BorderRadius - .circular( - 10), - color: themeProvider - .themeManager - .primaryColour, - ), - ) - : Container( - height: 6, - ) - ], - ), - ), - ) - : Container(); - }).toList(), - ) - : Container(), - Container( - height: 2, - width: MediaQuery.of(context).size.width, - color: - themeProvider.themeManager.borderSubtle01Color, - ), - Expanded( - child: PageView.builder( - controller: controller, - onPageChanged: (page) { - setState(() { - selected = page; - }); - }, - itemBuilder: (ctx, index) { - return Container( - child: index == 0 - ? issues(ctx, ref) - : pages[index]['page']); - }, - itemCount: pages.length, - ), - ), - issueProvider.statesState == StateEnum.loading || - issueProvider.issueState == StateEnum.loading - ? Container() - : selected == 0 && - issueProvider.statesState == - StateEnum.restricted - ? Container() - : selected == 0 && - issueProvider.statesState == - StateEnum.success - ? Container( - decoration: BoxDecoration( - color: themeProvider.themeManager - .primaryBackgroundDefaultColor, - boxShadow: themeProvider - .themeManager - .shadowBottomControlButtons, - ), - height: 50, - width: - MediaQuery.of(context).size.width, - child: Row( - children: [ - projectProvider.role == - Role.admin || - projectProvider.role == - Role.member - ? Expanded( - child: InkWell( - onTap: () { - issueProvider - .createIssuedata[ - 'state'] = - issueProvider - .states - .keys - .first; - - Navigator.of(context) - .push( - MaterialPageRoute( - builder: (context) => - const CreateIssue(), - ), - ); - }, - child: SizedBox.expand( - child: Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons.add, - color: themeProvider - .themeManager - .primaryTextColor, - size: 20, - ), - const CustomText( - ' Issue', - type: FontStyle - .Medium, - ) - ], - ), - ), - ), - ) - : Container(), - Container( - height: 50, - width: 0.5, - color: themeProvider - .themeManager - .borderSubtle01Color, - ), - Expanded( - child: InkWell( - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: - BoxConstraints( - maxHeight: - height * 0.5), - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius - .only( - topLeft: - Radius.circular(30), - topRight: - Radius.circular(30), - )), - context: context, - builder: (ctx) { - return const TypeSheet( - issueCategory: - IssueCategory - .issues, - ); - }); - }, - child: SizedBox.expand( - child: Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons.list_outlined, - color: themeProvider - .themeManager - .primaryTextColor, - size: 19, - ), - const CustomText( - ' Layout', - type: FontStyle.Medium, - ) - ], - ), - ), - )), - Container( - height: 50, - width: 0.5, - color: themeProvider - .themeManager - .borderSubtle01Color, - ), - issueProvider - .issues.projectView == - ProjectView.calendar - ? Container() - : Expanded( - child: InkWell( - onTap: () { - showModalBottomSheet( - isScrollControlled: - true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: MediaQuery.of( - context) - .size - .height * - 0.9), - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius - .only( - topLeft: Radius - .circular(30), - topRight: Radius - .circular(30), - )), - context: context, - builder: (ctx) { - return ViewsSheet( - projectView: - issueProvider - .issues - .projectView, - issueCategory: - IssueCategory - .issues, - ); - }); - }, - child: SizedBox.expand( - child: Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons - .wysiwyg_outlined, - color: themeProvider - .themeManager - .primaryTextColor, - size: 19, - ), - const CustomText( - ' Display', - type: FontStyle - .Medium, - ) - ], - ), - ), - )), - Container( - height: 50, - width: 0.5, - color: themeProvider - .themeManager - .borderSubtle01Color, - ), - Expanded( - child: InkWell( - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, - constraints: BoxConstraints( - minHeight: - MediaQuery.of( - context) - .size - .height * - 0.75, - maxHeight: - MediaQuery.of( - context) - .size - .height * - 0.85), - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius - .only( - topLeft: - Radius.circular(30), - topRight: - Radius.circular(30), - )), - context: context, - builder: (ctx) { - return FilterSheet( - issueCategory: - IssueCategory - .issues, - ); - }); - }, - child: SizedBox.expand( - child: Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons - .filter_list_outlined, - color: themeProvider - .themeManager - .primaryTextColor, - size: 19, - ), - const CustomText( - ' Filters', - type: FontStyle.Medium, - ) - ], - ), - ), - )), - ], - ), - ) - : Container(), - selected == 4 - ? selected == 4 && - pageProvider - .pages[pageProvider.selectedFilter]! - .isEmpty - ? Container() - : Container( - height: 51, - width: MediaQuery.of(context).size.width, - decoration: BoxDecoration( - color: themeProvider.themeManager - .primaryBackgroundDefaultColor, - boxShadow: themeProvider.themeManager - .shadowBottomControlButtons, - ), - child: Column( - children: [ - SizedBox( - height: 50, - child: Row( - children: [ - projectProvider.role == - Role.admin - ? Expanded( - child: InkWell( - onTap: () { - Navigator.of( - context) - .push( - MaterialPageRoute( - builder: - (context) => - const CreatePage(), - ), - ); - }, - child: - SizedBox.expand( - child: Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons.add, - color: themeProvider - .themeManager - .primaryTextColor, - size: 20, - ), - const CustomText( - ' Page', - type: FontStyle - .Medium, - ) - ], - ), - ), - ), - ) - : Container(), - Container( - height: 50, - width: 0.5, - color: themeProvider - .themeManager - .borderSubtle01Color, - ), - Expanded( - child: InkWell( - onTap: () { - showModalBottomSheet( - isScrollControlled: - true, - enableDrag: true, - constraints: BoxConstraints( - maxHeight: MediaQuery.of( - context) - .size - .height * - 0.8), - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius - .only( - topLeft: - Radius.circular( - 30), - topRight: - Radius.circular( - 30), - )), - context: context, - builder: (ctx) { - return const FilterPageSheet(); - }); - }, - child: SizedBox.expand( - child: Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons - .filter_list_outlined, - color: themeProvider - .themeManager - .primaryTextColor, - size: 19, - ), - const CustomText( - ' Filters', - type: - FontStyle.Medium, - ) - ], - ), - ), - )), - ], - ), - ), - ], - ), - ) - : Container(), - ], - ), - ), - ), - ); - } -} - -Widget issues(BuildContext context, WidgetRef ref, {bool isViews = false}) { - final themeProvider = ref.watch(ProviderList.themeProvider); - final issueProvider = ref.watch(ProviderList.issuesProvider); - final projectProvider = ref.watch(ProviderList.projectProvider); - // log(issueProvider.issueState.name); - if (issueProvider.issues.projectView == ProjectView.list && - !(issueProvider.issuePropertyState == StateEnum.loading || - issueProvider.issueState == StateEnum.loading || - issueProvider.statesState == StateEnum.loading || - issueProvider.projectViewState == StateEnum.loading || - issueProvider.orderByState == StateEnum.loading)) { - issueProvider.initializeBoard(views: isViews); - } - - // log('Project Issues'); - // log(issueProvider.issues.projectView.toString()); - - return LoadingWidget( - loading: issueProvider.issuePropertyState == StateEnum.loading || - issueProvider.issueState == StateEnum.loading || - issueProvider.statesState == StateEnum.loading || - issueProvider.projectViewState == StateEnum.loading || - issueProvider.orderByState == StateEnum.loading, - widgetClass: issueProvider.statesState == StateEnum.restricted - ? EmptyPlaceholder.joinProject( - context, - ref, - projectProvider.currentProject['id'], - ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug) - : Container( - color: themeProvider.themeManager.secondaryBackgroundDefaultColor, - padding: issueProvider.issues.projectView == ProjectView.kanban - ? const EdgeInsets.only(top: 15, left: 0) - : null, - child: issueProvider.issueState == StateEnum.loading || - issueProvider.statesState == StateEnum.loading || - issueProvider.projectViewState == StateEnum.loading || - issueProvider.orderByState == StateEnum.loading - ? Container() - : issueProvider.isISsuesEmpty - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - EmptyPlaceholder.emptyIssues(context, ref: ref), - ], - ) - : issueProvider.issues.projectView == ProjectView.list - ? Container( - color: themeProvider - .themeManager.secondaryBackgroundDefaultColor, - margin: const EdgeInsets.only(top: 5), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: issueProvider.issues.issues - .map((state) => state.items.isEmpty && - !issueProvider.showEmptyStates - ? Container() - : SizedBox( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Container( - padding: - const EdgeInsets.only( - left: 15), - margin: - const EdgeInsets.only( - bottom: 10), - child: Row( - children: [ - state.leading ?? - Container(), - Container( - padding: - const EdgeInsets - .only( - left: 10, - ), - width: MediaQuery.of( - context) - .size - .width * - 0.6, - child: CustomText( - state.title!, - overflow: - TextOverflow - .ellipsis, - maxLines: 1, - type: - FontStyle.Large, - color: themeProvider - .themeManager - .primaryTextColor, - fontWeight: - FontWeightt - .Semibold, - ), - ), - Container( - alignment: - Alignment.center, - margin: - const EdgeInsets - .only( - left: 15, - ), - decoration: BoxDecoration( - borderRadius: - BorderRadius - .circular( - 15), - color: themeProvider - .themeManager - .tertiaryBackgroundDefaultColor), - height: 25, - width: 30, - child: CustomText( - state.items.length - .toString(), - type: - FontStyle.Small, - ), - ), - const Spacer(), - projectProvider.role == - Role - .admin || - projectProvider - .role == - Role.member - ? IconButton( - onPressed: () { - if (issueProvider - .issues - .groupBY == - GroupBY - .state) { - issueProvider - .createIssuedata['state'] = - state - .id; - } else { - issueProvider - .createIssuedata['prioriry'] = - 'de3c90cd-25cd-42ec-ac6c-a66caf8029bc'; - // createIssuedata['s'] = element.id; - } - Navigator.of( - context) - .push(MaterialPageRoute( - builder: (ctx) => - const CreateIssue())); - }, - icon: Icon( - Icons.add, - color: themeProvider - .themeManager - .tertiaryTextColor, - )) - : Container( - height: 40, - ), - const SizedBox( - width: 10, - ), - ], - ), - ), - Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - children: state.items - .map((e) => e) - .toList()), - state.items.isEmpty - ? Container( - margin: - const EdgeInsets - .only( - bottom: 10), - width: MediaQuery.of( - context) - .size - .width, - color: themeProvider - .themeManager - .primaryBackgroundDefaultColor, - padding: - const EdgeInsets - .only( - top: 15, - bottom: 15, - left: 15), - child: - const CustomText( - 'No issues.', - type: - FontStyle.Small, - maxLines: 10, - textAlign: - TextAlign.start, - ), - ) - : Container( - margin: - const EdgeInsets - .only( - bottom: 10), - ) - ], - ), - )) - .toList()), - ), - ) - : issueProvider.issues.projectView == ProjectView.kanban - ? KanbanBoard( - issueProvider.initializeBoard(views: isViews), - boardID: 'issues-board', - isCardsDraggable: - issueProvider.checkIsCardsDaraggable(), - onItemReorder: ( - {newCardIndex, - newListIndex, - oldCardIndex, - oldListIndex}) { - // print('newCardIndex: $newCardIndex, newListIndex: $newListIndex, oldCardIndex: $oldCardIndex, oldListIndex: $oldListIndex'); - issueProvider - .reorderIssue( - context: context, - newCardIndex: newCardIndex!, - newListIndex: newListIndex!, - oldCardIndex: oldCardIndex!, - oldListIndex: oldListIndex!, - ) - .then((value) { - if (issueProvider.issues.orderBY != - OrderBY.manual) { - CustomToast.showToast(context, - message: - 'This board is ordered by ${issueProvider.issues.orderBY == OrderBY.lastUpdated ? 'last updated' : 'created at'} ', - toastType: ToastType.warning); - } - }).catchError((e) { - CustomToast.showToast(context, - message: 'Failed to update issue', - toastType: ToastType.failure); - }); - }, - groupEmptyStates: - !issueProvider.showEmptyStates, - backgroundColor: themeProvider.themeManager - .secondaryBackgroundDefaultColor, - cardPlaceHolderColor: themeProvider - .themeManager.primaryBackgroundDefaultColor, - cardPlaceHolderDecoration: BoxDecoration( - color: themeProvider.themeManager - .primaryBackgroundDefaultColor, - boxShadow: [ - BoxShadow( - blurRadius: 2, - color: themeProvider - .themeManager.borderSubtle01Color, - spreadRadius: 0, - ), - ]), - listScrollConfig: ScrollConfig( - offset: 65, - duration: const Duration(milliseconds: 100), - curve: Curves.linear), - boardScrollConfig: ScrollConfig( - offset: 45, - duration: const Duration(milliseconds: 100), - curve: Curves.linear), - listTransitionDuration: - const Duration(milliseconds: 200), - cardTransitionDuration: - const Duration(milliseconds: 400), - ) - : issueProvider.issues.projectView == - ProjectView.calendar - ? const CalendarView() - : const SpreadSheetView( - issueCategory: IssueCategory.issues, - ), - ), - ); -} - -Widget cycles() { - return const CycleWidget(); -} - -Widget view(WidgetRef ref) { - final themeProvider = ref.read(ProviderList.themeProvider); - return Container( - color: themeProvider.themeManager.primaryBackgroundDefaultColor, - padding: const EdgeInsets.only(left: 20, right: 20, top: 15), - child: const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // SizedBox( - // child: Text( - // 'Current Cycles', - // style: TextStyle(fontSize: 20, fontWeight: FontWeightt.Medium), - // ), - // ), - Views() - ], - ), - ); -} - -// bool checkVisbility(int index) { -// final featuresProvider = ref.watch(ProviderList.featuresProvider); -// if(featuresProvider.features[index]['title'] == featuresProvider.features[index]){ -// return true; -// } -// return false; -// } diff --git a/lib/screens/MainScreens/Activity/activity.dart b/lib/screens/activity/activity.dart similarity index 99% rename from lib/screens/MainScreens/Activity/activity.dart rename to lib/screens/activity/activity.dart index 7bc642c4..33ba11c6 100644 --- a/lib/screens/MainScreens/Activity/activity.dart +++ b/lib/screens/activity/activity.dart @@ -5,7 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/user_profile.dart'; +import 'package:plane/screens/profile/user-profile/user_profile.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; @@ -16,8 +17,6 @@ import 'package:plane/widgets/error_state.dart'; import 'package:plane/widgets/loading_widget.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class Activity extends ConsumerStatefulWidget { const Activity({super.key}); diff --git a/lib/screens/create_state.dart b/lib/screens/create_state.dart index 2163c3f4..6d755bbd 100644 --- a/lib/screens/create_state.dart +++ b/lib/screens/create_state.dart @@ -47,7 +47,8 @@ class _CreateStateState extends ConsumerState { @override Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + final statesNotifier = ref.read(ProviderList.statesProvider.notifier); return Scaffold( appBar: CustomAppBar( onPressed: () { @@ -56,7 +57,7 @@ class _CreateStateState extends ConsumerState { text: 'Create State', ), body: LoadingWidget( - loading: issuesProvider.statesState == StateEnum.loading, + loading: statesProvider.statesState == StateEnum.loading, widgetClass: SingleChildScrollView( child: Form( key: formKey, @@ -400,14 +401,7 @@ class _CreateStateState extends ConsumerState { text: 'Create State', ontap: () async { if (!formKey.currentState!.validate()) return; - await issuesProvider.createState( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .read(ProviderList.projectProvider) - .currentProject["id"], + await statesNotifier.createState( data: { "name": name.text, "color": "#${colorController.text}", diff --git a/lib/screens/create_view_screen.dart b/lib/screens/create_view_screen.dart index 3287c7ed..2abf5bfc 100644 --- a/lib/screens/create_view_screen.dart +++ b/lib/screens/create_view_screen.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/filters/filter_sheet.dart'; +import 'package:plane/bottom-sheets/filters/filter_sheet.dart'; import 'package:plane/models/issues.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; @@ -108,6 +108,8 @@ class _CreateViewState extends ConsumerState { final viewsProvider = ref.watch(ProviderList.viewsProvider); final issuesProvider = ref.watch(ProviderList.issuesProvider); final projectProvider = ref.watch(ProviderList.projectProvider); + final labelProvider = ref.watch(ProviderList.labelProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); return GestureDetector( onTap: () { FocusScope.of(context).unfocus(); @@ -250,190 +252,215 @@ class _CreateViewState extends ConsumerState { itemCount: filtersData['Filters']!.length, primary: false, shrinkWrap: true, - itemBuilder: - ((context, index) => - filtersData['Filters']! - .values - .elementAt(index) - .isNotEmpty - ? Container( - padding: - const EdgeInsets.only( - top: 10, - bottom: 10, - left: 10, - right: 10), - margin: const EdgeInsets.only( - bottom: 10), - decoration: BoxDecoration( + itemBuilder: ((context, index) => + filtersData['Filters']! + .values + .elementAt(index) + .isNotEmpty + ? Container( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 10, + right: 10), + margin: const EdgeInsets.only( + bottom: 10), + decoration: BoxDecoration( + color: themeProvider + .themeManager + .primaryBackgroundDefaultColor, + borderRadius: + BorderRadius.circular(4), + border: Border.all( color: themeProvider .themeManager - .primaryBackgroundDefaultColor, - borderRadius: - BorderRadius.circular( - 4), - border: Border.all( - color: themeProvider - .themeManager - .borderStrong01Color)), - child: Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - children: [ - CustomText( - filterKeys[index], - fontWeight: FontWeightt - .Semibold, - ), - const SizedBox( - height: 10, - ), - Wrap( - children: ((filtersData['Filters'] as Map).values.elementAt(index) as List) - .map((e) => filterKeys[index] == 'Priority:' - ? GestureDetector( - onTap: () { - log(e); - ((filtersData['Filters'] as Map).values.elementAt(index) - as List) - .remove( - e); - setState( - () {}); - }, - child: - filterWidget( - color: priorities[ - e] - [ - 'color'], - icon: - Icon( - priorities[e] - [ - 'icon'], - size: + .borderStrong01Color)), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + CustomText( + filterKeys[index], + fontWeight: + FontWeightt.Semibold, + ), + const SizedBox( + height: 10, + ), + Wrap( + children: + ((filtersData['Filters'] + as Map) + .values + .elementAt( + index) + as List) + .map((e) { + return filterKeys[ + index] == + 'Priority:' + ? GestureDetector( + onTap: () { + ((filtersData['Filters'] + as Map) + .values + .elementAt( + index) as List) + .remove(e); + setState(() {}); + }, + child: + filterWidget( + color: + priorities[ + e][ + 'color'], + icon: Icon( + priorities[e] + ['icon'], + size: 15, + color: priorities[ + e] + ['color'], + ), + text: + priorities[ + e][ + 'text'], + ), + ) + : filterKeys[index] == + 'State:' + ? GestureDetector( + onTap: () { + ((filtersData['Filters'] + as Map) + .values + .elementAt(index) as List) + .remove(e); + setState( + () {}); + }, + child: + filterWidget( + color: statesProvider + .projectStates[ + e]! + .color + .toColor(), + icon: SizedBox( + height: 15, - color: priorities[e] - [ - 'color'], - ), - text: priorities[ - e] - [ - 'text'], - ), - ) - : filterKeys[index] == 'State:' - ? GestureDetector( - onTap: - () { - ((filtersData['Filters'] as Map).values.elementAt(index) as List) - .remove(e); - setState( - () {}); - }, - child: - filterWidget( - color: issuesProvider - .states[e]['color'] - .toString() - .toColor(), - icon: SizedBox( - height: 15, - width: 15, - child: issuesProvider.stateIcons[e]), - text: issuesProvider.states[e] - [ - 'name'], - ), - ) - : filterKeys[index] == 'Assignees:' || filterKeys[index] == 'Created By:' - ? GestureDetector( - onTap: - () { - ((filtersData['Filters'] as Map).values.elementAt(index) as List).remove(e); - setState(() {}); - }, - child: - filterWidget( - fill: false, - color: Colors.black, - icon: projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['avatar'] != '' - ? CircleAvatar( - radius: 10, - backgroundImage: NetworkImage(projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['avatar']), - ) - : CircleAvatar( - radius: 10, - backgroundColor: const Color.fromRGBO(55, 65, 81, 1), - child: Center( - child: CustomText( - projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['first_name'][0].toString().toUpperCase(), - color: Colors.white, - fontSize: 12, - )), - ), - text: projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['display_name'] ?? '', - ), - ) - : filterKeys[index] == 'Labels:' - ? GestureDetector( - onTap: () { - ((filtersData['Filters'] as Map).values.elementAt(index) as List).remove(e); - setState(() {}); - }, - child: filterWidget( - color: issuesProvider.labels.where((element) => element["id"] == e).first['color'].toString().toUpperCase().toColor(), - icon: Container( - height: 15, - width: 15, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: issuesProvider.labels.where((element) => element["id"] == e).first['color'].toString().toUpperCase().toColor(), - ), - ), - text: issuesProvider.labels.where((element) => element["id"] == e).first["name"]), + width: + 15, + child: issuesProvider + .stateIcons[e]), + text: statesProvider + .projectStates[ + e]! + .name, + ), + ) + : filterKeys[index] == + 'Assignees:' || + filterKeys[ + index] == + 'Created By:' + ? GestureDetector( + onTap: + () { + ((filtersData['Filters'] as Map).values.elementAt(index) + as List) + .remove(e); + setState( + () {}); + }, + child: + filterWidget( + fill: + false, + color: Colors + .black, + icon: projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['avatar'] != + '' + ? CircleAvatar( + radius: 10, + backgroundImage: NetworkImage(projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['avatar']), ) - : filterKeys[index] == 'Target Date:' - ? GestureDetector( - onTap: () { - ((filtersData['Filters'] as Map).values.elementAt(index) as List).remove(e); - setState(() {}); - }, - child: filterWidget( - color: Colors.black, - icon: Icon( - Icons.calendar_today_outlined, - size: 15, - color: themeProvider.themeManager.placeholderTextColor, - ), - text: e, + : CircleAvatar( + radius: 10, + backgroundColor: const Color.fromRGBO(55, 65, 81, 1), + child: Center( + child: CustomText( + projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['first_name'][0].toString().toUpperCase(), + color: Colors.white, + fontSize: 12, + )), + ), + text: projectProvider.projectMembers.where((element) => element['member']["id"] == e).first['member']['display_name'] ?? + '', + ), + ) + : filterKeys[ + index] == + 'Labels:' + ? GestureDetector( + onTap: + () { + ((filtersData['Filters'] as Map).values.elementAt(index) as List).remove(e); + setState(() {}); + }, + child: filterWidget( + color: labelProvider.projectLabels[e]!.color.toUpperCase().toColor(), + icon: Container( + height: 15, + width: 15, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: labelProvider.projectLabels[e]!.color.toUpperCase().toColor(), + ), + ), + text: labelProvider.projectLabels[e]!.name), + ) + : filterKeys[index] == + 'Target Date:' + ? GestureDetector( + onTap: () { + ((filtersData['Filters'] as Map).values.elementAt(index) as List).remove(e); + setState(() {}); + }, + child: filterWidget( + color: Colors.black, + icon: Icon( + Icons.calendar_today_outlined, + size: 15, + color: themeProvider.themeManager.placeholderTextColor, + ), + text: e, + ), + ) + : filterKeys[index] == 'Start Date:' + ? GestureDetector( + onTap: () { + ((filtersData['Filters'] as Map).values.elementAt(index) as List).remove(e); + setState(() {}); + }, + child: filterWidget( + color: Colors.black, + icon: Icon( + Icons.calendar_today_outlined, + size: 15, + color: themeProvider.themeManager.placeholderTextColor, ), - ) - : filterKeys[index] == 'Start Date:' - ? GestureDetector( - onTap: () { - ((filtersData['Filters'] as Map).values.elementAt(index) as List).remove(e); - setState(() {}); - }, - child: filterWidget( - color: Colors.black, - icon: Icon( - Icons.calendar_today_outlined, - size: 15, - color: themeProvider.themeManager.placeholderTextColor, - ), - text: e, - ), - ) - : Container()) - .toList(), - ), - ], - )) - : const SizedBox())) + text: e, + ), + ) + : Container(); + }).toList(), + ), + ], + )) + : const SizedBox())) ], ), ), diff --git a/lib/screens/MainScreens/Home/Dashboard/activity_graph_wdiget.dart b/lib/screens/dashboard/activity_graph_wdiget.dart similarity index 99% rename from lib/screens/MainScreens/Home/Dashboard/activity_graph_wdiget.dart rename to lib/screens/dashboard/activity_graph_wdiget.dart index 7d545add..b7b0a9fa 100644 --- a/lib/screens/MainScreens/Home/Dashboard/activity_graph_wdiget.dart +++ b/lib/screens/dashboard/activity_graph_wdiget.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Home/Dashboard/dates_model.dart'; +import 'package:plane/screens/dashboard/dates_model.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; diff --git a/lib/screens/MainScreens/Home/Dashboard/dash_board_screen.dart b/lib/screens/dashboard/dash_board_screen.dart similarity index 98% rename from lib/screens/MainScreens/Home/Dashboard/dash_board_screen.dart rename to lib/screens/dashboard/dash_board_screen.dart index fd521dad..5e68ff32 100644 --- a/lib/screens/MainScreens/Home/Dashboard/dash_board_screen.dart +++ b/lib/screens/dashboard/dash_board_screen.dart @@ -4,29 +4,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:plane/bottom_sheets/select_month_sheet.dart'; -import 'package:plane/screens/MainScreens/Home/Dashboard/activity_graph_wdiget.dart'; +import 'package:plane/bottom-sheets/select_month_sheet.dart'; +import 'package:plane/screens/dashboard/activity_graph_wdiget.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; +import 'package:plane/screens/project/create_project_screen.dart'; import 'package:plane/utils/string_manager.dart'; import 'package:plane/widgets/padding_widget.dart'; import 'package:plane/widgets/workspace_logo_for_diffrent_extensions.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:plane/bottom_sheets/global_search_sheet.dart'; -import 'package:plane/bottom_sheets/select_workspace.dart'; +import 'package:plane/bottom-sheets/global_search_sheet.dart'; +import 'package:plane/bottom-sheets/select_workspace.dart'; import 'package:plane/provider/dashboard_provider.dart'; import 'package:plane/provider/profile_provider.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/theme_provider.dart'; -import 'package:plane/screens/MainScreens/Projects/create_project_screen.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/global_functions.dart'; import 'package:plane/utils/time_manager.dart'; import 'package:plane/widgets/custom_text.dart'; -import '../../../on_boarding/auth/setup_workspace.dart'; - class DashBoardScreen extends ConsumerStatefulWidget { final bool fromSignUp; const DashBoardScreen({required this.fromSignUp, super.key}); @@ -109,8 +108,8 @@ class _DashBoardScreenState extends ConsumerState { context, profileProvider, themeProvider), const SizedBox(height: 20), dashboardProvider.hideGithubBlock == false - ? starUsOnGitHubWidget( - themeProvider, context, dashboardProvider) + ? starUsOnGitHubWidget(themeProvider, context, + dashboardProvider, profileProvider) : Container(), const SizedBox(height: 20), projectProvider.projects.isEmpty && @@ -242,7 +241,7 @@ class _DashBoardScreenState extends ConsumerState { } Stack starUsOnGitHubWidget(ThemeProvider themeProvider, BuildContext context, - DashBoardProvider dashboardProvider) { + DashBoardProvider dashboardProvider, ProfileProvider profileProvider) { return Stack( children: [ Container( @@ -304,7 +303,8 @@ class _DashBoardScreenState extends ConsumerState { postHogService( eventName: 'STAR_US_ON_GITHIB', properties: {}, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( diff --git a/lib/screens/MainScreens/Home/Dashboard/dates_model.dart b/lib/screens/dashboard/dates_model.dart similarity index 100% rename from lib/screens/MainScreens/Home/Dashboard/dates_model.dart rename to lib/screens/dashboard/dates_model.dart diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 9847fcf4..0d05025a 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,18 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:plane/screens/MainScreens/My_issues/my_issues_screen.dart'; -import 'package:plane/screens/MainScreens/Notification/notification.dart'; -import 'package:plane/screens/MainScreens/Profile/ProfileSettings/profile_screen.dart'; +import 'package:plane/screens/dashboard/dash_board_screen.dart'; +import 'package:plane/screens/my-issues/my_issues_screen.dart'; +import 'package:plane/screens/notifications/notification.dart'; import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/profile/profile-settings/profile_screen.dart'; +import 'package:plane/screens/project/project_screen.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/error_state.dart'; -import 'MainScreens/Home/Dashboard/dash_board_screen.dart'; -import 'MainScreens/Projects/project_screen.dart'; - class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({required this.fromSignUp, super.key}); final bool fromSignUp; diff --git a/lib/screens/Import & Export/cancel_goback.dart b/lib/screens/import-export/cancel_goback.dart similarity index 100% rename from lib/screens/Import & Export/cancel_goback.dart rename to lib/screens/import-export/cancel_goback.dart diff --git a/lib/screens/Import & Export/import_export.dart b/lib/screens/import-export/import_export.dart similarity index 98% rename from lib/screens/Import & Export/import_export.dart rename to lib/screens/import-export/import_export.dart index 6fde9e66..6d315ef6 100644 --- a/lib/screens/Import & Export/import_export.dart +++ b/lib/screens/import-export/import_export.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/goto_plane_web_notifier_sheet.dart'; +import 'package:plane/bottom-sheets/goto_plane_web_notifier_sheet.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/loading_widget.dart'; @@ -8,14 +8,14 @@ import 'package:plane/provider/provider_list.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:url_launcher/url_launcher.dart'; -class ImportEport extends ConsumerStatefulWidget { - const ImportEport({super.key}); +class ImportExport extends ConsumerStatefulWidget { + const ImportExport({super.key}); @override - ConsumerState createState() => _ImportEportState(); + ConsumerState createState() => _ImportEportState(); } -class _ImportEportState extends ConsumerState { +class _ImportEportState extends ConsumerState { @override void initState() { final prov = ref.read(ProviderList.integrationProvider); diff --git a/lib/screens/Import & Export/jira_import.dart b/lib/screens/import-export/jira_import.dart similarity index 100% rename from lib/screens/Import & Export/jira_import.dart rename to lib/screens/import-export/jira_import.dart diff --git a/lib/screens/MainScreens/My_issues/my_issues_screen.dart b/lib/screens/my-issues/my_issues_screen.dart similarity index 97% rename from lib/screens/MainScreens/My_issues/my_issues_screen.dart rename to lib/screens/my-issues/my_issues_screen.dart index 7c9a237a..339f4e2c 100644 --- a/lib/screens/MainScreens/My_issues/my_issues_screen.dart +++ b/lib/screens/my-issues/my_issues_screen.dart @@ -1,22 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/filters/filter_sheet.dart'; -import 'package:plane/bottom_sheets/global_search_sheet.dart'; -import 'package:plane/bottom_sheets/views_and_layout_sheet.dart'; +import 'package:plane/bottom-sheets/filters/filter_sheet.dart'; +import 'package:plane/bottom-sheets/global_search_sheet.dart'; +import 'package:plane/bottom-sheets/views_and_layout_sheet.dart'; import 'package:plane/kanban/custom/board.dart'; import 'package:plane/kanban/models/inputs.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; - +import 'package:plane/screens/project/issues/create_issue.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/empty.dart'; import 'package:plane/widgets/loading_widget.dart'; - -import '../../../provider/profile_provider.dart'; +import '../../provider/profile_provider.dart'; class MyIssuesScreen extends ConsumerStatefulWidget { const MyIssuesScreen({super.key}); @@ -446,7 +444,7 @@ class _MyIssuesScreenState extends ConsumerState { final projectProvider = ref.watch(ProviderList.projectProvider); final profileProvider = ref.watch(ProviderList.profileProvider); // log(issueProvider.issueState.name); - if (issueProvider.issues.projectView == ProjectView.list) { + if (issueProvider.issues.projectView == IssueLayout.list) { issueProvider.initializeBoard(); } @@ -455,7 +453,7 @@ class _MyIssuesScreenState extends ConsumerState { issueProvider.myIssuesFilterState == StateEnum.loading, widgetClass: Container( color: themeProvider.themeManager.secondaryBackgroundDefaultColor, - padding: issueProvider.issues.projectView == ProjectView.kanban + padding: issueProvider.issues.projectView == IssueLayout.kanban ? const EdgeInsets.only(top: 15, left: 0) : null, child: issueProvider.myIssuesViewState == StateEnum.loading @@ -484,7 +482,7 @@ class _MyIssuesScreenState extends ConsumerState { ), ], ) - : issueProvider.issues.projectView == ProjectView.list + : issueProvider.issues.projectView == IssueLayout.list ? Container( color: themeProvider .themeManager.secondaryBackgroundDefaultColor, @@ -511,6 +509,14 @@ class _MyIssuesScreenState extends ConsumerState { state.leading ?? const SizedBox.shrink(), Container( + constraints: + BoxConstraints( + maxWidth: MediaQuery.of( + context) + .size + .width * + 0.65, + ), padding: EdgeInsets.only( left: issueProvider .issues @@ -519,11 +525,6 @@ class _MyIssuesScreenState extends ConsumerState { ? 0 : 10, ), - width: - MediaQuery.of(context) - .size - .width * - 0.6, child: CustomText( state.title!, overflow: TextOverflow diff --git a/lib/screens/MainScreens/Notification/extra_notification.dart b/lib/screens/notifications/extra_notification.dart similarity index 95% rename from lib/screens/MainScreens/Notification/extra_notification.dart rename to lib/screens/notifications/extra_notification.dart index 768724f7..5d386a44 100644 --- a/lib/screens/MainScreens/Notification/extra_notification.dart +++ b/lib/screens/notifications/extra_notification.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Notification/notifications_list.dart'; +import 'package:plane/screens/notifications/notifications_list.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/loading_widget.dart'; diff --git a/lib/screens/MainScreens/Notification/notification.dart b/lib/screens/notifications/notification.dart similarity index 96% rename from lib/screens/MainScreens/Notification/notification.dart rename to lib/screens/notifications/notification.dart index c7441f22..cbaa54da 100644 --- a/lib/screens/MainScreens/Notification/notification.dart +++ b/lib/screens/notifications/notification.dart @@ -3,10 +3,10 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:plane/bottom_sheets/notification_filter_sheet.dart'; -import 'package:plane/bottom_sheets/notification_more_options_sheet.dart'; +import 'package:plane/bottom-sheets/notification_filter_sheet.dart'; +import 'package:plane/bottom-sheets/notification_more_options_sheet.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Notification/notifications_list.dart'; +import 'package:plane/screens/notifications/notifications_list.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/custom_divider.dart'; import 'package:plane/widgets/custom_text.dart'; @@ -153,6 +153,7 @@ class _NotifiactionScreenState extends ConsumerState unselectedLabelColor: themeProvider.themeManager.placeholderTextColor, labelColor: themeProvider.themeManager.primaryColour, + dividerColor: themeProvider.themeManager.borderSubtle01Color, tabs: const [ Tab( text: 'My Issues', diff --git a/lib/screens/MainScreens/Notification/notifications_list.dart b/lib/screens/notifications/notifications_list.dart similarity index 99% rename from lib/screens/MainScreens/Notification/notifications_list.dart rename to lib/screens/notifications/notifications_list.dart index d493fd3f..6cdbd6b6 100644 --- a/lib/screens/MainScreens/Notification/notifications_list.dart +++ b/lib/screens/notifications/notifications_list.dart @@ -2,8 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:intl/intl.dart'; -import 'package:plane/bottom_sheets/snooze_time_sheet.dart'; +import 'package:plane/bottom-sheets/snooze_time_sheet.dart'; import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_rich_text.dart'; @@ -12,8 +13,6 @@ import 'package:plane/widgets/empty.dart'; import 'package:plane/widgets/error_state.dart'; import 'package:plane/widgets/loading_widget.dart'; -import '../Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class NotificationsList extends ConsumerStatefulWidget { const NotificationsList({super.key, required this.data, required this.type}); final List data; diff --git a/lib/screens/on_boarding/auth/forgot_password.dart b/lib/screens/onboarding/auth/forgot_password.dart similarity index 100% rename from lib/screens/on_boarding/auth/forgot_password.dart rename to lib/screens/onboarding/auth/forgot_password.dart diff --git a/lib/screens/on_boarding/auth/invite_co_workers.dart b/lib/screens/onboarding/auth/invite_co_workers.dart similarity index 99% rename from lib/screens/on_boarding/auth/invite_co_workers.dart rename to lib/screens/onboarding/auth/invite_co_workers.dart index 8478305f..36336d15 100644 --- a/lib/screens/on_boarding/auth/invite_co_workers.dart +++ b/lib/screens/onboarding/auth/invite_co_workers.dart @@ -2,7 +2,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:plane/bottom_sheets/permission_role_sheet.dart'; +import 'package:plane/bottom-sheets/permission_role_sheet.dart'; import 'package:plane/screens/home_screen.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/loading_widget.dart'; diff --git a/lib/screens/on_boarding/auth/join_workspaces.dart b/lib/screens/onboarding/auth/join_workspaces.dart similarity index 99% rename from lib/screens/on_boarding/auth/join_workspaces.dart rename to lib/screens/onboarding/auth/join_workspaces.dart index 5aafa177..82a3d8bc 100644 --- a/lib/screens/on_boarding/auth/join_workspaces.dart +++ b/lib/screens/onboarding/auth/join_workspaces.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/on_boarding/auth/setup_workspace.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; import 'package:plane/utils/color_manager.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; diff --git a/lib/screens/on_boarding/auth/reset_password.dart b/lib/screens/onboarding/auth/reset_password.dart similarity index 100% rename from lib/screens/on_boarding/auth/reset_password.dart rename to lib/screens/onboarding/auth/reset_password.dart diff --git a/lib/screens/on_boarding/auth/setup_profile_screen.dart b/lib/screens/onboarding/auth/setup_profile_screen.dart similarity index 98% rename from lib/screens/on_boarding/auth/setup_profile_screen.dart rename to lib/screens/onboarding/auth/setup_profile_screen.dart index 2fe2b7d0..a05d3daa 100644 --- a/lib/screens/on_boarding/auth/setup_profile_screen.dart +++ b/lib/screens/onboarding/auth/setup_profile_screen.dart @@ -3,18 +3,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:plane/bottom_sheets/time_zone_selector_sheet.dart'; -import 'package:plane/screens/on_boarding/auth/join_workspaces.dart'; +import 'package:plane/bottom-sheets/time_zone_selector_sheet.dart'; +import 'package:plane/screens/onboarding/auth/join_workspaces.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/on_boarding/auth/setup_workspace.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; import 'package:plane/utils/timezone_manager.dart'; import 'package:plane/widgets/loading_widget.dart'; // import '../Provider/provider_list.dart'; import '../../../widgets/custom_button.dart'; import '../../../widgets/custom_rich_text.dart'; -import '../../../bottom_sheets/role_sheet.dart'; +import '../../../bottom-sheets/role_sheet.dart'; import '../../../widgets/custom_text.dart'; class SetupProfileScreen extends ConsumerStatefulWidget { diff --git a/lib/screens/on_boarding/auth/setup_workspace.dart b/lib/screens/onboarding/auth/setup_workspace.dart similarity index 94% rename from lib/screens/on_boarding/auth/setup_workspace.dart rename to lib/screens/onboarding/auth/setup_workspace.dart index a83aae68..e7463450 100644 --- a/lib/screens/on_boarding/auth/setup_workspace.dart +++ b/lib/screens/onboarding/auth/setup_workspace.dart @@ -2,17 +2,18 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:plane/config/config_variables.dart'; -import 'package:plane/screens/on_boarding/auth/invite_co_workers.dart'; +import 'package:plane/screens/onboarding/auth/invite_co_workers.dart'; +import 'package:plane/utils/bottom_sheet.helper.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_rich_text.dart'; import 'package:plane/widgets/loading_widget.dart'; import '../../../provider/provider_list.dart'; import '../../../widgets/custom_button.dart'; -import '../../../bottom_sheets/company_size_sheet.dart'; +import '../../../bottom-sheets/company_size_sheet.dart'; import '../../../widgets/custom_text.dart'; class SetupWorkspace extends ConsumerStatefulWidget { @@ -193,7 +194,8 @@ class _SetupWorkspaceState extends ConsumerState { left: 15, ), child: CustomText( - Config.webUrl!, + // Config.webUrl!, + dotenv.env['WEB_URL'].toString(), type: FontStyle.Small, color: themeProvider.themeManager .placeholderTextColor, @@ -231,23 +233,11 @@ class _SetupWorkspaceState extends ConsumerState { key: const Key('companySize'), onTap: () { FocusScope.of(context).unfocus(); - showModalBottomSheet( - context: context, - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context) - .size - .height * - 0.5, - ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - builder: (context) { - return const CompanySize(); - }); + BottomSheetHelper.showBottomSheet( + context, const CompanySize(), + constraints: const BoxConstraints( + maxHeight: 400, + )); }, child: Container( height: 50, diff --git a/lib/screens/on_boarding/auth/signUp.dart b/lib/screens/onboarding/auth/signUp.dart similarity index 99% rename from lib/screens/on_boarding/auth/signUp.dart rename to lib/screens/onboarding/auth/signUp.dart index 224835dd..b4344c86 100644 --- a/lib/screens/on_boarding/auth/signUp.dart +++ b/lib/screens/onboarding/auth/signUp.dart @@ -5,7 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/screens/home_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_profile_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_profile_screen.dart'; import 'package:plane/widgets/custom_button.dart'; import 'package:plane/widgets/custom_rich_text.dart'; import 'package:plane/widgets/loading_widget.dart'; diff --git a/lib/screens/on_boarding/auth/sign_in.dart b/lib/screens/onboarding/auth/sign_in.dart similarity index 98% rename from lib/screens/on_boarding/auth/sign_in.dart rename to lib/screens/onboarding/auth/sign_in.dart index 43bcab89..d6d4103f 100644 --- a/lib/screens/on_boarding/auth/sign_in.dart +++ b/lib/screens/onboarding/auth/sign_in.dart @@ -8,13 +8,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/Authentication/google_sign_in.dart'; +import 'package:plane/authentication/google_sign_in.dart'; import 'package:plane/config/config_variables.dart'; import 'package:plane/mixins/widget_state_mixin.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/screens/home_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_profile_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_profile_screen.dart'; import 'package:plane/utils/global_functions.dart'; import 'package:plane/widgets/custom_button.dart'; import 'package:plane/utils/constants.dart'; @@ -288,7 +288,10 @@ class _SignInScreenState extends ConsumerState 'Source': 'Magic Code', 'Email': email.text, }, - ref: ref); + userEmail: profileProvider + .userProfile.email!, + userID: profileProvider + .userProfile.id!); Navigator.pushAndRemoveUntil( context, MaterialPageRoute( diff --git a/lib/screens/on_boarding/auth/sign_in_selfhosted.dart b/lib/screens/onboarding/auth/sign_in_selfhosted.dart similarity index 99% rename from lib/screens/on_boarding/auth/sign_in_selfhosted.dart rename to lib/screens/onboarding/auth/sign_in_selfhosted.dart index 3d40c2f1..d71c5faa 100644 --- a/lib/screens/on_boarding/auth/sign_in_selfhosted.dart +++ b/lib/screens/onboarding/auth/sign_in_selfhosted.dart @@ -5,7 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/screens/home_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_profile_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_profile_screen.dart'; import 'package:plane/widgets/custom_button.dart'; import 'package:plane/widgets/custom_rich_text.dart'; import 'package:plane/widgets/loading_widget.dart'; diff --git a/lib/screens/on_boarding/on_boarding_screen.dart b/lib/screens/onboarding/on_boarding_screen.dart similarity index 99% rename from lib/screens/on_boarding/on_boarding_screen.dart rename to lib/screens/onboarding/on_boarding_screen.dart index db865dbd..4ed025b6 100644 --- a/lib/screens/on_boarding/on_boarding_screen.dart +++ b/lib/screens/onboarding/on_boarding_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:plane/config/const.dart'; -import 'package:plane/screens/on_boarding/auth/signUp.dart'; +import 'package:plane/screens/onboarding/auth/signUp.dart'; import 'package:plane/utils/constants.dart'; import '../../provider/provider_list.dart'; import '../../widgets/custom_button.dart'; diff --git a/lib/screens/MainScreens/Profile/ProfileSettings/WorkSpaceInvites/workspace_invite_screen.dart b/lib/screens/profile/profile-settings/WorkSpaceInvites/workspace_invite_screen.dart similarity index 100% rename from lib/screens/MainScreens/Profile/ProfileSettings/WorkSpaceInvites/workspace_invite_screen.dart rename to lib/screens/profile/profile-settings/WorkSpaceInvites/workspace_invite_screen.dart diff --git a/lib/screens/MainScreens/Profile/ProfileSettings/profile_detail_screen.dart b/lib/screens/profile/profile-settings/profile_detail_screen.dart similarity index 99% rename from lib/screens/MainScreens/Profile/ProfileSettings/profile_detail_screen.dart rename to lib/screens/profile/profile-settings/profile_detail_screen.dart index 88250e71..df6f98b3 100644 --- a/lib/screens/MainScreens/Profile/ProfileSettings/profile_detail_screen.dart +++ b/lib/screens/profile/profile-settings/profile_detail_screen.dart @@ -8,22 +8,22 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/time_zone_selector_sheet.dart'; +import 'package:plane/bottom-sheets/time_zone_selector_sheet.dart'; import 'package:plane/config/const.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/Theming/prefrences.dart'; +import 'package:plane/screens/themes/prefrences.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/timezone_manager.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/custom_button.dart'; -import 'package:plane/bottom_sheets/role_sheet.dart'; +import 'package:plane/bottom-sheets/role_sheet.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/loading_widget.dart'; import 'package:plane/widgets/shimmer_effect_widget.dart'; -import '../../../../widgets/custom_rich_text.dart'; +import '../../../widgets/custom_rich_text.dart'; class ProfileDetailScreen extends ConsumerStatefulWidget { const ProfileDetailScreen({super.key}); @@ -72,8 +72,6 @@ class _ProfileDetailScreenState extends ConsumerState { final themeProvider = ref.watch(ProviderList.themeProvider); final profileProvider = ref.watch(ProviderList.profileProvider); final fileUploadProvider = ref.watch(ProviderList.fileUploadProvider); - log('${profileProvider.userProfile.avatar} photo'); - log('profile : ${profileProvider.userProfile} '); return GestureDetector( onTap: () { FocusScope.of(context).unfocus(); diff --git a/lib/screens/MainScreens/Profile/ProfileSettings/profile_screen.dart b/lib/screens/profile/profile-settings/profile_screen.dart similarity index 96% rename from lib/screens/MainScreens/Profile/ProfileSettings/profile_screen.dart rename to lib/screens/profile/profile-settings/profile_screen.dart index 39d74bb0..6e15e478 100644 --- a/lib/screens/MainScreens/Profile/ProfileSettings/profile_screen.dart +++ b/lib/screens/profile/profile-settings/profile_screen.dart @@ -2,25 +2,25 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/select_workspace.dart'; +import 'package:plane/bottom-sheets/select_workspace.dart'; import 'package:plane/provider/profile_provider.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/theme_provider.dart'; import 'package:plane/provider/workspace_provider.dart'; -import 'package:plane/screens/Import%20&%20Export/import_export.dart'; -import 'package:plane/screens/MainScreens/Activity/activity.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/user_profile.dart'; -import 'package:plane/screens/Theming/prefrences.dart'; +import 'package:plane/screens/Activity/activity.dart'; +import 'package:plane/screens/import-export/import_export.dart'; +import 'package:plane/screens/profile/profile-settings/profile_detail_screen.dart'; +import 'package:plane/screens/profile/user-profile/user_profile.dart'; +import 'package:plane/screens/profile/workpsace-settings/members.dart'; +import 'package:plane/screens/profile/workpsace-settings/workspace_general.dart'; +import 'package:plane/screens/themes/prefrences.dart'; import 'package:plane/screens/integrations.dart'; -import 'package:plane/screens/MainScreens/Profile/WorkpsaceSettings/members.dart'; -import 'package:plane/screens/MainScreens/Profile/ProfileSettings/profile_detail_screen.dart'; -import 'package:plane/screens/MainScreens/Profile/WorkpsaceSettings/workspace_general.dart'; -import 'package:plane/screens/on_boarding/auth/join_workspaces.dart'; +import 'package:plane/screens/onboarding/auth/join_workspaces.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_button.dart'; -import 'package:plane/screens/on_boarding/on_boarding_screen.dart'; +import 'package:plane/screens/onboarding/on_boarding_screen.dart'; import 'package:plane/widgets/custom_divider.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/padding_widget.dart'; @@ -135,7 +135,7 @@ class _ProfileScreenState extends ConsumerState { Navigator.push( context, MaterialPageRoute( - builder: (context) => const ImportEport(), + builder: (context) => const ImportExport(), ), ); } diff --git a/lib/screens/MainScreens/Profile/User_profile/assigned_issues.dart b/lib/screens/profile/user-profile/assigned_issues.dart similarity index 99% rename from lib/screens/MainScreens/Profile/User_profile/assigned_issues.dart rename to lib/screens/profile/user-profile/assigned_issues.dart index e46e640b..b2d2a156 100644 --- a/lib/screens/MainScreens/Profile/User_profile/assigned_issues.dart +++ b/lib/screens/profile/user-profile/assigned_issues.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:loading_indicator/loading_indicator.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_rich_text.dart'; diff --git a/lib/screens/MainScreens/Profile/User_profile/created_issue_page.dart.dart b/lib/screens/profile/user-profile/created_issue_page.dart.dart similarity index 99% rename from lib/screens/MainScreens/Profile/User_profile/created_issue_page.dart.dart rename to lib/screens/profile/user-profile/created_issue_page.dart.dart index b7739283..8fc590c5 100644 --- a/lib/screens/MainScreens/Profile/User_profile/created_issue_page.dart.dart +++ b/lib/screens/profile/user-profile/created_issue_page.dart.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_rich_text.dart'; diff --git a/lib/screens/MainScreens/Profile/User_profile/member_profile.dart b/lib/screens/profile/user-profile/member_profile.dart similarity index 77% rename from lib/screens/MainScreens/Profile/User_profile/member_profile.dart rename to lib/screens/profile/user-profile/member_profile.dart index ab086d25..f4fe8ba7 100644 --- a/lib/screens/MainScreens/Profile/User_profile/member_profile.dart +++ b/lib/screens/profile/user-profile/member_profile.dart @@ -13,10 +13,10 @@ import 'package:plane/widgets/loading_widget.dart'; import 'package:plane/widgets/member_logo_widget.dart'; import 'package:plane/widgets/shimmer_effect_widget.dart'; -import '../../../../bottom_sheets/project_select_cover_image.dart'; -import '../../../../provider/profile_provider.dart'; -import '../../../../utils/constants.dart'; -import '../../../../utils/app_theme.dart'; +import '../../../bottom-sheets/project_select_cover_image.dart'; +import '../../../provider/profile_provider.dart'; +import '../../../utils/constants.dart'; +import '../../../utils/app_theme.dart'; class MemberProfile extends ConsumerStatefulWidget { const MemberProfile({required this.userID, super.key}); @@ -191,104 +191,107 @@ class _MemberProfileState extends ConsumerState { ), memberprofileProvider.expanded[index] ? Column( - children: [ - memberprofileProvider.memberProfile['project_data'] - [index][ - memberprofileProvider.projectData[0] - ['key']] == - 0 && - memberprofileProvider - .memberProfile['project_data'] - [index][ - memberprofileProvider.projectData[1] - ['key']] == - 0 - ? Container() - : Padding( - padding: const EdgeInsets.only(bottom: 10), - child: CustomProgressBar.withColorOverride( - width: width, - itemValue: [ - memberprofileProvider.memberProfile[ - 'project_data'][index][ + children: [ + memberprofileProvider.memberProfile['project_data'] + [index][ memberprofileProvider.projectData[0] - ['key']], - memberprofileProvider.memberProfile[ - 'project_data'][index][ + ['key']] == + 0 && + memberprofileProvider + .memberProfile['project_data'] + [index][ memberprofileProvider.projectData[1] - ['key']], - memberprofileProvider.memberProfile[ - 'project_data'][index][ - memberprofileProvider.projectData[2] - ['key']], - memberprofileProvider.memberProfile[ - 'project_data'][index][ - memberprofileProvider.projectData[3] - ['key']], - ], - itemColors: [ - memberprofileProvider.projectData[0] - ['color'], - memberprofileProvider.projectData[1] - ['color'], - memberprofileProvider.projectData[2] - ['color'], - memberprofileProvider.projectData[3] - ['color'], - ]), - ), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: - memberprofileProvider.projectData.length, - itemBuilder: (ctx, i) { - return Container( - margin: const EdgeInsets.only(top: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - margin: const EdgeInsets.only( - left: 30, right: 10), - height: 12, - width: 12, - decoration: BoxDecoration( - color: memberprofileProvider - .projectData[i]['color'], - borderRadius: - BorderRadius.circular(4)), - ), - Container( - margin: const EdgeInsets.only(), - child: CustomText( - memberprofileProvider.projectData[i] - ['name'], - color: themeProvider.themeManager - .placeholderTextColor, - type: FontStyle.Small, - fontWeight: FontWeightt.Regular, + ['key']] == + 0 + ? Container() + : Padding( + padding: const EdgeInsets.only(bottom: 10), + child: CustomProgressBar.withColorOverride( + width: width, + itemValue: [ + memberprofileProvider + .memberProfile['project_data'] + [index][ + memberprofileProvider.projectData[0] + ['key']], + memberprofileProvider + .memberProfile['project_data'] + [index][ + memberprofileProvider.projectData[1] + ['key']], + memberprofileProvider + .memberProfile['project_data'] + [index][ + memberprofileProvider.projectData[2] + ['key']], + memberprofileProvider + .memberProfile['project_data'] + [index][ + memberprofileProvider.projectData[3] + ['key']], + ], + itemColors: [ + memberprofileProvider.projectData[0] + ['color'], + memberprofileProvider.projectData[1] + ['color'], + memberprofileProvider.projectData[2] + ['color'], + memberprofileProvider.projectData[3] + ['color'], + ]), + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: memberprofileProvider.projectData.length, + itemBuilder: (ctx, i) { + return Container( + margin: const EdgeInsets.only(top: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.only( + left: 30, right: 10), + height: 12, + width: 12, + decoration: BoxDecoration( + color: memberprofileProvider + .projectData[i]['color'], + borderRadius: + BorderRadius.circular(4)), ), - ), - const Spacer(), - Container( - margin: const EdgeInsets.only( - right: 30, + Container( + margin: const EdgeInsets.only(), + child: CustomText( + memberprofileProvider.projectData[i] + ['name'], + color: themeProvider + .themeManager.placeholderTextColor, + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + ), ), - child: CustomText( - "${memberprofileProvider.memberProfile['project_data'][index][memberprofileProvider.projectData[i]['key']]} Issues", - color: themeProvider - .themeManager.tertiaryTextColor, - type: FontStyle.Small, - fontWeight: FontWeightt.Medium, + const Spacer(), + Container( + margin: const EdgeInsets.only( + right: 30, + ), + child: CustomText( + "${memberprofileProvider.memberProfile['project_data'][index][memberprofileProvider.projectData[i]['key']]} Issues", + color: themeProvider + .themeManager.tertiaryTextColor, + type: FontStyle.Small, + fontWeight: FontWeightt.Medium, + ), ), - ), - ], - ), - ); - }), - ], - ) + ], + ), + ); + }), + ], + ) : Container(), index == memberprofileProvider diff --git a/lib/screens/MainScreens/Profile/User_profile/over_view.dart b/lib/screens/profile/user-profile/over_view.dart similarity index 98% rename from lib/screens/MainScreens/Profile/User_profile/over_view.dart rename to lib/screens/profile/user-profile/over_view.dart index a5526873..7948763a 100644 --- a/lib/screens/MainScreens/Profile/User_profile/over_view.dart +++ b/lib/screens/profile/user-profile/over_view.dart @@ -6,8 +6,9 @@ import 'package:loading_indicator/loading_indicator.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:plane/config/const.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; +import 'package:plane/screens/project/cycles/cycle-detail/cycle_issues_page.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; +import 'package:plane/screens/project/modules/module-detail/module_issues_page.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; @@ -470,8 +471,7 @@ class _OverViewScreenState extends ConsumerState { Navigator.push( Const.globalKey.currentContext!, MaterialPageRoute( - builder: (context) => CycleDetail( - fromModule: true, + builder: (context) => ModuleDetail( projId: userProfileProvider.userActivity .results![index].projectDetail!.id, moduleId: userProfileProvider.userActivity @@ -511,7 +511,6 @@ class _OverViewScreenState extends ConsumerState { Const.globalKey.currentContext!, MaterialPageRoute( builder: (context) => CycleDetail( - fromModule: false, projId: userProfileProvider.userActivity .results![index].projectDetail!.id, cycleId: userProfileProvider.userActivity diff --git a/lib/screens/MainScreens/Profile/User_profile/subscribed_issues.dart b/lib/screens/profile/user-profile/subscribed_issues.dart similarity index 99% rename from lib/screens/MainScreens/Profile/User_profile/subscribed_issues.dart rename to lib/screens/profile/user-profile/subscribed_issues.dart index da3d7fcf..5ee3e0f5 100644 --- a/lib/screens/MainScreens/Profile/User_profile/subscribed_issues.dart +++ b/lib/screens/profile/user-profile/subscribed_issues.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:loading_indicator/loading_indicator.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_rich_text.dart'; diff --git a/lib/screens/MainScreens/Profile/User_profile/user_profile.dart b/lib/screens/profile/user-profile/user_profile.dart similarity index 93% rename from lib/screens/MainScreens/Profile/User_profile/user_profile.dart rename to lib/screens/profile/user-profile/user_profile.dart index cdda695e..2ce50fe8 100644 --- a/lib/screens/MainScreens/Profile/User_profile/user_profile.dart +++ b/lib/screens/profile/user-profile/user_profile.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/assigned_issues.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/created_issue_page.dart.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/member_profile.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/over_view.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/subscribed_issues.dart'; +import 'package:plane/screens/profile/user-profile/assigned_issues.dart'; +import 'package:plane/screens/profile/user-profile/created_issue_page.dart.dart'; +import 'package:plane/screens/profile/user-profile/member_profile.dart'; +import 'package:plane/screens/profile/user-profile/over_view.dart'; +import 'package:plane/screens/profile/user-profile/subscribed_issues.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; diff --git a/lib/screens/MainScreens/Profile/WorkpsaceSettings/invite_members.dart b/lib/screens/profile/workpsace-settings/invite_members.dart similarity index 98% rename from lib/screens/MainScreens/Profile/WorkpsaceSettings/invite_members.dart rename to lib/screens/profile/workpsace-settings/invite_members.dart index a0f93e8e..86178de8 100644 --- a/lib/screens/MainScreens/Profile/WorkpsaceSettings/invite_members.dart +++ b/lib/screens/profile/workpsace-settings/invite_members.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/member_status.dart'; +import 'package:plane/bottom-sheets/member_status.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; @@ -43,6 +43,7 @@ class _InviteMembersState extends ConsumerState { final themeProvider = ref.watch(ProviderList.themeProvider); final workspaceProvider = ref.watch(ProviderList.workspaceProvider); final projectProvider = ref.watch(ProviderList.projectProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); final BuildContext mainBuildContext = context; return GestureDetector( onTap: () { @@ -459,7 +460,9 @@ class _InviteMembersState extends ConsumerState { properties: { 'INVITED_USER_EMAIL': emailController.text, }, - ref: ref); + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); + if (workspaceProvider.workspaceInvitationState == StateEnum.success) { CustomToast.showToast(mainBuildContext, diff --git a/lib/screens/MainScreens/Profile/WorkpsaceSettings/members.dart b/lib/screens/profile/workpsace-settings/members.dart similarity index 99% rename from lib/screens/MainScreens/Profile/WorkpsaceSettings/members.dart rename to lib/screens/profile/workpsace-settings/members.dart index 877083dd..1bc4d62c 100644 --- a/lib/screens/MainScreens/Profile/WorkpsaceSettings/members.dart +++ b/lib/screens/profile/workpsace-settings/members.dart @@ -2,16 +2,16 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:plane/bottom_sheets/delete_leave_project_sheet.dart'; -import 'package:plane/bottom_sheets/delete_workspace_sheet.dart'; +import 'package:plane/bottom-sheets/delete_leave_project_sheet.dart'; +import 'package:plane/bottom-sheets/delete_workspace_sheet.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Profile/User_profile/user_profile.dart'; +import 'package:plane/screens/profile/user-profile/user_profile.dart'; +import 'package:plane/screens/profile/workpsace-settings/invite_members.dart'; import 'package:plane/utils/color_manager.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; -import 'package:plane/screens/MainScreens/Profile/WorkpsaceSettings/invite_members.dart'; import 'package:plane/utils/constants.dart'; -import 'package:plane/bottom_sheets/member_status.dart'; +import 'package:plane/bottom-sheets/member_status.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/error_state.dart'; diff --git a/lib/screens/MainScreens/Profile/WorkpsaceSettings/workspace_general.dart b/lib/screens/profile/workpsace-settings/workspace_general.dart similarity index 99% rename from lib/screens/MainScreens/Profile/WorkpsaceSettings/workspace_general.dart rename to lib/screens/profile/workpsace-settings/workspace_general.dart index cbad1493..e28875ad 100644 --- a/lib/screens/MainScreens/Profile/WorkpsaceSettings/workspace_general.dart +++ b/lib/screens/profile/workpsace-settings/workspace_general.dart @@ -7,8 +7,8 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/company_size_sheet.dart'; -import 'package:plane/bottom_sheets/delete_workspace_sheet.dart'; +import 'package:plane/bottom-sheets/company_size_sheet.dart'; +import 'package:plane/bottom-sheets/delete_workspace_sheet.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/archived_issues.dart b/lib/screens/project/archived_issues.dart similarity index 97% rename from lib/screens/MainScreens/Projects/ProjectDetail/archived_issues.dart rename to lib/screens/project/archived_issues.dart index 8a275778..55399927 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/archived_issues.dart +++ b/lib/screens/project/archived_issues.dart @@ -2,10 +2,10 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/filters/filter_sheet.dart'; -import 'package:plane/bottom_sheets/views_sheet.dart'; +import 'package:plane/bottom-sheets/filters/filter_sheet.dart'; +import 'package:plane/bottom-sheets/views_sheet.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/custom_text.dart'; @@ -25,7 +25,7 @@ class _ArchivedIssuesState extends ConsumerState { super.initState(); final issueProvider = ref.read(ProviderList.issuesProvider); issueProvider.tempProjectView = issueProvider.issues.projectView; - issueProvider.issues.projectView = ProjectView.list; + issueProvider.issues.projectView = IssueLayout.list; issueProvider.setsState(); issueProvider.filterIssues( @@ -43,8 +43,8 @@ class _ArchivedIssuesState extends ConsumerState { final themeProvider = ref.watch(ProviderList.themeProvider); final issueProvider = ref.watch(ProviderList.issuesProvider); final projectProvider = ref.watch(ProviderList.projectProvider); - // log(issueProvider.issueState.name); - if (issueProvider.issues.projectView == ProjectView.list) { + final statesProvider = ref.watch(ProviderList.statesProvider); + if (issueProvider.issues.projectView == IssueLayout.list) { issueProvider.initializeBoard( isArchive: true, ); @@ -86,10 +86,10 @@ class _ArchivedIssuesState extends ConsumerState { body: LoadingWidget( loading: issueProvider.issuePropertyState == StateEnum.loading || issueProvider.issueState == StateEnum.loading || - issueProvider.statesState == StateEnum.loading || + statesProvider.statesState == StateEnum.loading || issueProvider.projectViewState == StateEnum.loading || issueProvider.orderByState == StateEnum.loading, - widgetClass: issueProvider.statesState == StateEnum.restricted + widgetClass: statesProvider.statesState == StateEnum.restricted ? EmptyPlaceholder.joinProject( context, ref, @@ -102,7 +102,7 @@ class _ArchivedIssuesState extends ConsumerState { color: themeProvider .themeManager.secondaryBackgroundDefaultColor, child: issueProvider.issueState == StateEnum.loading || - issueProvider.statesState == StateEnum.loading || + statesProvider.statesState == StateEnum.loading || issueProvider.projectViewState == StateEnum.loading || issueProvider.orderByState == StateEnum.loading ? Container() @@ -139,12 +139,12 @@ class _ArchivedIssuesState extends ConsumerState { Container( padding: const EdgeInsets - .only( + .only( left: 15), margin: const EdgeInsets - .only( + .only( bottom: 10), child: Row( @@ -249,7 +249,7 @@ class _ArchivedIssuesState extends ConsumerState { .isEmpty ? Container( margin: const EdgeInsets - .only( + .only( bottom: 10), width: MediaQuery.of( @@ -260,7 +260,7 @@ class _ArchivedIssuesState extends ConsumerState { .themeManager .primaryBackgroundDefaultColor, padding: const EdgeInsets - .only( + .only( top: 15, bottom: @@ -280,7 +280,7 @@ class _ArchivedIssuesState extends ConsumerState { ) : Container( margin: const EdgeInsets - .only( + .only( bottom: 10), ) @@ -291,7 +291,7 @@ class _ArchivedIssuesState extends ConsumerState { ), ), ), - issueProvider.statesState == + statesProvider.statesState == StateEnum.loading || issueProvider.issueState == StateEnum.loading @@ -315,7 +315,7 @@ class _ArchivedIssuesState extends ConsumerState { .borderSubtle01Color, ), issueProvider.issues.projectView == - ProjectView.calendar + IssueLayout.calendar ? Container() : Expanded( child: InkWell( @@ -350,7 +350,7 @@ class _ArchivedIssuesState extends ConsumerState { .issues, isArchived: true, projectView: - ProjectView + IssueLayout .list, ); }); diff --git a/lib/screens/MainScreens/Projects/create_project_screen.dart b/lib/screens/project/create_project_screen.dart similarity index 98% rename from lib/screens/MainScreens/Projects/create_project_screen.dart rename to lib/screens/project/create_project_screen.dart index b5b9269a..6bcb9f04 100644 --- a/lib/screens/MainScreens/Projects/create_project_screen.dart +++ b/lib/screens/project/create_project_screen.dart @@ -5,12 +5,12 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/emoji_sheet.dart'; +import 'package:plane/bottom-sheets/emoji_sheet.dart'; import 'package:plane/config/const.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/custom_button.dart'; -import 'package:plane/bottom_sheets/project_select_cover_image.dart'; +import 'package:plane/bottom-sheets/project_select_cover_image.dart'; import 'package:plane/widgets/loading_widget.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; @@ -51,6 +51,9 @@ class _CreateProjectState extends ConsumerState { @override void initState() { generateEmojis(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + ref.read(ProviderList.projectProvider).getUnsplashImages(); + }); super.initState(); } @@ -188,8 +191,7 @@ class _CreateProjectState extends ConsumerState { enableDrag: true, isScrollControlled: true, constraints: BoxConstraints( - maxHeight: height * - 0.8, + maxHeight: height * 0.8, ), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/create_cycle.dart b/lib/screens/project/cycles/create_cycle.dart similarity index 100% rename from lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/create_cycle.dart rename to lib/screens/project/cycles/create_cycle.dart diff --git a/lib/screens/project/cycles/cycle-detail/cycle_details_page.dart b/lib/screens/project/cycles/cycle-detail/cycle_details_page.dart new file mode 100644 index 00000000..1ad501c3 --- /dev/null +++ b/lib/screens/project/cycles/cycle-detail/cycle_details_page.dart @@ -0,0 +1,713 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:loading_indicator/loading_indicator.dart'; +import 'package:plane/bottom-sheets/assignee_sheet.dart'; +import 'package:plane/models/chart_model.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/widgets/assignee_widget.dart'; +import 'package:plane/screens/project/widgets/assignees_widget.dart'; +import 'package:plane/screens/project/widgets/progress_chart.dart'; +import 'package:plane/screens/project/widgets/states_widget.dart'; +import 'package:plane/utils/constants.dart'; +import 'package:plane/utils/custom_toast.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/utils/extensions/string_extensions.dart'; +import 'package:plane/widgets/completion_percentage.dart'; +import 'package:plane/widgets/custom_text.dart'; +import 'package:plane/widgets/square_avatar_widget.dart'; + +class CycleDetailsPage extends ConsumerStatefulWidget { + const CycleDetailsPage( + {super.key, required this.cycleId, required this.chartData}); + final String cycleId; + final List chartData; + + @override + ConsumerState createState() => _CycleDetailsPageState(); +} + +class _CycleDetailsPageState extends ConsumerState { + DateTime? dueDate; + DateTime? startDate; + + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final cyclesProvider = ref.watch(ProviderList.cyclesProvider); + + if (cyclesProvider.cyclesDetailState == StateEnum.loading) { + return Center( + child: SizedBox( + width: 30, + height: 30, + child: LoadingIndicator( + indicatorType: Indicator.lineSpinFadeLoader, + colors: [themeProvider.themeManager.primaryTextColor], + strokeWidth: 1.0, + backgroundColor: Colors.transparent, + ), + ), + ); + } else { + return ListView( + children: [ + const SizedBox(height: 30), + dateWidget(), + const SizedBox(height: 30), + detailsWidget(), + const SizedBox(height: 30), + progressWidget(ref: ref, chartData: widget.chartData), + const SizedBox(height: 30), + assigneesWidget( + ref: ref, detailData: cyclesProvider.cyclesDetailsData), + const SizedBox(height: 30), + statesWidget(ref: ref, detailData: cyclesProvider.cyclesDetailsData), + const SizedBox(height: 30), + labelsWidget(), + const SizedBox(height: 30), + // widget.fromModule ? links() : Container() + ], + ); + } + } + + Widget dateWidget() { + final cyclesProvider = ref.watch(ProviderList.cyclesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + final detailData = cyclesProvider.cyclesDetailsData; + + startDate = DateFormat('yyyy-MM-dd').parse( + detailData['start_date'] == null || detailData['start_date'] == '' + ? DateTime.now().toString() + : detailData['start_date']!); + + dueDate = DateFormat('yyyy-MM-dd').parse( + detailData['end_date'] == null || detailData['end_date'] == '' + ? DateTime.now().toString() + : detailData['end_date']!); + + return Wrap( + runSpacing: 20, + children: [ + (detailData['start_date'] == null || detailData['start_date'] == '') || + (detailData['end_date'] == null || detailData['end_date'] == '') + ? Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + border: Border.all( + width: 1, + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: const CustomText( + 'Draft', + type: FontStyle.Small, + ), + ) + : Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date']) == + 'Draft' + ? themeProvider + .themeManager.tertiaryBackgroundDefaultColor + : checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date']) == + 'Completed' + ? themeProvider + .themeManager.secondaryBackgroundActiveColor + : themeProvider.themeManager.successBackgroundColor, + borderRadius: BorderRadius.circular(5)), + child: CustomText( + checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date'], + ), + type: FontStyle.Small, + color: checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date'], + ) == + 'Draft' + ? greyColor + : checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date'], + ) == + 'Completed' + ? themeProvider.themeManager.primaryColour + : greenHighLight, + ), + ), + const SizedBox(width: 20), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () async { + if (projectProvider.role != Role.admin && + projectProvider.role != Role.member) { + CustomToast.showToast(context, + message: accessRestrictedMSG, + toastType: ToastType.failure); + return; + } + final date = await showDatePicker( + builder: (context, child) => Theme( + data: themeProvider.themeManager.datePickerThemeData, + child: child!, + ), + context: context, + initialDate: startDate!, + firstDate: DateTime(2020), + lastDate: DateTime(2025), + ); + if (date != null) { + final bool dateNotConflicted = dueDate == null + ? true + : await cyclesProvider.dateCheck( + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: ref + .read(ProviderList.projectProvider) + .currentProject["id"], + data: { + "cycle_id": widget.cycleId, + "start_date": DateFormat('yyyy-MM-dd').format(date), + "end_date": + DateFormat('yyyy-MM-dd').format(dueDate!), + }, + ); + if (dateNotConflicted) { + if (dueDate != null && date.isAfter(dueDate!)) { + CustomToast.showToast(context, + message: 'Start date cannot be after end date', + toastType: ToastType.failure); + return; + } + setState(() { + startDate = date; + }); + } else { + CustomToast.showToast(context, + message: 'Date is conflicted with other cycle', + toastType: ToastType.failure); + return; + } + } + + if (date != null) { + cyclesProvider.cycleDetailsCrud( + disableLoading: true, + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: ref + .read(ProviderList.projectProvider) + .currentProject["id"], + method: CRUD.update, + cycleId: widget.cycleId, + data: {'start_date': DateFormat('yyyy-MM-dd').format(date)}, + ); + } + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: + themeProvider.themeManager.primaryBackgroundDefaultColor, + border: Border.all( + width: 1, + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: (detailData['start_date'] == null || + detailData['start_date'] == '') || + (detailData['end_date'] == null || + detailData['end_date'] == '') + ? CustomText( + 'Start Date', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.secondaryTextColor, + ) + : Row( + children: [ + Icon(Icons.calendar_today_outlined, + size: 15, + color: themeProvider + .themeManager.placeholderTextColor), + const SizedBox(width: 7), + CustomText( + '${dateFormating(detailData['start_date'])} ', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: + themeProvider.themeManager.secondaryTextColor, + ), + ], + ), + ), + ), + //arrow + const SizedBox(width: 5), + Icon( + Icons.arrow_forward, + size: 15, + color: themeProvider.themeManager.placeholderTextColor, + ), + const SizedBox(width: 5), + GestureDetector( + onTap: () async { + if (projectProvider.role != Role.admin && + projectProvider.role != Role.member) { + CustomToast.showToast(context, + message: accessRestrictedMSG, + toastType: ToastType.failure); + return; + } + final date = await showDatePicker( + builder: (context, child) => Theme( + data: themeProvider.themeManager.datePickerThemeData, + child: child!, + ), + context: context, + initialDate: dueDate!, + firstDate: startDate ?? DateTime.now(), + lastDate: DateTime(2025), + ); + + if (date != null) { + if (!date.isAfter(DateTime.now())) { + CustomToast.showToast(context, + message: 'Due date not valid ', + toastType: ToastType.failure); + return; + } + if (date.isAfter(startDate!)) { + final bool dateNotConflicted = + await cyclesProvider.dateCheck( + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: ref + .read(ProviderList.projectProvider) + .currentProject["id"], + data: { + "cycle_id": widget.cycleId, + "start_date": + DateFormat('yyyy-MM-dd').format(startDate!), + "end_date": DateFormat('yyyy-MM-dd').format(date), + }, + ); + + if (dateNotConflicted) { + setState(() { + dueDate = date; + }); + cyclesProvider.cycleDetailsCrud( + disableLoading: true, + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: ref + .read(ProviderList.projectProvider) + .currentProject["id"], + method: CRUD.update, + cycleId: widget.cycleId, + data: { + 'end_date': DateFormat('yyyy-MM-dd').format(date) + }, + ); + cyclesProvider.changeTabIndex(1); + } else { + CustomToast.showToast(context, + message: 'Date is conflicted with other cycle ', + toastType: ToastType.failure); + } + } else { + CustomToast.showToast(context, + message: 'Start date cannot be after end date ', + toastType: ToastType.failure); + } + } + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: + themeProvider.themeManager.primaryBackgroundDefaultColor, + border: Border.all( + width: 1, + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: (detailData['start_date'] == null || + detailData['start_date'] == '') || + (detailData['end_date'] == null || + detailData['end_date'] == '') + ? CustomText('End Date', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.secondaryTextColor) + : Row( + children: [ + Icon(Icons.calendar_today_outlined, + size: 15, + color: themeProvider + .themeManager.placeholderTextColor), + const SizedBox(width: 5), + CustomText( + '${dateFormating(detailData['end_date'])} ', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: themeProvider + .themeManager.secondaryTextColor), + ], + ), + ), + ), + ], + ), + ], + ); + } + + Widget detailsWidget() { + final themeProvider = ref.watch(ProviderList.themeProvider); + final cyclesProvider = ref.watch(ProviderList.cyclesProvider); + return Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomText( + 'Details', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + )), + const SizedBox(height: 10), + stateWidget(), + const SizedBox(height: 10), + assigneeWidget(ref: ref, detailData: cyclesProvider.cyclesDetailsData), + ], + ); + } + + Widget labelsWidget({bool fromModule = false}) { + final cyclesProvider = ref.watch(ProviderList.cyclesProvider); + final issuesProvider = ref.watch(ProviderList.issuesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + + final detailData = cyclesProvider.cyclesDetailsData; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomText( + 'Labels', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + )), + const SizedBox(height: 10), + detailData['distribution']['labels'].isNotEmpty + ? Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: + themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: ListView.builder( + shrinkWrap: true, + primary: false, + itemCount: detailData['distribution']['labels'].length, + itemBuilder: (context, index) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 8), + margin: const EdgeInsets.symmetric(vertical: 2), + decoration: BoxDecoration( + color: issuesProvider.issues.filters.labels.contains( + detailData['distribution']['labels'][index] + ['label_id']) + ? themeProvider + .themeManager.secondaryBackgroundDefaultColor + : themeProvider + .themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.circle, + size: 20, + color: detailData['distribution']['labels'] + [index]['label_name'] == + null + ? themeProvider.themeManager + .tertiaryBackgroundDefaultColor + : detailData['distribution']['labels'] + [index]['color'] == + '' || + detailData['distribution'] + ['labels'][index] + ['color'] == + null + ? themeProvider + .themeManager.placeholderTextColor + : detailData['distribution']['labels'] + [index]['color'] + .toString() + .toColor(), + ), + const SizedBox( + width: 10, + ), + SizedBox( + width: width * 0.4, + child: CustomText( + detailData['distribution']['labels'][index] + ['label_name'] ?? + 'No Label', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + Row( + children: [ + CompletionPercentage( + value: detailData['distribution']['labels'] + [index]['completed_issues'], + totalValue: detailData['distribution'] + ['labels'][index]['total_issues']) + ], + ) + ], + ), + ); + }), + ) + : const Align( + alignment: Alignment.center, + child: CustomText('No data found'), + ) + ], + ); + } + + Widget stateWidget({bool fromModule = false}) { + final cyclesProvider = ref.watch(ProviderList.cyclesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + return Container( + height: 45, + width: double.infinity, + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + children: [ + //icon + Icon( + //four squares icon + Icons.timelapse_rounded, + color: themeProvider.themeManager.placeholderTextColor), + const SizedBox(width: 15), + CustomText( + 'Progress', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.placeholderTextColor, + ), + Expanded(child: Container()), + + CompletionPercentage( + value: cyclesProvider.cyclesDetailsData['completed_issues'], + totalValue: cyclesProvider.cyclesDetailsData['total_issues'], + ) + ], + ), + ), + ); + } + + Widget membersWidget() { + final modulesProvider = ref.watch(ProviderList.modulesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + return GestureDetector( + onTap: () { + if (projectProvider.role != Role.admin && + projectProvider.role != Role.member) { + CustomToast.showToast(context, + message: accessRestrictedMSG, toastType: ToastType.failure); + return; + } + showModalBottomSheet( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.5, + ), + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + context: context, + builder: (context) => const AssigneeSheet( + fromModuleDetail: false, + ), + ); + }, + child: Container( + height: 45, + width: double.infinity, + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + children: [ + //icon + Icon( + //two people icon + Icons.people_alt_rounded, + color: themeProvider.themeManager.placeholderTextColor, + ), + const SizedBox(width: 15), + CustomText( + 'Members', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.placeholderTextColor, + ), + Expanded(child: Container()), + (modulesProvider.currentModule['members_detail'] == null || + modulesProvider.currentModule['members_detail'].isEmpty) + ? Row( + children: [ + CustomText( + 'No members', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.primaryTextColor, + ), + const SizedBox( + width: 5, + ), + Icon( + Icons.keyboard_arrow_down, + color: themeProvider.themeManager.primaryTextColor, + ), + ], + ) + : SizedBox( + height: 27, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.center, + child: SizedBox( + height: 30, + child: SquareAvatarWidget( + borderRadius: 50, + details: modulesProvider + .currentModule['members_detail']), + ), + ), + ) + ], + ), + ), + ), + ); + } + + String dateFormating(String date) { + final DateTime formatedDate = DateTime.parse(date); + final String finalDate = DateFormat('dd MMM').format(formatedDate); + return finalDate; + } + + String checkDate({required String startDate, required String endDate}) { + final DateTime now = DateTime.now(); + if ((startDate.isEmpty) || (endDate.isEmpty)) { + return 'Draft'; + } else { + if (DateTime.parse(startDate).isAfter(now)) { + final Duration difference = + DateTime.parse(startDate.split('+').first).difference(now); + if (difference.inDays == 0) { + return 'Today'; + } else { + return '${difference.inDays.abs() + 1} Days Left'; + } + } + if (DateTime.parse(startDate).isBefore(now) && + DateTime.parse(endDate).isAfter(now)) { + final Duration difference = DateTime.parse(endDate).difference(now); + if (difference.inDays == 0) { + return 'Today'; + } else { + return '${difference.inDays.abs() + 1} Days Left'; + } + } else { + return 'Completed'; + } + } + } + + String checkTimeDifferenc(String dateTime) { + final DateTime now = DateTime.now(); + final Duration difference = now.difference(DateTime.parse(dateTime)); + String? format; + + if (difference.inDays > 0) { + format = '${difference.inDays} days ago'; + } else if (difference.inHours > 0) { + format = '${difference.inHours} hours ago'; + } else if (difference.inMinutes > 0) { + format = '${difference.inMinutes} minutes ago'; + } else { + format = '${difference.inSeconds} seconds ago'; + } + + return format; + } +} diff --git a/lib/screens/project/cycles/cycle-detail/cycle_issues_page.dart b/lib/screens/project/cycles/cycle-detail/cycle_issues_page.dart new file mode 100644 index 00000000..c8478321 --- /dev/null +++ b/lib/screens/project/cycles/cycle-detail/cycle_issues_page.dart @@ -0,0 +1,831 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:loading_indicator/loading_indicator.dart'; +import 'package:plane/bottom-sheets/filters/filter_sheet.dart'; +import 'package:plane/bottom-sheets/type_sheet.dart'; +import 'package:plane/bottom-sheets/views_sheet.dart'; +import 'package:plane/kanban/custom/board.dart'; +import 'package:plane/kanban/models/inputs.dart'; +import 'package:plane/models/chart_model.dart'; +import 'package:plane/models/issues.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/cycles/cycle-detail/cycle_details_page.dart'; +import 'package:plane/screens/project/issue-layouts/calender_view.dart'; +import 'package:plane/screens/project/issue-layouts/spreadsheet_view.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; +import 'package:plane/utils/constants.dart'; +import 'package:plane/utils/custom_toast.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_app_bar.dart'; +import 'package:plane/widgets/custom_text.dart'; +import 'package:plane/widgets/empty.dart'; +import '../../../../../bottom-sheets/select_cycle_sheet.dart'; + +class CycleDetail extends ConsumerStatefulWidget { + const CycleDetail( + {super.key, this.cycleId, this.cycleName, this.projId, this.from}); + final String? cycleName; + final String? cycleId; + final String? projId; + final PreviousScreen? from; + + @override + ConsumerState createState() => _CycleDetailState(); +} + +class _CycleDetailState extends ConsumerState { + List chartData = []; + PageController? pageController = PageController(); + List tempIssuesList = []; + + DateTime? dueDate; + DateTime? startDate; + + @override + void initState() { + final issuesProvider = ref.read(ProviderList.issuesProvider); + tempIssuesList = issuesProvider.issuesList; + issuesProvider.tempProjectView = issuesProvider.issues.projectView; + issuesProvider.tempGroupBy = issuesProvider.issues.groupBY; + issuesProvider.tempOrderBy = issuesProvider.issues.orderBY; + issuesProvider.tempIssueType = issuesProvider.issues.issueType; + issuesProvider.tempFilters = issuesProvider.issues.filters; + + issuesProvider.issues.projectView = IssueLayout.kanban; + issuesProvider.issues.groupBY = GroupBY.state; + + issuesProvider.issues.orderBY = OrderBY.lastCreated; + issuesProvider.issues.issueType = IssueType.all; + issuesProvider.showEmptyStates = true; + issuesProvider.issues.filters = Filters( + assignees: [], + createdBy: [], + labels: [], + priorities: [], + states: [], + targetDate: [], + startDate: [], + stateGroup: [], + subscriber: [], + ); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + getCycleData(); + }); + super.initState(); + } + + Future getCycleData() async { + final cyclesProvider = ref.read(ProviderList.cyclesProvider); + final issuesProvider = ref.read(ProviderList.issuesProvider); + cyclesProvider.cyclesDetailState = StateEnum.loading; + pageController = PageController( + initialPage: cyclesProvider.cycleDetailSelectedIndex, + keepPage: true, + viewportFraction: 1); + + await cyclesProvider + .cycleDetailsCrud( + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: widget.projId ?? + ref.read(ProviderList.projectProvider).currentProject['id'], + method: CRUD.read, + disableLoading: true, + cycleId: widget.cycleId!) + .then((value) => getChartData(cyclesProvider + .cyclesDetailsData['distribution']['completion_chart'])); + + ref + .read(ProviderList.issuesProvider) + .getIssueDisplayProperties(issueCategory: IssueCategory.cycleIssues); + await cyclesProvider.getCycleView(cycleId: widget.cycleId!); + cyclesProvider + .filterCycleIssues( + cycleID: widget.cycleId!, + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: widget.projId ?? + ref.read(ProviderList.projectProvider).currentProject['id'], + ref: ref) + .then((value) { + if (issuesProvider.issues.projectView == IssueLayout.list) { + cyclesProvider.initializeBoard(); + } + }); + } + + Future getChartData(Map data) async { + data.forEach((key, value) { + chartData.add( + ChartData(DateTime.parse(key), value != null ? value.toDouble() : 0)); + }); + } + + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final cyclesProvider = ref.watch(ProviderList.cyclesProvider); + final cyclesProviderRead = ref.read(ProviderList.cyclesProvider.notifier); + final issueProvider = ref.watch(ProviderList.issuesProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + return WillPopScope( + onWillPop: () async { + return true; + }, + child: Scaffold( + floatingActionButton: cyclesProvider.cyclesIssueState == + StateEnum.loading && + (cyclesProvider.cyclesDetailsData['backlog_issues'] != 0 && + cyclesProvider.cyclesDetailsData['started_issues'] != 0 && + cyclesProvider.cyclesDetailsData['unstarted_issues'] != 0) + ? FloatingActionButton( + backgroundColor: themeProvider.themeManager.primaryColour, + onPressed: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: + BoxConstraints(maxHeight: height * 0.8, minHeight: 250), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + ), + ), + context: context, + builder: (ctx) { + return const SelectCycleSheet(); + }, + ); + }, + child: Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryColour, + borderRadius: BorderRadius.circular(50), + ), + child: const Icon( + Icons.error_outline_outlined, + color: Colors.white, + ), + ), + ) + : null, + appBar: CustomAppBar( + centerTitle: true, + onPressed: () { + if (widget.from == PreviousScreen.myIssues) { + Navigator.pop(context); + return; + } + cyclesProvider.changeTabIndex(0); + cyclesProvider.changeState(StateEnum.empty); + Navigator.pop(context); + }, + text: widget.projId == null + ? projectProvider.currentProject['name'] + : projectProvider.projects.firstWhere( + (element) => element['id'] == widget.projId)['name'], + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 25, top: 20), + child: CustomText( + widget.cycleName!, + maxLines: 1, + type: FontStyle.H5, + fontWeight: FontWeightt.Semibold, + ), + ), + ), + ], + ), + SizedBox( + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + cyclesProvider.changeTabIndex(0); + pageController!.animateToPage( + 0, + duration: const Duration(milliseconds: 200), + curve: Curves.easeIn, + ); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: CustomText( + overrride: true, + 'Issues', + type: FontStyle.Large, + fontWeight: FontWeightt.Medium, + color: + cyclesProviderRead.cycleDetailSelectedIndex == 1 + ? themeProvider + .themeManager.placeholderTextColor + : themeProvider.themeManager.primaryColour, + ), + ), + Container( + height: 7, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: + cyclesProviderRead.cycleDetailSelectedIndex == 0 + ? themeProvider.themeManager.primaryColour + : Colors.transparent, + ), + ), + ], + ), + )), + Expanded( + child: InkWell( + onTap: () { + cyclesProvider.changeTabIndex(1); + pageController!.animateToPage( + 1, + duration: const Duration(milliseconds: 200), + curve: Curves.easeIn, + ); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: CustomText( + 'Details', + overrride: true, + type: FontStyle.Large, + fontWeight: FontWeightt.Medium, + color: + cyclesProviderRead.cycleDetailSelectedIndex == 0 + ? themeProvider + .themeManager.placeholderTextColor + : themeProvider.themeManager.primaryColour, + ), + ), + Container( + height: 7, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: + cyclesProviderRead.cycleDetailSelectedIndex == 1 + ? themeProvider.themeManager.primaryColour + : Colors.transparent, + ), + ), + ], + ), + )), + ], + ), + ), + Container( + height: 2, + width: MediaQuery.of(context).size.width, + color: themeProvider.themeManager.borderSubtle01Color, + ), + Expanded( + child: PageView( + controller: pageController, + onPageChanged: (value) { + if (value == 0) { + cyclesProvider.changeTabIndex(0); + } else { + cyclesProvider.changeTabIndex(1); + } + }, + children: [ + Column( + children: [ + Expanded( + child: + (cyclesProvider.cyclesIssueState == + StateEnum.loading || + cyclesProvider.cyclesDetailState == + StateEnum.loading) || + cyclesProvider.cycleViewState == + StateEnum.loading + ? Center( + child: SizedBox( + width: 30, + height: 30, + child: LoadingIndicator( + indicatorType: + Indicator.lineSpinFadeLoader, + colors: [ + themeProvider + .themeManager.primaryTextColor + ], + strokeWidth: 1.0, + backgroundColor: themeProvider + .themeManager + .primaryBackgroundDefaultColor, + )), + ) + : (cyclesProvider.isIssuesEmpty) + ? EmptyPlaceholder.emptyIssues(context, + cycleId: widget.cycleId, + projectId: widget.projId, + type: IssueCategory.cycleIssues, + ref: ref) + : (issueProvider.issues.projectView == + IssueLayout.list) + ? Container( + color: themeProvider.themeManager + .secondaryBackgroundDefaultColor, + margin: + const EdgeInsets.only(top: 5), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: cyclesProvider + .issues.issues + .map((state) => state + .items + .isEmpty && + !cyclesProvider + .showEmptyStates + ? Container() + : SizedBox( + child: Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Container( + padding: const EdgeInsets + .only( + left: + 15), + margin: const EdgeInsets + .only( + bottom: + 10), + child: Row( + children: [ + state.leading ?? + Container(), + Container( + padding: + const EdgeInsets.only( + left: + 10, + ), + width: + MediaQuery.of(context).size.width * 0.6, + child: + CustomText( + state.title!, + overflow: + TextOverflow.ellipsis, + maxLines: + 1, + type: + FontStyle.Small, + fontWeight: + FontWeightt.Medium, + ), + ), + Container( + alignment: + Alignment.center, + margin: + const EdgeInsets.only( + left: + 15, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + color: themeProvider.themeManager.secondaryBackgroundDefaultColor), + height: + 25, + width: + 30, + child: + CustomText( + state.items.length.toString(), + type: + FontStyle.Medium, + ), + ), + const Spacer(), + IconButton( + onPressed: + () { + if (issueProvider.issues.groupBY == GroupBY.state) { + issueProvider.createIssuedata['state'] = state.id; + } else { + issueProvider.createIssuedata['priority'] = 'de3c90cd-25cd-42ec-ac6c-a66caf8029bc'; + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => CreateIssue( + cycleId: widget.cycleId, + ), + ), + ); + }, + icon: + Icon( + Icons.add, + color: themeProvider.themeManager.primaryColour, + )), + const SizedBox( + width: + 10, + ), + ], + ), + ), + Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: state + .items + .map((e) => + e) + .toList()), + state.items + .isEmpty + ? Container( + margin: const EdgeInsets + .only( + bottom: 10), + width: MediaQuery.of(context) + .size + .width, + color: themeProvider + .themeManager + .primaryBackgroundDefaultColor, + padding: const EdgeInsets + .only( + top: 15, + bottom: 15, + left: 15), + child: + const CustomText( + 'No issues.', + type: + FontStyle.Small, + maxLines: + 10, + textAlign: + TextAlign.start, + ), + ) + : Container( + margin: const EdgeInsets + .only( + bottom: 10), + ) + ], + ), + )) + .toList()), + ), + ) + : issueProvider.issues.projectView == + IssueLayout.kanban + ? Padding( + padding: const EdgeInsets.only( + left: 8), + child: KanbanBoard( + cyclesProvider + .initializeBoard(), + boardID: 'cycle-board', + onItemReorder: ( + {newCardIndex, + newListIndex, + oldCardIndex, + oldListIndex}) { + cyclesProvider + .reorderIssue( + newCardIndex: + newCardIndex!, + newListIndex: + newListIndex!, + oldCardIndex: + oldCardIndex!, + oldListIndex: + oldListIndex!, + ) + .catchError( + (err) { + log(err.toString()); + CustomToast.showToast( + context, + message: + 'Failed to update issue', + toastType: + ToastType.failure, + ); + }, + ); + }, + isCardsDraggable: issueProvider + .checkIsCardsDaraggable(), + groupEmptyStates: + !cyclesProvider + .showEmptyStates, + cardPlaceHolderColor: + themeProvider.themeManager + .primaryBackgroundDefaultColor, + cardPlaceHolderDecoration: + BoxDecoration( + color: themeProvider + .themeManager + .primaryBackgroundDefaultColor, + boxShadow: [ + BoxShadow( + blurRadius: 2, + color: themeProvider + .themeManager + .borderSubtle01Color, + spreadRadius: 0, + ) + ], + ), + backgroundColor: themeProvider + .themeManager + .secondaryBackgroundDefaultColor, + listScrollConfig: + ScrollConfig( + offset: 65, + duration: const Duration( + milliseconds: 100, + ), + curve: Curves.linear, + ), + listTransitionDuration: + const Duration( + milliseconds: 200, + ), + cardTransitionDuration: + const Duration( + milliseconds: 400, + ), + textStyle: TextStyle( + fontSize: 19, + height: 1.3, + color: Colors.grey.shade800, + fontWeight: FontWeight.w500, + ), + ), + ) + : issueProvider + .issues.projectView == + IssueLayout.calendar + ? const CalendarView() + : const SpreadSheetView( + issueCategory: IssueCategory + .cycleIssues, + ), + ), + SafeArea( + child: Container( + height: 50, + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: themeProvider + .themeManager.primaryBackgroundDefaultColor, + boxShadow: themeProvider + .themeManager.shadowBottomControlButtons), + child: Row( + children: [ + projectProvider.role == Role.admin || + projectProvider.role == Role.member + ? Expanded( + child: GestureDetector( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CreateIssue( + projectId: widget.projId ?? + projectProvider + .currentProject['id'], + fromMyIssues: true, + cycleId: widget.cycleId, + ), + ), + ); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.add, + color: themeProvider + .themeManager + .primaryTextColor, + size: 20, + ), + const CustomText( + ' Issue', + type: FontStyle.Medium, + ) + ], + ), + ), + ), + ) + : Container(), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + Expanded( + child: GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context) + .size + .height * + 0.85), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return const TypeSheet( + issueCategory: + IssueCategory.cycleIssues, + ); + }); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.menu, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Layout', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + issueProvider.issues.projectView == + IssueLayout.calendar + ? Container() + : Expanded( + child: GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context) + .size + .height * + 0.9), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return ViewsSheet( + projectView: issueProvider + .issues.projectView, + issueCategory: + IssueCategory.cycleIssues, + cycleId: widget.cycleId, + ); + }); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.wysiwyg_outlined, + color: themeProvider.themeManager + .primaryTextColor, + size: 19, + ), + const CustomText( + ' Display', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + Expanded( + child: GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context) + .size + .height * + 0.85), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return FilterSheet( + issueCategory: + IssueCategory.cycleIssues, + cycleOrModuleId: widget.cycleId, + ); + }); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.filter_list_outlined, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Filters', + type: FontStyle.Medium, + ) + ], + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + CycleDetailsPage( + cycleId: widget.cycleId!, + chartData: chartData, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_active_card.dart b/lib/screens/project/cycles/cycle_active_card.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_active_card.dart rename to lib/screens/project/cycles/cycle_active_card.dart index c300cfa0..99da1a71 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_active_card.dart +++ b/lib/screens/project/cycles/cycle_active_card.dart @@ -7,7 +7,7 @@ import 'package:plane/models/chart_model.dart'; import 'package:plane/provider/cycles_provider.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/theme_provider.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart'; +import 'package:plane/screens/project/cycles/cycle-detail/cycle_issues_page.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/extensions/list_extensions.dart'; import 'package:plane/utils/extensions/string_extensions.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/project_details_cycles.dart b/lib/screens/project/cycles/project_details_cycles.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/project_details_cycles.dart rename to lib/screens/project/cycles/project_details_cycles.dart index aa6263ee..da375537 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/CyclesTab/project_details_cycles.dart +++ b/lib/screens/project/cycles/project_details_cycles.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_active_card.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart'; +import 'package:plane/screens/project/cycles/cycle-detail/cycle_issues_page.dart'; +import 'package:plane/screens/project/cycles/cycle_active_card.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/calender_view.dart b/lib/screens/project/issue-layouts/calender_view.dart similarity index 98% rename from lib/screens/MainScreens/Projects/ProjectDetail/calender_view.dart rename to lib/screens/project/issue-layouts/calender_view.dart index 7e66fcc2..3c122642 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/calender_view.dart +++ b/lib/screens/project/issue-layouts/calender_view.dart @@ -2,10 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; -import 'package:plane/bottom_sheets/filters/filter_sheet.dart'; -import 'package:plane/bottom_sheets/type_sheet.dart'; +import 'package:plane/bottom-sheets/filters/filter_sheet.dart'; +import 'package:plane/bottom-sheets/type_sheet.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_app_bar.dart'; @@ -13,8 +14,6 @@ import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/square_avatar_widget.dart'; import 'package:table_calendar/table_calendar.dart'; -import 'IssuesTab/issue_detail.dart'; - class CalendarView extends ConsumerStatefulWidget { const CalendarView({Key? key}) : super(key: key); @@ -291,6 +290,8 @@ class _DayDetailState extends ConsumerState { Widget build(BuildContext context) { final issuesProvider = ref.watch(ProviderList.issuesProvider); final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + return Scaffold( appBar: CustomAppBar( icon: Icons.arrow_back, @@ -346,10 +347,7 @@ class _DayDetailState extends ConsumerState { focusedDay: widget.selectedDay, calendarFormat: CalendarFormat.month, eventLoader: (day) { - return ref - .read(ProviderList.issuesProvider) - .issuesList - .where((element) { + return issuesProvider.issuesList.where((element) { if (element['target_date'] == null) return false; return DateFormat("MMM d, yyyy").format( DateTime.parse(element['target_date'])) == @@ -502,7 +500,7 @@ class _DayDetailState extends ConsumerState { Row( children: [ CustomText( - '${issuesProvider.issuesList[index]['project_detail']['identifier'].toString()} - ${issuesProvider.issuesList[index]['sequence_id']}', + '${projectProvider.currentProject['identifier'].toString()} - ${issuesProvider.issuesList[index]['sequence_id']}', type: FontStyle.Small, ), const SizedBox( diff --git a/lib/screens/project/issue-layouts/issue_layout.dart b/lib/screens/project/issue-layouts/issue_layout.dart new file mode 100644 index 00000000..e8e75fc9 --- /dev/null +++ b/lib/screens/project/issue-layouts/issue_layout.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issue-layouts/calender_view.dart'; +import 'package:plane/screens/project/issue-layouts/kanban-layout/kanban_root.dart'; +import 'package:plane/screens/project/issue-layouts/list-layout/list_root.dart'; +import 'package:plane/screens/project/issue-layouts/spreadsheet_view.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/empty.dart'; + +class IssueLayoutHandler extends ConsumerStatefulWidget { + const IssueLayoutHandler( + {required this.issueLayout, required this.isView, super.key}); + final IssueLayout issueLayout; + final bool isView; + @override + ConsumerState createState() => _IssueLayoutState(); +} + +class _IssueLayoutState extends ConsumerState { + @override + Widget build(BuildContext context) { + final issueProvider = ref.watch(ProviderList.issuesProvider); + + return issueProvider.groupByResponse.isEmpty && + issueProvider.issueState == StateEnum.success + ? EmptyPlaceholder.emptyIssues(context, ref: ref) + : widget.issueLayout == IssueLayout.list + ? const ListLayoutRoot() + : widget.issueLayout == IssueLayout.kanban + ? KanbanLayoutRoot( + isViews: widget.isView, + ) + : widget.issueLayout == IssueLayout.calendar + ? const CalendarView() + : const SpreadSheetView( + issueCategory: IssueCategory.issues, + ); + } +} diff --git a/lib/screens/project/issue-layouts/kanban-layout/kanban_root.dart b/lib/screens/project/issue-layouts/kanban-layout/kanban_root.dart new file mode 100644 index 00000000..b7f1077b --- /dev/null +++ b/lib/screens/project/issue-layouts/kanban-layout/kanban_root.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/kanban/custom/board.dart'; +import 'package:plane/kanban/models/inputs.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/utils/custom_toast.dart'; +import 'package:plane/utils/enums.dart'; + +class KanbanLayoutRoot extends ConsumerStatefulWidget { + const KanbanLayoutRoot({required this.isViews, super.key}); + final bool isViews; + @override + ConsumerState createState() => _KanbanLayoutRootState(); +} + +class _KanbanLayoutRootState extends ConsumerState { + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final issuesProvider = ref.watch(ProviderList.issuesProvider); + return KanbanBoard( + issuesProvider.initializeBoard(views: widget.isViews), + boardID: 'issues-board', + isCardsDraggable: issuesProvider.checkIsCardsDaraggable(), + onItemReorder: ( + {newCardIndex, newListIndex, oldCardIndex, oldListIndex}) { + // print('newCardIndex: $newCardIndex, newListIndex: $newListIndex, oldCardIndex: $oldCardIndex, oldListIndex: $oldListIndex'); + issuesProvider + .reorderIssue( + context: context, + newCardIndex: newCardIndex!, + newListIndex: newListIndex!, + oldCardIndex: oldCardIndex!, + oldListIndex: oldListIndex!, + ) + .then((value) { + if (issuesProvider.issues.orderBY != OrderBY.manual) { + CustomToast.showToast(context, + message: + 'This board is ordered by ${issuesProvider.issues.orderBY == OrderBY.lastUpdated ? 'last updated' : 'created at'} ', + toastType: ToastType.warning); + } + }).catchError((e) { + CustomToast.showToast(context, + message: 'Failed to update issue', toastType: ToastType.failure); + }); + }, + groupEmptyStates: !issuesProvider.showEmptyStates, + backgroundColor: + themeProvider.themeManager.secondaryBackgroundDefaultColor, + cardPlaceHolderColor: + themeProvider.themeManager.primaryBackgroundDefaultColor, + cardPlaceHolderDecoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + boxShadow: [ + BoxShadow( + blurRadius: 2, + color: themeProvider.themeManager.borderSubtle01Color, + spreadRadius: 0, + ), + ]), + listScrollConfig: ScrollConfig( + offset: 65, + duration: const Duration(milliseconds: 100), + curve: Curves.linear), + boardScrollConfig: ScrollConfig( + offset: 45, + duration: const Duration(milliseconds: 100), + curve: Curves.linear), + listTransitionDuration: const Duration(milliseconds: 200), + cardTransitionDuration: const Duration(milliseconds: 400), + ); + } +} diff --git a/lib/screens/project/issue-layouts/list-layout/list_root.dart b/lib/screens/project/issue-layouts/list-layout/list_root.dart new file mode 100644 index 00000000..422afadd --- /dev/null +++ b/lib/screens/project/issue-layouts/list-layout/list_root.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_text.dart'; + +class ListLayoutRoot extends ConsumerStatefulWidget { + const ListLayoutRoot({super.key}); + + @override + ConsumerState createState() => _ListLayoutRootState(); +} + +class _ListLayoutRootState extends ConsumerState { + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final issuesProvider = ref.watch(ProviderList.issuesProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + return Container( + color: themeProvider.themeManager.secondaryBackgroundDefaultColor, + margin: const EdgeInsets.only(top: 5), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: issuesProvider.issues.issues + .map((state) => state.items.isEmpty && + !issuesProvider.showEmptyStates + ? Container() + : SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + // padding: const EdgeInsets.only(left: 15), + margin: const EdgeInsets.only(bottom: 10), + child: Row( + children: [ + state.leading ?? Container(), + Container( + padding: const EdgeInsets.only( + left: 10, + ), + child: CustomText( + state.title!, + overflow: TextOverflow.ellipsis, + maxLines: 1, + type: FontStyle.Large, + color: themeProvider + .themeManager.primaryTextColor, + fontWeight: FontWeightt.Semibold, + ), + ), + Container( + alignment: Alignment.center, + margin: const EdgeInsets.only( + left: 15, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + color: themeProvider.themeManager + .tertiaryBackgroundDefaultColor), + height: 25, + width: 30, + child: CustomText( + state.items.length.toString(), + type: FontStyle.Small, + ), + ), + const Spacer(), + projectProvider.role == Role.admin || + projectProvider.role == Role.member + ? IconButton( + onPressed: () { + if (issuesProvider.issues.groupBY == + GroupBY.state) { + issuesProvider.createIssuedata[ + 'state'] = state.id; + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + const CreateIssue())); + }, + icon: Icon( + Icons.add, + color: themeProvider + .themeManager.tertiaryTextColor, + )) + : Container( + height: 40, + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: state.items.map((e) => e).toList()), + state.items.isEmpty + ? Container( + margin: const EdgeInsets.only(bottom: 10), + width: MediaQuery.of(context).size.width, + color: themeProvider.themeManager + .primaryBackgroundDefaultColor, + padding: const EdgeInsets.only( + top: 15, bottom: 15, left: 15), + child: const CustomText( + 'No issues.', + type: FontStyle.Small, + maxLines: 10, + textAlign: TextAlign.start, + ), + ) + : Container( + margin: const EdgeInsets.only(bottom: 10), + ) + ], + ), + )) + .toList()), + ), + ); + } +} diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/spreadsheet_view.dart b/lib/screens/project/issue-layouts/spreadsheet_view.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/spreadsheet_view.dart rename to lib/screens/project/issue-layouts/spreadsheet_view.dart index edcf9342..f75c4796 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/spreadsheet_view.dart +++ b/lib/screens/project/issue-layouts/spreadsheet_view.dart @@ -2,13 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/extensions/string_extensions.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/square_avatar_widget.dart'; -import 'IssuesTab/issue_detail.dart'; - class SpreadSheetView extends ConsumerStatefulWidget { const SpreadSheetView({super.key, required this.issueCategory}); final IssueCategory issueCategory; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart b/lib/screens/project/issues/create_issue.dart similarity index 96% rename from lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart rename to lib/screens/project/issues/create_issue.dart index b327dbbb..a5a1bf89 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart +++ b/lib/screens/project/issues/create_issue.dart @@ -7,19 +7,19 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/issues_list_sheet.dart'; -import 'package:plane/bottom_sheets/selectProjectSheet.dart'; -import 'package:plane/bottom_sheets/select_estimate.dart'; -import 'package:plane/bottom_sheets/select_issue_labels.dart'; -import 'package:plane/bottom_sheets/select_priority.dart'; -import 'package:plane/bottom_sheets/select_project_members.dart'; -import 'package:plane/bottom_sheets/select_states.dart'; +import 'package:plane/bottom-sheets/issues_list_sheet.dart'; +import 'package:plane/bottom-sheets/selectProjectSheet.dart'; +import 'package:plane/bottom-sheets/select_estimate.dart'; +import 'package:plane/bottom-sheets/select_issue_labels.dart'; +import 'package:plane/bottom-sheets/select_priority.dart'; +import 'package:plane/bottom-sheets/select_project_members.dart'; +import 'package:plane/bottom-sheets/select_states.dart'; import 'package:plane/config/config_variables.dart'; import 'package:plane/config/const.dart'; import 'package:plane/mixins/widget_state_mixin.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/theme_provider.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/selected_label_widget.dart'; +import 'package:plane/screens/project/issues/widgets/selected_label_widget.dart'; import 'package:plane/utils/color_manager.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; @@ -56,7 +56,6 @@ class _CreateIssueState extends ConsumerState Map tempStates = {}; List tempStateOrdering = []; Map tempStatesIcons = {}; - List tempLabels = []; List tempIssues = []; List tempAssignees = []; bool descriptionExpanded = false; @@ -67,6 +66,9 @@ class _CreateIssueState extends ConsumerState void initCreateIssue() { final prov = ref.read(ProviderList.issuesProvider); final projectProvider = ref.read(ProviderList.projectProvider); + final statesNotifier = ref.read(ProviderList.statesProvider.notifier); + final statesProvider = ref.read(ProviderList.statesProvider); + ref.read(ProviderList.issueProvider).initCookies(); prov.createIssueProjectData = widget.projectId != null ? projectProvider.projects @@ -74,27 +76,25 @@ class _CreateIssueState extends ConsumerState : projectProvider.currentProject; final themeProvider = ref.read(ProviderList.themeProvider); tempStatesData = prov.statesData; - tempStates = prov.states; - tempStateOrdering = prov.stateOrdering; + tempStates = statesProvider.projectStates; tempStatesIcons = prov.stateIcons; - tempLabels = prov.labels; tempIssues = ref.read(ProviderList.searchIssueProvider).issues; - tempAssignees = prov.members; + tempAssignees = projectProvider.projectMembers; if (widget.fromMyIssues) { - prov.statesState = StateEnum.loading; - prov + statesProvider.statesState = StateEnum.loading; + statesNotifier .getStates( - showLoading: false, slug: ref .read(ProviderList.workspaceProvider) .selectedWorkspace .workspaceSlug, - projID: widget.projectId ?? + projectId: widget.projectId ?? ref.read(ProviderList.projectProvider).currentProject['id']) .then((value) { - prov.createIssuedata['state'] = - (prov.states.isNotEmpty ? prov.states.keys.first : null); + prov.createIssuedata['state'] = (statesProvider.projectStates.isNotEmpty + ? statesProvider.projectStates.keys.first + : null); }); ref.read(ProviderList.estimatesProvider).getEstimates( @@ -106,14 +106,14 @@ class _CreateIssueState extends ConsumerState ref.read(ProviderList.projectProvider).currentProject['id']); } else { bool found = false; - for (final element in prov.states.keys) { + for (final element in statesProvider.projectStates.keys) { if (element == prov.createIssuedata['state']) { found = true; } } if (!found) { - prov.createIssuedata['state'] = prov.states.keys.first; + prov.createIssuedata['state'] = statesProvider.projectStates.keys.first; } } @@ -141,7 +141,7 @@ class _CreateIssueState extends ConsumerState LoadingType getLoading(WidgetRef ref) { return setWidgetState([ ref.read(ProviderList.issuesProvider).createIssueState, - ref.read(ProviderList.issuesProvider).statesState, + ref.read(ProviderList.statesProvider).statesState, ], loadingType: LoadingType.wrap); } @@ -165,23 +165,22 @@ class _CreateIssueState extends ConsumerState final themeProvider = ref.watch(ProviderList.themeProvider); final issuesProvider = ref.watch(ProviderList.issuesProvider); final projectProvider = ref.watch(ProviderList.projectProvider); + final statesNotifier = ref.watch(ProviderList.statesProvider.notifier); final estimatesProvider = ref.watch(ProviderList.estimatesProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); final BuildContext baseContext = context; if (issuesProvider.createIssuedata['state'] == null && - issuesProvider.states.isNotEmpty) { + statesProvider.projectStates.isNotEmpty) { issuesProvider.createIssuedata['state'] = - issuesProvider.states.keys.first; + statesProvider.projectStates.keys.first; } return WillPopScope( onWillPop: () async { - log('WillPopScope'); issuesProvider.createIssuedata = {}; issuesProvider.statesData = tempStatesData; - issuesProvider.states = tempStates; - issuesProvider.stateOrdering = tempStateOrdering; + // issuesProvider.states = tempStates; issuesProvider.stateIcons = tempStatesIcons; - issuesProvider.labels = tempLabels; - issuesProvider.members = tempAssignees; + projectProvider.projectMembers = tempAssignees; issuesProvider.setsState(); ref.read(ProviderList.searchIssueProvider).issues = tempIssues; ref.read(ProviderList.issueProvider).clearCookies(); @@ -196,11 +195,9 @@ class _CreateIssueState extends ConsumerState onPressed: () { issuesProvider.createIssuedata = {}; issuesProvider.statesData = tempStatesData; - issuesProvider.states = tempStates; - issuesProvider.stateOrdering = tempStateOrdering; + // issuesProvider.states = tempStates; issuesProvider.stateIcons = tempStatesIcons; - issuesProvider.labels = tempLabels; - issuesProvider.members = tempAssignees; + projectProvider.projectMembers = tempAssignees; issuesProvider.setsState(); ref.read(ProviderList.issueProvider).clearCookies(); ref.read(ProviderList.searchIssueProvider).issues = tempIssues; @@ -271,20 +268,21 @@ class _CreateIssueState extends ConsumerState builder: (ctx) => const SelectProject()).then( (value) { - issuesProvider + statesNotifier .getStates( slug: ref .read(ProviderList .workspaceProvider) .selectedWorkspace .workspaceSlug, - projID: issuesProvider + projectId: issuesProvider .createIssueProjectData[ 'id']) .then((value) { issuesProvider .createIssuedata['state'] = - issuesProvider.states.keys.first; + statesProvider + .projectStates.keys.first; }); ref .read( @@ -774,20 +772,21 @@ class _CreateIssueState extends ConsumerState issuesProvider .createIssuedata[ 'state']]), - CustomText( - issuesProvider.createIssuedata[ - 'state'] == - null - ? 'Select' - : issuesProvider - .states[issuesProvider - .createIssuedata[ - 'state']]['name'], - type: FontStyle.Small, - color: themeProvider - .themeManager - .primaryTextColor, - ), + // CustomText( + // issuesProvider.createIssuedata[ + // 'state'] == + // null + // ? 'Select' + // : issuesProvider + // .states[issuesProvider + // .createIssuedata[ + // 'state']]['name'], + // type: FontStyle.Small, + // color: themeProvider + // .themeManager + // .primaryTextColor, + // ), + issuesProvider.createIssuedata[ 'state'] == null @@ -1836,11 +1835,9 @@ class _CreateIssueState extends ConsumerState issuesProvider.createIssuedata = {}; issuesProvider.statesData = tempStatesData; - issuesProvider.states = tempStates; - issuesProvider.stateOrdering = tempStateOrdering; + // issuesProvider.states = tempStates; issuesProvider.stateIcons = tempStatesIcons; - issuesProvider.labels = tempLabels; - issuesProvider.members = tempAssignees; + projectProvider.projectMembers = tempAssignees; issuesProvider.setsState(); ref.read(ProviderList.searchIssueProvider).issues = tempIssues; @@ -1870,32 +1867,31 @@ class _CreateIssueState extends ConsumerState final themeProvider = ref.read(ProviderList.themeProvider); tempStatesData = issuesProvider.statesData; - tempStates = issuesProvider.states; - tempStateOrdering = issuesProvider.stateOrdering; + tempStates = statesProvider.projectStates; tempStatesIcons = issuesProvider.stateIcons; - tempLabels = issuesProvider.labels; tempIssues = ref .read(ProviderList.searchIssueProvider) .issues; - tempAssignees = issuesProvider.members; + tempAssignees = projectProvider.projectMembers; if (widget.fromMyIssues) { - issuesProvider + statesNotifier .getStates( slug: ref .read( ProviderList.workspaceProvider) .selectedWorkspace .workspaceSlug, - projID: widget.projectId ?? + projectId: widget.projectId ?? ref .read(ProviderList .projectProvider) .currentProject['id']) .then((value) { issuesProvider.createIssuedata['state'] = - (issuesProvider.states.isNotEmpty - ? issuesProvider.states.keys.first + (statesProvider.projectStates.isNotEmpty + ? statesProvider + .projectStates.keys.first : null); }); diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart b/lib/screens/project/issues/issue_detail.dart similarity index 95% rename from lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart rename to lib/screens/project/issues/issue_detail.dart index 5111f379..b61b39a9 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart +++ b/lib/screens/project/issues/issue_detail.dart @@ -5,10 +5,10 @@ import 'package:plane/provider/issues_provider.dart'; import 'package:plane/provider/my_issues_provider.dart'; import 'package:plane/utils/enums.dart'; -import '../../../../../provider/cycles_provider.dart'; -import '../../../../../provider/modules_provider.dart'; -import '../../../../../provider/provider_list.dart'; -import '../../../../../utils/editor.dart'; +import '../../../../provider/cycles_provider.dart'; +import '../../../../provider/modules_provider.dart'; +import '../../../../provider/provider_list.dart'; +import '../../../../utils/editor.dart'; enum PreviousScreen { myIssues, @@ -117,12 +117,12 @@ class _IssueDetailState extends ConsumerState { case PreviousScreen.cycles: reff .read(ProviderList.cyclesProvider) - .filterCycleIssues(slug: workspaceSlug, projectId: projID); + .filterCycleIssues(slug: workspaceSlug, projectId: projID, ref: ref); break; case PreviousScreen.modules: reff .read(ProviderList.modulesProvider) - .filterModuleIssues(slug: workspaceSlug, projectId: projID); + .filterModuleIssues(slug: workspaceSlug, projectId: projID, ref: ref); break; case PreviousScreen.views: reff diff --git a/lib/screens/project/issues/issues_tab.dart b/lib/screens/project/issues/issues_tab.dart new file mode 100644 index 00000000..76058a4a --- /dev/null +++ b/lib/screens/project/issues/issues_tab.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issue-layouts/issue_layout.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/loading_widget.dart'; + +class IssuesTab extends ConsumerStatefulWidget { + const IssuesTab({super.key}); + + @override + ConsumerState createState() => _IssuesTabState(); +} + +class _IssuesTabState extends ConsumerState { + @override + Widget build(BuildContext context) { + final issueProvider = ref.watch(ProviderList.issuesProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + return LoadingWidget( + loading: issueProvider.issuePropertyState == StateEnum.loading || + issueProvider.issueState == StateEnum.loading || + statesProvider.statesState == StateEnum.loading || + issueProvider.projectViewState == StateEnum.loading || + issueProvider.orderByState == StateEnum.loading, + widgetClass: IssueLayoutHandler( + issueLayout: issueProvider.issues.projectView, + isView: false, + )); + } +} diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/selected_label_widget.dart b/lib/screens/project/issues/widgets/selected_label_widget.dart similarity index 100% rename from lib/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/selected_label_widget.dart rename to lib/screens/project/issues/widgets/selected_label_widget.dart diff --git a/lib/screens/project/models/project_detail_models.dart b/lib/screens/project/models/project_detail_models.dart new file mode 100644 index 00000000..63cc45a1 --- /dev/null +++ b/lib/screens/project/models/project_detail_models.dart @@ -0,0 +1,10 @@ +class ProjectDetailTab { + ProjectDetailTab({ + required this.title, + required this.width, + required this.show, + }); + String title; + double width; + bool show; +} diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/create_module.dart b/lib/screens/project/modules/create_module.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/create_module.dart rename to lib/screens/project/modules/create_module.dart index e186f2be..268b8d2b 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/create_module.dart +++ b/lib/screens/project/modules/create_module.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:intl/intl.dart'; -import 'package:plane/bottom_sheets/assignee_sheet.dart'; -import 'package:plane/bottom_sheets/lead_sheet.dart'; -import 'package:plane/bottom_sheets/status_sheet.dart'; +import 'package:plane/bottom-sheets/assignee_sheet.dart'; +import 'package:plane/bottom-sheets/lead_sheet.dart'; +import 'package:plane/bottom-sheets/status_sheet.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/widgets/custom_button.dart'; import 'package:plane/widgets/loading_widget.dart'; diff --git a/lib/screens/project/modules/module-detail/module_detail_page.dart b/lib/screens/project/modules/module-detail/module_detail_page.dart new file mode 100644 index 00000000..6ca73b03 --- /dev/null +++ b/lib/screens/project/modules/module-detail/module_detail_page.dart @@ -0,0 +1,664 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:loading_indicator/loading_indicator.dart'; +import 'package:plane/bottom-sheets/assignee_sheet.dart'; +import 'package:plane/models/chart_model.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/widgets/assignee_widget.dart'; +import 'package:plane/screens/project/widgets/assignees_widget.dart'; +import 'package:plane/screens/project/widgets/links_widget.dart'; +import 'package:plane/screens/project/widgets/progress_chart.dart'; +import 'package:plane/screens/project/widgets/states_widget.dart'; +import 'package:plane/utils/constants.dart'; +import 'package:plane/utils/custom_toast.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/utils/extensions/string_extensions.dart'; +import 'package:plane/widgets/completion_percentage.dart'; +import 'package:plane/widgets/custom_text.dart'; +import 'package:plane/widgets/square_avatar_widget.dart'; + +class ModuleDetailsPage extends ConsumerStatefulWidget { + const ModuleDetailsPage( + {super.key, required this.moduleId, required this.chartData}); + final String moduleId; + final List chartData; + + @override + ConsumerState createState() => _ModuleDetailsPageState(); +} + +class _ModuleDetailsPageState extends ConsumerState { + DateTime? dueDate; + DateTime? startDate; + + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final modulesProvider = ref.watch(ProviderList.modulesProvider); + + if (modulesProvider.moduleDetailState == StateEnum.loading) { + return Center( + child: SizedBox( + width: 30, + height: 30, + child: LoadingIndicator( + indicatorType: Indicator.lineSpinFadeLoader, + colors: [themeProvider.themeManager.primaryTextColor], + strokeWidth: 1.0, + backgroundColor: Colors.transparent, + ), + ), + ); + } else { + return ListView( + children: [ + const SizedBox(height: 30), + dateWidget(), + const SizedBox(height: 30), + detailsWidget(), + const SizedBox(height: 30), + progressWidget(ref: ref, chartData: widget.chartData), + const SizedBox(height: 30), + assigneesWidget( + ref: ref, + detailData: modulesProvider.moduleDetailsData['distribution']), + const SizedBox(height: 30), + statesWidget(ref: ref, detailData: modulesProvider.moduleDetailsData), + const SizedBox(height: 30), + labelsWidget(), + const SizedBox(height: 30), + links(ref: ref, context: context) + ], + ); + } + } + + Widget dateWidget() { + final modulesProvider = ref.watch(ProviderList.modulesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + final detailData = modulesProvider.moduleDetailsData; + + startDate = DateFormat('yyyy-MM-dd').parse( + detailData['start_date'] == null || detailData['start_date'] == '' + ? DateTime.now().toString() + : detailData['start_date']!); + + dueDate = DateFormat('yyyy-MM-dd').parse( + detailData['end_date'] == null || detailData['end_date'] == '' + ? DateTime.now().toString() + : detailData['end_date']!); + + return Wrap( + runSpacing: 20, + children: [ + (detailData['start_date'] == null || detailData['start_date'] == '') || + (detailData['end_date'] == null || detailData['end_date'] == '') + ? Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + border: Border.all( + width: 1, + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: const CustomText( + 'Draft', + type: FontStyle.Small, + ), + ) + : Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date']) == + 'Draft' + ? themeProvider + .themeManager.tertiaryBackgroundDefaultColor + : checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date']) == + 'Completed' + ? themeProvider + .themeManager.secondaryBackgroundActiveColor + : themeProvider.themeManager.successBackgroundColor, + borderRadius: BorderRadius.circular(5)), + child: CustomText( + checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date'], + ), + type: FontStyle.Small, + color: checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date'], + ) == + 'Draft' + ? greyColor + : checkDate( + startDate: detailData['start_date'], + endDate: detailData['end_date'], + ) == + 'Completed' + ? themeProvider.themeManager.primaryColour + : greenHighLight, + ), + ), + const SizedBox(width: 20), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () async { + if (projectProvider.role != Role.admin && + projectProvider.role != Role.member) { + CustomToast.showToast(context, + message: accessRestrictedMSG, + toastType: ToastType.failure); + return; + } + final date = await showDatePicker( + builder: (context, child) => Theme( + data: themeProvider.themeManager.datePickerThemeData, + child: child!, + ), + context: context, + initialDate: startDate!, + firstDate: DateTime(2020), + lastDate: DateTime(2025), + ); + if (date != null) { + if (dueDate != null && date.isAfter(dueDate!)) { + CustomToast.showToast(context, + message: 'Start date cannot be after end date', + toastType: ToastType.failure); + return; + } + setState(() { + startDate = date; + }); + } + + if (date != null) { + modulesProvider.updateModules( + disableLoading: true, + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projId: ref + .read(ProviderList.projectProvider) + .currentProject["id"], + moduleId: widget.moduleId, + data: {'start_date': DateFormat('yyyy-MM-dd').format(date)}, + ref: ref, + ); + } + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: + themeProvider.themeManager.primaryBackgroundDefaultColor, + border: Border.all( + width: 1, + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: (detailData['start_date'] == null || + detailData['start_date'] == '') || + (detailData['end_date'] == null || + detailData['end_date'] == '') + ? CustomText( + 'Start Date', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.secondaryTextColor, + ) + : Row( + children: [ + Icon(Icons.calendar_today_outlined, + size: 15, + color: themeProvider + .themeManager.placeholderTextColor), + const SizedBox(width: 7), + CustomText( + '${dateFormating(detailData['start_date'])} ', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: + themeProvider.themeManager.secondaryTextColor, + ), + ], + ), + ), + ), + //arrow + const SizedBox(width: 5), + Icon( + Icons.arrow_forward, + size: 15, + color: themeProvider.themeManager.placeholderTextColor, + ), + const SizedBox(width: 5), + GestureDetector( + onTap: () async { + if (projectProvider.role != Role.admin && + projectProvider.role != Role.member) { + CustomToast.showToast(context, + message: accessRestrictedMSG, + toastType: ToastType.failure); + return; + } + final date = await showDatePicker( + builder: (context, child) => Theme( + data: themeProvider.themeManager.datePickerThemeData, + child: child!, + ), + context: context, + initialDate: dueDate!, + firstDate: startDate ?? DateTime.now(), + lastDate: DateTime(2025), + ); + + if (date != null) { + if (!date.isAfter(DateTime.now())) { + CustomToast.showToast(context, + message: 'Due date not valid ', + toastType: ToastType.failure); + return; + } + if (date.isAfter(startDate!)) { + modulesProvider.updateModules( + disableLoading: true, + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projId: ref + .read(ProviderList.projectProvider) + .currentProject["id"], + moduleId: widget.moduleId, + data: { + 'end_date': DateFormat('yyyy-MM-dd').format(date) + }, + ref: ref); + modulesProvider.changeTabIndex(1); + } else { + CustomToast.showToast(context, + message: 'Start date cannot be after end date ', + toastType: ToastType.failure); + } + } + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: + themeProvider.themeManager.primaryBackgroundDefaultColor, + border: Border.all( + width: 1, + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: (detailData['start_date'] == null || + detailData['start_date'] == '') || + (detailData['end_date'] == null || + detailData['end_date'] == '') + ? CustomText('End Date', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.secondaryTextColor) + : Row( + children: [ + Icon(Icons.calendar_today_outlined, + size: 15, + color: themeProvider + .themeManager.placeholderTextColor), + const SizedBox(width: 5), + CustomText( + '${dateFormating(detailData['end_date'])} ', + type: FontStyle.Small, + fontWeight: FontWeightt.Regular, + color: themeProvider + .themeManager.secondaryTextColor), + ], + ), + ), + ), + ], + ), + ], + ); + } + + Widget detailsWidget() { + final themeProvider = ref.watch(ProviderList.themeProvider); + final modulesProvider = ref.watch(ProviderList.modulesProvider); + return Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomText( + 'Details', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + )), + const SizedBox(height: 10), + stateWidget(), + const SizedBox(height: 10), + assigneeWidget(ref: ref, detailData: modulesProvider.moduleDetailsData), + ], + ); + } + + Widget labelsWidget({bool fromModule = false}) { + final modulesProvider = ref.watch(ProviderList.modulesProvider); + final issuesProvider = ref.watch(ProviderList.issuesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + + final detailData = modulesProvider.moduleDetailsData; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomText( + 'Labels', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + )), + const SizedBox(height: 10), + detailData['distribution']['labels'].isNotEmpty + ? Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: + themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: ListView.builder( + shrinkWrap: true, + primary: false, + itemCount: detailData['distribution']['labels'].length, + itemBuilder: (context, index) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 8), + margin: const EdgeInsets.symmetric(vertical: 2), + decoration: BoxDecoration( + color: issuesProvider.issues.filters.labels.contains( + detailData['distribution']['labels'][index] + ['label_id']) + ? themeProvider + .themeManager.secondaryBackgroundDefaultColor + : themeProvider + .themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.circle, + size: 20, + color: detailData['distribution']['labels'] + [index]['label_name'] == + null + ? themeProvider.themeManager + .tertiaryBackgroundDefaultColor + : detailData['distribution']['labels'] + [index]['color'] == + '' || + detailData['distribution'] + ['labels'][index] + ['color'] == + null + ? themeProvider + .themeManager.placeholderTextColor + : detailData['distribution']['labels'] + [index]['color'] + .toString() + .toColor(), + ), + const SizedBox( + width: 10, + ), + SizedBox( + width: width * 0.4, + child: CustomText( + detailData['distribution']['labels'][index] + ['label_name'] ?? + 'No Label', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + Row( + children: [ + CompletionPercentage( + value: detailData['distribution']['labels'] + [index]['completed_issues'], + totalValue: detailData['distribution'] + ['labels'][index]['total_issues']) + ], + ) + ], + ), + ); + }), + ) + : const Align( + alignment: Alignment.center, + child: CustomText('No data found'), + ) + ], + ); + } + + Widget stateWidget({bool fromModule = false}) { + final modulesProvider = ref.watch(ProviderList.modulesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + return Container( + height: 45, + width: double.infinity, + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + children: [ + //icon + Icon( + //four squares icon + Icons.timelapse_rounded, + color: themeProvider.themeManager.placeholderTextColor), + const SizedBox(width: 15), + CustomText( + 'Progress', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.placeholderTextColor, + ), + Expanded(child: Container()), + + CompletionPercentage( + value: modulesProvider.moduleDetailsData['completed_issues'], + totalValue: modulesProvider.moduleDetailsData['total_issues'], + ) + ], + ), + ), + ); + } + + Widget membersWidget() { + final modulesProvider = ref.watch(ProviderList.modulesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + return GestureDetector( + onTap: () { + if (projectProvider.role != Role.admin && + projectProvider.role != Role.member) { + CustomToast.showToast(context, + message: accessRestrictedMSG, toastType: ToastType.failure); + return; + } + showModalBottomSheet( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.5, + ), + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + context: context, + builder: (context) => const AssigneeSheet( + fromModuleDetail: false, + ), + ); + }, + child: Container( + height: 45, + width: double.infinity, + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + children: [ + //icon + Icon( + //two people icon + Icons.people_alt_rounded, + color: themeProvider.themeManager.placeholderTextColor, + ), + const SizedBox(width: 15), + CustomText( + 'Members', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.placeholderTextColor, + ), + Expanded(child: Container()), + (modulesProvider.currentModule['members_detail'] == null || + modulesProvider.currentModule['members_detail'].isEmpty) + ? Row( + children: [ + CustomText( + 'No members', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.primaryTextColor, + ), + const SizedBox( + width: 5, + ), + Icon( + Icons.keyboard_arrow_down, + color: themeProvider.themeManager.primaryTextColor, + ), + ], + ) + : SizedBox( + height: 27, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.center, + child: SizedBox( + height: 30, + child: SquareAvatarWidget( + borderRadius: 50, + details: modulesProvider + .currentModule['members_detail']), + ), + ), + ) + ], + ), + ), + ), + ); + } + + String dateFormating(String date) { + final DateTime formatedDate = DateTime.parse(date); + final String finalDate = DateFormat('dd MMM').format(formatedDate); + return finalDate; + } + + String checkDate({required String startDate, required String endDate}) { + final DateTime now = DateTime.now(); + if ((startDate.isEmpty) || (endDate.isEmpty)) { + return 'Draft'; + } else { + if (DateTime.parse(startDate).isAfter(now)) { + final Duration difference = + DateTime.parse(startDate.split('+').first).difference(now); + if (difference.inDays == 0) { + return 'Today'; + } else { + return '${difference.inDays.abs() + 1} Days Left'; + } + } + if (DateTime.parse(startDate).isBefore(now) && + DateTime.parse(endDate).isAfter(now)) { + final Duration difference = DateTime.parse(endDate).difference(now); + if (difference.inDays == 0) { + return 'Today'; + } else { + return '${difference.inDays.abs() + 1} Days Left'; + } + } else { + return 'Completed'; + } + } + } + + String checkTimeDifferenc(String dateTime) { + final DateTime now = DateTime.now(); + final Duration difference = now.difference(DateTime.parse(dateTime)); + String? format; + + if (difference.inDays > 0) { + format = '${difference.inDays} days ago'; + } else if (difference.inHours > 0) { + format = '${difference.inHours} hours ago'; + } else if (difference.inMinutes > 0) { + format = '${difference.inMinutes} minutes ago'; + } else { + format = '${difference.inSeconds} seconds ago'; + } + + return format; + } +} diff --git a/lib/screens/project/modules/module-detail/module_issues_page.dart b/lib/screens/project/modules/module-detail/module_issues_page.dart new file mode 100644 index 00000000..d4d0d3f4 --- /dev/null +++ b/lib/screens/project/modules/module-detail/module_issues_page.dart @@ -0,0 +1,823 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:loading_indicator/loading_indicator.dart'; +import 'package:plane/bottom-sheets/filters/filter_sheet.dart'; +import 'package:plane/bottom-sheets/type_sheet.dart'; +import 'package:plane/bottom-sheets/views_sheet.dart'; +import 'package:plane/kanban/custom/board.dart'; +import 'package:plane/kanban/models/inputs.dart'; +import 'package:plane/models/chart_model.dart'; +import 'package:plane/models/issues.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issue-layouts/calender_view.dart'; +import 'package:plane/screens/project/issue-layouts/spreadsheet_view.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; +import 'package:plane/screens/project/modules/module-detail/module_detail_page.dart'; +import 'package:plane/utils/constants.dart'; +import 'package:plane/utils/custom_toast.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_app_bar.dart'; +import 'package:plane/widgets/custom_text.dart'; +import 'package:plane/widgets/empty.dart'; +import '../../../../../bottom-sheets/select_cycle_sheet.dart'; + +class ModuleDetail extends ConsumerStatefulWidget { + const ModuleDetail( + {super.key, this.moduleId, this.moduleName, this.projId, this.from}); + final String? moduleName; + final String? moduleId; + final String? projId; + final PreviousScreen? from; + + @override + ConsumerState createState() => _ModuleDetailState(); +} + +class _ModuleDetailState extends ConsumerState { + List chartData = []; + PageController? pageController = PageController(); + List tempIssuesList = []; + + DateTime? dueDate; + DateTime? startDate; + + @override + void initState() { + final issuesProvider = ref.read(ProviderList.issuesProvider); + tempIssuesList = issuesProvider.issuesList; + issuesProvider.tempProjectView = issuesProvider.issues.projectView; + issuesProvider.tempGroupBy = issuesProvider.issues.groupBY; + issuesProvider.tempOrderBy = issuesProvider.issues.orderBY; + issuesProvider.tempIssueType = issuesProvider.issues.issueType; + issuesProvider.tempFilters = issuesProvider.issues.filters; + + issuesProvider.issues.projectView = IssueLayout.kanban; + issuesProvider.issues.groupBY = GroupBY.state; + + issuesProvider.issues.orderBY = OrderBY.lastCreated; + issuesProvider.issues.issueType = IssueType.all; + issuesProvider.showEmptyStates = true; + issuesProvider.issues.filters = Filters( + assignees: [], + createdBy: [], + labels: [], + priorities: [], + states: [], + targetDate: [], + startDate: [], + stateGroup: [], + subscriber: [], + ); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + getModuleData(); + }); + super.initState(); + } + + Future getModuleData() async { + final modulesProvider = ref.read(ProviderList.modulesProvider); + final issuesProvider = ref.read(ProviderList.issuesProvider); + modulesProvider.moduleDetailState = StateEnum.loading; + pageController = PageController( + initialPage: modulesProvider.moduleDetailSelectedIndex, + keepPage: true, + viewportFraction: 1); + + await modulesProvider + .getModuleDetails( + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projId: widget.projId ?? + ref.read(ProviderList.projectProvider).currentProject['id'], + disableLoading: true, + moduleId: widget.moduleId!) + .then((value) => getChartData(modulesProvider + .moduleDetailsData['distribution']['completion_chart'])); + + ref + .read(ProviderList.issuesProvider) + .getIssueDisplayProperties(issueCategory: IssueCategory.cycleIssues); + await modulesProvider.getModuleView(moduleId: widget.moduleId!); + modulesProvider + .filterModuleIssues( + moduleID: widget.moduleId!, + slug: ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: widget.projId ?? + ref.read(ProviderList.projectProvider).currentProject['id'], + ref: ref) + .then((value) { + if (issuesProvider.issues.projectView == IssueLayout.list) { + modulesProvider.initializeBoard(); + } + }); + } + + Future getChartData(Map data) async { + data.forEach((key, value) { + chartData.add(ChartData( + DateTime.parse(key), value != null ? value.toDouble() : 0.0)); + }); + } + + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final issueProvider = ref.watch(ProviderList.issuesProvider); + final modulesProvider = ref.watch(ProviderList.modulesProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + return WillPopScope( + onWillPop: () async { + return true; + }, + child: Scaffold( + floatingActionButton: modulesProvider.moduleIssueState == + StateEnum.loading && + (modulesProvider.moduleDetailsData['backlog_issues'] != 0 && + modulesProvider.moduleDetailsData['started_issues'] != 0 && + modulesProvider.moduleDetailsData['unstarted_issues'] != 0) + ? FloatingActionButton( + backgroundColor: themeProvider.themeManager.primaryColour, + onPressed: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: height * 0.8, minHeight: 250), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return const SelectCycleSheet(); + }); + }, + child: Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryColour, + borderRadius: BorderRadius.circular(50), + ), + child: const Icon( + Icons.error_outline_outlined, + color: Colors.white, + ), + ), + ) + : null, + appBar: CustomAppBar( + centerTitle: true, + onPressed: () { + if (widget.from == PreviousScreen.myIssues) { + Navigator.pop(context); + return; + } + modulesProvider.changeTabIndex(0); + modulesProvider.changeState(StateEnum.empty); + Navigator.pop(context); + }, + text: widget.projId == null + ? projectProvider.currentProject['name'] + : projectProvider.projects.firstWhere( + (element) => element['id'] == widget.projId)['name'], + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 25, top: 20), + child: CustomText( + widget.moduleName!, + maxLines: 1, + type: FontStyle.H5, + fontWeight: FontWeightt.Semibold, + ), + ), + ), + ], + ), + SizedBox( + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + modulesProvider.changeTabIndex(0); + pageController!.animateToPage( + 0, + duration: const Duration(milliseconds: 100), + curve: Curves.easeIn, + ); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: CustomText( + overrride: true, + 'Issues', + type: FontStyle.Large, + fontWeight: FontWeightt.Medium, + color: + modulesProvider.moduleDetailSelectedIndex == 1 + ? themeProvider + .themeManager.placeholderTextColor + : themeProvider.themeManager.primaryColour, + ), + ), + Container( + height: 7, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: + modulesProvider.moduleDetailSelectedIndex == 0 + ? themeProvider.themeManager.primaryColour + : Colors.transparent, + ), + ), + ], + ), + )), + Expanded( + child: InkWell( + onTap: () { + modulesProvider.changeTabIndex(1); + pageController!.animateToPage( + 1, + duration: const Duration(milliseconds: 100), + curve: Curves.easeIn, + ); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: CustomText( + 'Details', + overrride: true, + type: FontStyle.Large, + fontWeight: FontWeightt.Medium, + color: + modulesProvider.moduleDetailSelectedIndex == 0 + ? themeProvider + .themeManager.placeholderTextColor + : themeProvider.themeManager.primaryColour, + ), + ), + Container( + height: 7, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: + modulesProvider.moduleDetailSelectedIndex == 1 + ? themeProvider.themeManager.primaryColour + : Colors.transparent, + ), + ), + ], + ), + )), + ], + ), + ), + Container( + height: 2, + width: MediaQuery.of(context).size.width, + color: themeProvider.themeManager.borderSubtle01Color, + ), + Expanded( + child: PageView( + controller: pageController, + onPageChanged: (value) { + if (value == 0) { + modulesProvider.changeTabIndex(0); + } else { + modulesProvider.changeTabIndex(1); + } + }, + children: [ + Column( + children: [ + Expanded( + child: + (modulesProvider.moduleIssueState == + StateEnum.loading || + modulesProvider.moduleDetailState == + StateEnum.loading) || + modulesProvider.moduleViewState == + StateEnum.loading + ? Center( + child: SizedBox( + width: 30, + height: 30, + child: LoadingIndicator( + indicatorType: + Indicator.lineSpinFadeLoader, + colors: [ + themeProvider + .themeManager.primaryTextColor + ], + strokeWidth: 1.0, + backgroundColor: themeProvider + .themeManager + .primaryBackgroundDefaultColor, + )), + ) + : (modulesProvider.isIssuesEmpty) + ? EmptyPlaceholder.emptyIssues(context, + moduleId: widget.moduleId, + projectId: widget.projId, + type: IssueCategory.cycleIssues, + ref: ref) + : (issueProvider.issues.projectView == + IssueLayout.list) + ? Container( + color: themeProvider.themeManager + .secondaryBackgroundDefaultColor, + margin: + const EdgeInsets.only(top: 5), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: modulesProvider + .issues.issues + .map((state) => state + .items + .isEmpty && + !modulesProvider + .showEmptyStates + ? Container() + : SizedBox( + child: Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Container( + padding: const EdgeInsets + .only( + left: + 15), + margin: const EdgeInsets + .only( + bottom: + 10), + child: Row( + children: [ + state.leading ?? + Container(), + Container( + padding: + const EdgeInsets.only( + left: + 10, + ), + width: + MediaQuery.of(context).size.width * 0.6, + child: + CustomText( + state.title!, + overflow: + TextOverflow.ellipsis, + maxLines: + 1, + type: + FontStyle.Small, + fontWeight: + FontWeightt.Medium, + ), + ), + Container( + alignment: + Alignment.center, + margin: + const EdgeInsets.only( + left: + 15, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + color: themeProvider.themeManager.secondaryBackgroundDefaultColor), + height: + 25, + width: + 30, + child: + CustomText( + state.items.length.toString(), + type: + FontStyle.Medium, + ), + ), + const Spacer(), + IconButton( + onPressed: + () { + if (issueProvider.issues.groupBY == GroupBY.state) { + issueProvider.createIssuedata['state'] = state.id; + } else { + issueProvider.createIssuedata['priority'] = 'de3c90cd-25cd-42ec-ac6c-a66caf8029bc'; + } + Navigator.of(context).push(MaterialPageRoute( + builder: (ctx) => CreateIssue( + moduleId: widget.moduleId, + ))); + }, + icon: + Icon( + Icons.add, + color: themeProvider.themeManager.primaryColour, + )), + const SizedBox( + width: + 10, + ), + ], + ), + ), + Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: state + .items + .map((e) => + e) + .toList()), + state.items + .isEmpty + ? Container( + margin: const EdgeInsets + .only( + bottom: 10), + width: MediaQuery.of(context) + .size + .width, + color: themeProvider + .themeManager + .primaryBackgroundDefaultColor, + padding: const EdgeInsets + .only( + top: 15, + bottom: 15, + left: 15), + child: + const CustomText( + 'No issues.', + type: + FontStyle.Small, + maxLines: + 10, + textAlign: + TextAlign.start, + ), + ) + : Container( + margin: const EdgeInsets + .only( + bottom: 10), + ) + ], + ), + )) + .toList()), + ), + ) + : issueProvider.issues.projectView == + IssueLayout.kanban + ? Padding( + padding: const EdgeInsets.only( + left: 8), + child: KanbanBoard( + modulesProvider + .initializeBoard(), + boardID: 'cycle-board', + onItemReorder: ( + {newCardIndex, + newListIndex, + oldCardIndex, + oldListIndex}) { + modulesProvider + .reorderIssue( + newCardIndex: + newCardIndex!, + newListIndex: + newListIndex!, + oldCardIndex: + oldCardIndex!, + oldListIndex: + oldListIndex!, + ) + .catchError( + (err) { + log(err.toString()); + CustomToast.showToast( + context, + message: + 'Failed to update issue', + toastType: + ToastType.failure, + ); + }, + ); + }, + isCardsDraggable: issueProvider + .checkIsCardsDaraggable(), + groupEmptyStates: + !modulesProvider + .showEmptyStates, + cardPlaceHolderColor: + themeProvider.themeManager + .primaryBackgroundDefaultColor, + cardPlaceHolderDecoration: + BoxDecoration( + color: themeProvider + .themeManager + .primaryBackgroundDefaultColor, + boxShadow: [ + BoxShadow( + blurRadius: 2, + color: themeProvider + .themeManager + .borderSubtle01Color, + spreadRadius: 0, + ) + ], + ), + backgroundColor: themeProvider + .themeManager + .secondaryBackgroundDefaultColor, + listScrollConfig: + ScrollConfig( + offset: 65, + duration: + const Duration( + milliseconds: 100, + ), + curve: Curves.linear), + listTransitionDuration: + const Duration( + milliseconds: 200), + cardTransitionDuration: + const Duration( + milliseconds: 400), + textStyle: TextStyle( + fontSize: 19, + height: 1.3, + color: + Colors.grey.shade800, + fontWeight: + FontWeight.w500), + ), + ) + : issueProvider + .issues.projectView == + IssueLayout.calendar + ? const CalendarView() + : const SpreadSheetView( + issueCategory: IssueCategory + .cycleIssues, + ), + ), + SafeArea( + child: Container( + height: 50, + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: themeProvider + .themeManager.primaryBackgroundDefaultColor, + boxShadow: themeProvider + .themeManager.shadowBottomControlButtons), + child: Row( + children: [ + projectProvider.role == Role.admin || + projectProvider.role == Role.member + ? Expanded( + child: GestureDetector( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CreateIssue( + projectId: widget.projId ?? + projectProvider + .currentProject['id'], + fromMyIssues: true, + moduleId: widget.moduleId, + ), + ), + ); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.add, + color: themeProvider + .themeManager + .primaryTextColor, + size: 20, + ), + const CustomText( + ' Issue', + type: FontStyle.Medium, + ) + ], + ), + ), + ), + ) + : Container(), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + Expanded( + child: GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context) + .size + .height * + 0.85), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return const TypeSheet( + issueCategory: + IssueCategory.moduleIssues, + ); + }); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.menu, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Layout', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + issueProvider.issues.projectView == + IssueLayout.calendar + ? Container() + : Expanded( + child: GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context) + .size + .height * + 0.9), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return ViewsSheet( + projectView: issueProvider + .issues.projectView, + issueCategory: + IssueCategory.moduleIssues, + moduleId: widget.moduleId, + ); + }); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.wysiwyg_outlined, + color: themeProvider.themeManager + .primaryTextColor, + size: 19, + ), + const CustomText( + ' Display', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + Expanded( + child: GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context) + .size + .height * + 0.85), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return FilterSheet( + issueCategory: + IssueCategory.moduleIssues, + cycleOrModuleId: widget.moduleId, + ); + }); + }, + child: SizedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.filter_list_outlined, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Filters', + type: FontStyle.Medium, + ) + ], + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ModuleDetailsPage( + moduleId: widget.moduleId!, + chartData: chartData, + ) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/module_screen.dart b/lib/screens/project/modules/module_screen.dart similarity index 97% rename from lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/module_screen.dart rename to lib/screens/project/modules/module_screen.dart index 9acb4072..8446c5bf 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/module_screen.dart +++ b/lib/screens/project/modules/module_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ModulesTab/simple_module_card.dart'; +import 'package:plane/screens/project/modules/widgets/simple_module_card.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/empty.dart'; import 'package:plane/widgets/loading_widget.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/module_card.dart b/lib/screens/project/modules/widgets/module_card.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/module_card.dart rename to lib/screens/project/modules/widgets/module_card.dart index a202f97f..8edfe04c 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/module_card.dart +++ b/lib/screens/project/modules/widgets/module_card.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:plane/bottom_sheets/delete_module_sheet.dart'; - +import 'package:plane/bottom-sheets/delete_module_sheet.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart'; +import 'package:plane/screens/project/modules/module-detail/module_issues_page.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/utils/constants.dart'; import '/utils/enums.dart'; @@ -37,14 +36,13 @@ class _ModuleCardState extends ConsumerState { Navigator.push( context, MaterialPageRoute( - builder: (context) => CycleDetail( + builder: (context) => ModuleDetail( moduleId: widget.isFav ? modulesProvider.favModules[widget.index]['id'] : modulesProvider.modules[widget.index]['id'], moduleName: widget.isFav ? modulesProvider.favModules[widget.index]['name'] : modulesProvider.modules[widget.index]['name'], - fromModule: true, ), ), ); diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/simple_module_card.dart b/lib/screens/project/modules/widgets/simple_module_card.dart similarity index 98% rename from lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/simple_module_card.dart rename to lib/screens/project/modules/widgets/simple_module_card.dart index d971510a..918f3db0 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/ModulesTab/simple_module_card.dart +++ b/lib/screens/project/modules/widgets/simple_module_card.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:lucide_icons/lucide_icons.dart'; -import 'package:plane/bottom_sheets/delete_cycle_sheet.dart'; +import 'package:plane/bottom-sheets/delete_cycle_sheet.dart'; +import 'package:plane/screens/project/modules/module-detail/module_issues_page.dart'; import 'package:plane/utils/string_manager.dart'; import '/utils/enums.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/widgets/custom_text.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/cycle_detail.dart'; class SimpleModuleCard extends ConsumerStatefulWidget { const SimpleModuleCard({ @@ -36,14 +36,13 @@ class _SimpleModuleCardState extends ConsumerState { Navigator.push( context, MaterialPageRoute( - builder: (context) => CycleDetail( + builder: (context) => ModuleDetail( moduleId: widget.isFav ? modulesProvider.favModules[widget.index]['id'] : modulesProvider.modules[widget.index]['id'], moduleName: widget.isFav ? modulesProvider.favModules[widget.index]['name'] : modulesProvider.modules[widget.index]['name'], - fromModule: true, ), ), ); diff --git a/lib/screens/MainScreens/Projects/create_page_screen.dart b/lib/screens/project/pages/create_page_screen.dart similarity index 100% rename from lib/screens/MainScreens/Projects/create_page_screen.dart rename to lib/screens/project/pages/create_page_screen.dart diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_detail.dart b/lib/screens/project/pages/page_detail.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_detail.dart rename to lib/screens/project/pages/page_detail.dart index 476feb88..9d210c53 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_detail.dart +++ b/lib/screens/project/pages/page_detail.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/block_sheet.dart'; -import 'package:plane/bottom_sheets/label_sheet.dart'; +import 'package:plane/bottom-sheets/block_sheet.dart'; +import 'package:plane/bottom-sheets/label_sheet.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/extensions/string_extensions.dart'; @@ -56,12 +56,7 @@ class _PageDetailState extends ConsumerState { .workspaceSlug, projectId: ref.read(ProviderList.projectProvider).currentProject["id"], ref: ref); - ref.read(ProviderList.issuesProvider).getLabels( - slug: ref - .read(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref.read(ProviderList.projectProvider).currentProject["id"]); + ref.read(ProviderList.labelProvider.notifier).getProjectLabels(); for (final element in (prov.pages[prov.selectedFilter]![widget.index] ['label_details'] as List)) { prov.selectedLabels.add(element['id']); diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_screen.dart b/lib/screens/project/pages/page_screen.dart similarity index 94% rename from lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_screen.dart rename to lib/screens/project/pages/page_screen.dart index 382c4f5e..12634463 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_screen.dart +++ b/lib/screens/project/pages/page_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_card.dart'; +import 'package:plane/screens/project/pages/widgets/page_card.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/loading_widget.dart'; import 'package:plane/widgets/empty.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_block_card.dart b/lib/screens/project/pages/widgets/page_block_card.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_block_card.dart rename to lib/screens/project/pages/widgets/page_block_card.dart index dca3a02d..2828460f 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_block_card.dart +++ b/lib/screens/project/pages/widgets/page_block_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:plane/bottom_sheets/block_sheet.dart'; +import 'package:plane/bottom-sheets/block_sheet.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_card.dart b/lib/screens/project/pages/widgets/page_card.dart similarity index 98% rename from lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_card.dart rename to lib/screens/project/pages/widgets/page_card.dart index 2f171f25..901ce059 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_card.dart +++ b/lib/screens/project/pages/widgets/page_card.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:plane/bottom_sheets/delete_cycle_sheet.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/PagesTab/page_detail.dart'; +import 'package:plane/bottom-sheets/delete_cycle_sheet.dart'; import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/pages/page_detail.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; diff --git a/lib/screens/project/project_detail.dart b/lib/screens/project/project_detail.dart new file mode 100644 index 00000000..a6629047 --- /dev/null +++ b/lib/screens/project/project_detail.dart @@ -0,0 +1,106 @@ +// ignore_for_file: non_constant_identifier_names +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/cycles/project_details_cycles.dart'; +import 'package:plane/screens/project/views/views.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/empty.dart'; +import 'widgets/floating_action_button.dart'; +import 'widgets/project_detail_appbar.dart'; +import 'widgets/project_detail_root.dart'; + +class ProjectDetail extends ConsumerStatefulWidget { + const ProjectDetail({super.key, required this.index}); + final int index; + + @override + ConsumerState createState() => _ProjectDetailState(); +} + +class _ProjectDetailState extends ConsumerState { + final controller = PageController(initialPage: 0); + int selectedTab = 0; + + void onTabChange(int index) { + setState(() { + selectedTab = index; + }); + } + + void settingsOntap() { + final projectProvider = ref.read(ProviderList.projectProvider); + int count = 0; + for (int i = 0; i < projectProvider.features.length; i++) { + if (projectProvider.features[i]['show']) { + count++; + } + } + if (count < selectedTab + 1) { + setState(() { + selectedTab = count - 1; + }); + } + } + + @override + void initState() { + final issueProvider = ref.read(ProviderList.issuesProvider); + final statesProvider = ref.read(ProviderList.statesProvider); + issueProvider.orderByState = StateEnum.loading; + statesProvider.statesState = StateEnum.restricted; + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + issueProvider.setsState(); + statesProvider.statesState = StateEnum.restricted; + ref.read(ProviderList.projectProvider).initializeProject(ref: ref); + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final projectProvider = ref.watch(ProviderList.projectProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + + return Scaffold( + appBar: ProjectDetailAppbar( + context, + ref: ref, + settingsOntap: settingsOntap, + ), + floatingActionButton: + FLActionButton(context, ref: ref, selected: selectedTab), + body: SafeArea( + child: statesProvider.statesState == StateEnum.restricted + ? EmptyPlaceholder.joinProject( + context, + ref, + projectProvider.currentProject['id'], + ref + .read(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug) + : ProjectDetailRoot( + onTabChange: onTabChange, + selectedTab: selectedTab, + )), + ); + } +} + +Widget cycles() { + return const CycleWidget(); +} + +Widget view(WidgetRef ref) { + final themeProvider = ref.read(ProviderList.themeProvider); + return Container( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + padding: const EdgeInsets.only(left: 20, right: 20, top: 15), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [Views()], + ), + ); +} diff --git a/lib/screens/MainScreens/Projects/project_screen.dart b/lib/screens/project/project_screen.dart similarity index 98% rename from lib/screens/MainScreens/Projects/project_screen.dart rename to lib/screens/project/project_screen.dart index fd633468..72ad6aa5 100644 --- a/lib/screens/MainScreens/Projects/project_screen.dart +++ b/lib/screens/project/project_screen.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; -import 'package:plane/bottom_sheets/global_search_sheet.dart'; +import 'package:plane/bottom-sheets/global_search_sheet.dart'; import 'package:plane/provider/projects_provider.dart'; import 'package:plane/provider/theme_provider.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/project_detail.dart'; -import 'package:plane/screens/MainScreens/Projects/create_project_screen.dart'; +import 'package:plane/screens/project/create_project_screen.dart'; +import 'package:plane/screens/project/project_detail.dart'; import 'package:plane/utils/color_manager.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; @@ -141,6 +141,8 @@ class _ProjectScreenState extends ConsumerState .themeManager.placeholderTextColor, labelColor: themeProvider.themeManager.primaryColour, + dividerColor: themeProvider + .themeManager.borderSubtle01Color, tabs: const [ Tab( text: 'Projects', @@ -152,12 +154,12 @@ class _ProjectScreenState extends ConsumerState text: 'Unjoined', ), ]), - Container( - height: 1, - width: double.infinity, - color: - themeProvider.themeManager.borderSubtle01Color, - ), + // Container( + // height: 1, + // width: double.infinity, + // color: + // Colors.amber + // ), Expanded( child: Padding( padding: diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/automations_page.dart b/lib/screens/project/settings/automations_page.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/automations_page.dart rename to lib/screens/project/settings/automations_page.dart index 92338021..d1f3f61a 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/automations_page.dart +++ b/lib/screens/project/settings/automations_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:plane/bottom_sheets/select_automation_state.dart'; -import 'package:plane/bottom_sheets/select_month.dart'; +import 'package:plane/bottom-sheets/select_automation_state.dart'; +import 'package:plane/bottom-sheets/select_month.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/control_page.dart b/lib/screens/project/settings/control_page.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/control_page.dart rename to lib/screens/project/settings/control_page.dart index 8b7f5287..3841f79e 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/control_page.dart +++ b/lib/screens/project/settings/control_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/project_lead_assignee_sheet.dart'; +import 'package:plane/bottom-sheets/project_lead_assignee_sheet.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/widgets/custom_text.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/create_label.dart b/lib/screens/project/settings/create_label.dart similarity index 93% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/create_label.dart rename to lib/screens/project/settings/create_label.dart index 40fcb6d4..cb5e8721 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/create_label.dart +++ b/lib/screens/project/settings/create_label.dart @@ -1,5 +1,4 @@ import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/utils/custom_toast.dart'; @@ -45,7 +44,7 @@ class _CreateLabelState extends ConsumerState { @override Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); - final issuesProvider = ref.read(ProviderList.issuesProvider); + final labelNotifier = ref.read(ProviderList.labelProvider.notifier); return GestureDetector( onTap: () { setState(() { @@ -210,21 +209,18 @@ class _CreateLabelState extends ConsumerState { message: "Color is not valid", toastType: ToastType.failure); } else { - issuesProvider.issueLabels( - slug: ref - .watch(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .watch(ProviderList.projectProvider) - .currentProject['id'], - method: widget.method, - data: { - "name": lableController.text, - "color": '#${colorController.text}', - }, - labelId: widget.labelId, - ref: ref); + if (widget.method == CRUD.update) { + labelNotifier.updateLabel({ + "id": widget.labelId, + "name": lableController.text, + "color": '#${colorController.text}', + }); + } else if (widget.method == CRUD.create) { + labelNotifier.createLabel({ + "name": lableController.text, + "color": '#${colorController.text}', + }); + } Navigator.of(context).pop(); } }, diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/estimates_page.dart b/lib/screens/project/settings/estimates_page.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/estimates_page.dart rename to lib/screens/project/settings/estimates_page.dart index afb88313..d4768cbf 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/estimates_page.dart +++ b/lib/screens/project/settings/estimates_page.dart @@ -3,8 +3,8 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:plane/bottom_sheets/create_estimate.dart'; -import 'package:plane/bottom_sheets/delete_estimate_sheet.dart'; +import 'package:plane/bottom-sheets/create_estimate.dart'; +import 'package:plane/bottom-sheets/delete_estimate_sheet.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/widgets/loading_widget.dart'; @@ -21,8 +21,6 @@ class EstimatsPage extends ConsumerStatefulWidget { class _EstimatsPageState extends ConsumerState { @override void initState() { - log(ref.read(ProviderList.projectProvider).currentProject.toString()); - super.initState(); } diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/features_page.dart b/lib/screens/project/settings/features_page.dart similarity index 96% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/features_page.dart rename to lib/screens/project/settings/features_page.dart index 508c3e4a..8b487d51 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/features_page.dart +++ b/lib/screens/project/settings/features_page.dart @@ -54,6 +54,7 @@ class _FeaturesPageState extends ConsumerState { Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); final projectsProvider = ref.watch(ProviderList.projectProvider); + final profileProvider = ref.watch(ProviderList.profileProvider); return Container( color: themeProvider.themeManager.primaryBackgroundDefaultColor, child: ListView.builder( @@ -152,10 +153,10 @@ class _FeaturesPageState extends ConsumerState { : 'OFF'; postHogService( - eventName: prefix + suffix, - properties: {}, - ref: ref, - ); + eventName: prefix + suffix, + properties: {}, + userEmail: profileProvider.userProfile.email!, + userID: profileProvider.userProfile.id!); } : () { CustomToast.showToast(context, diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/general_page.dart b/lib/screens/project/settings/general_page.dart similarity index 99% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/general_page.dart rename to lib/screens/project/settings/general_page.dart index e350e729..94ba7d4a 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/general_page.dart +++ b/lib/screens/project/settings/general_page.dart @@ -4,9 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/delete_leave_project_sheet.dart'; -import 'package:plane/bottom_sheets/emoji_sheet.dart'; -import 'package:plane/bottom_sheets/project_select_cover_image.dart'; +import 'package:plane/bottom-sheets/delete_leave_project_sheet.dart'; +import 'package:plane/bottom-sheets/emoji_sheet.dart'; +import 'package:plane/bottom-sheets/project_select_cover_image.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/integrations_page.dart b/lib/screens/project/settings/integrations_page.dart similarity index 100% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/integrations_page.dart rename to lib/screens/project/settings/integrations_page.dart diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/lables_page.dart b/lib/screens/project/settings/lables_page.dart similarity index 86% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/lables_page.dart rename to lib/screens/project/settings/lables_page.dart index e2a99fff..07bae03b 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/lables_page.dart +++ b/lib/screens/project/settings/lables_page.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/delete_labels_sheet.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/create_label.dart'; +import 'package:plane/bottom-sheets/delete_labels_sheet.dart'; +import 'package:plane/screens/project/settings/create_label.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/extensions/string_extensions.dart'; @@ -22,9 +22,9 @@ class LablesPage extends ConsumerStatefulWidget { class _LablesPageState extends ConsumerState { List expanded = []; bool isChildAvailable(String id) { - final issuesProv = ref.read(ProviderList.issuesProvider); - for (final element in issuesProv.labels) { - if (element["parent"] == id) return true; + final labelProvider = ref.read(ProviderList.labelProvider); + for (final label in labelProvider.projectLabels.values) { + if (label.parent == id) return true; } return false; } @@ -32,21 +32,24 @@ class _LablesPageState extends ConsumerState { @override Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); + final labelProvider = ref.watch(ProviderList.labelProvider); + final labelNotifier = ref.read(ProviderList.labelProvider.notifier); + return LoadingWidget( - loading: issuesProvider.labelState == StateEnum.loading, + loading: labelProvider.labelState == StateEnum.loading, widgetClass: Container( color: themeProvider.themeManager.primaryBackgroundDefaultColor, - child: issuesProvider.labels.isEmpty + child: labelProvider.projectLabels.isEmpty ? EmptyPlaceholder.emptyLabels(context, ref) : ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), - itemCount: issuesProvider.labels.length, + itemCount: labelProvider.projectLabels.length, itemBuilder: (context, index) { - final isChildAvail = - isChildAvailable(issuesProvider.labels[index]["id"]); - return issuesProvider.labels[index]["parent"] == null + final label = + labelProvider.projectLabels.values.elementAt(index); + final isChildAvail = isChildAvailable(label.id); + return label.parent == null ? Container( margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( @@ -71,18 +74,13 @@ class _LablesPageState extends ConsumerState { !isChildAvail ? CircleAvatar( radius: 6, - backgroundColor: issuesProvider - .labels[index]['color'] - .toString() - .toColor(), + backgroundColor: + label.color.toColor(), ) : SvgPicture.asset( "assets/svg_images/label_group.svg", colorFilter: ColorFilter.mode( - issuesProvider.labels[index] - ['color'] - .toString() - .toColor(), + label.color.toColor(), BlendMode.srcIn), ), const SizedBox( @@ -94,8 +92,7 @@ class _LablesPageState extends ConsumerState { .width * 0.7, child: CustomText( - issuesProvider.labels[index] - ['name'], + label.name, type: FontStyle.H5, maxLines: 1, color: themeProvider @@ -145,14 +142,9 @@ class _LablesPageState extends ConsumerState { .viewInsets .bottom), child: CreateLabel( - label: issuesProvider - .labels[index] - ['name'], - labelColor: issuesProvider - .labels[index] - ['color'], - labelId: issuesProvider - .labels[index]['id'], + label: label.name, + labelColor: label.color, + labelId: label.id, method: CRUD.update, ), ); @@ -182,8 +174,7 @@ class _LablesPageState extends ConsumerState { context: context, builder: (context) { return SingleLabelSelect( - labelID: issuesProvider - .labels[index]["id"], + labelID: label.id, ); }, ); @@ -212,11 +203,8 @@ class _LablesPageState extends ConsumerState { .viewInsets .bottom), child: DeleteLabelSheet( - labelName: issuesProvider - .labels[index] - ['name'], - labelId: issuesProvider - .labels[index]['id'], + labelName: label.name, + labelId: label.id, ), ); }, @@ -347,11 +335,9 @@ class _LablesPageState extends ConsumerState { ), Column( children: expanded.contains(index) - ? issuesProvider.labels.map( - (e) { - return e["parent"] == - issuesProvider.labels[index] - ["id"] + ? labelProvider.projectLabels.values.map( + (childLabel) { + return childLabel.parent == label.id ? Container( margin: const EdgeInsets.only( @@ -383,16 +369,16 @@ class _LablesPageState extends ConsumerState { children: [ CircleAvatar( radius: 6, - backgroundColor: e[ - 'color'] - .toString() - .toColor(), + backgroundColor: + childLabel + .color + .toColor(), ), const SizedBox( width: 10, ), CustomText( - e['name'], + childLabel.name, type: FontStyle .Medium, maxLines: 3, @@ -448,12 +434,14 @@ class _LablesPageState extends ConsumerState { .bottom), child: CreateLabel( - label: e[ - 'name'], + label: childLabel + .name, labelColor: - e['color'], - labelId: e[ - 'id'], + childLabel + .color, + labelId: + childLabel + .id, method: CRUD .update, ), @@ -462,27 +450,10 @@ class _LablesPageState extends ConsumerState { ); } else if (val == 'CONVERT') { - issuesProvider - .issueLabels( - slug: ref - .watch(ProviderList - .workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .watch(ProviderList - .projectProvider) - .currentProject[ - 'id'], - method: CRUD - .update, - data: { - "parent": - null, - }, - labelId: e[ - "id"], - ref: ref); + labelNotifier + .updateLabel({ + "parent": null, + }); } else { showModalBottomSheet( constraints: @@ -516,9 +487,11 @@ class _LablesPageState extends ConsumerState { child: DeleteLabelSheet( labelName: - e['name'], - labelId: e[ - 'id'], + childLabel + .name, + labelId: + childLabel + .id, ), ); }, @@ -627,34 +600,36 @@ class SingleLabelSelect extends ConsumerStatefulWidget { class _SingleLabelSelectState extends ConsumerState { double height = 0.0; bool isChildAvailable(String id) { - final issuesProv = ref.read(ProviderList.issuesProvider); - for (final element in issuesProv.labels) { - if (element["parent"] == id) return true; + final labelProv = ref.read(ProviderList.labelProvider); + for (final label in labelProv.projectLabels.values) { + if (label.parent == id) return true; } return false; } bool isLabelsAvailable({int index = 0, bool iterate = false}) { - final issuesProvider = ref.read(ProviderList.issuesProvider); + final labelProv = ref.read(ProviderList.labelProvider); if (iterate) { - for (final element in issuesProvider.labels) { - if (!(element["id"] == widget.labelID || - element["parent"] == widget.labelID || - element["parent"] != null || - isChildAvailable(element["id"]))) return false; + for (final label in labelProv.projectLabels.values) { + if (!(label.id == widget.labelID || + label.parent == widget.labelID || + label.parent != null || + isChildAvailable(label.id))) return false; } return true; } - return issuesProvider.labels[index]["id"] == widget.labelID || - issuesProvider.labels[index]["parent"] == widget.labelID || - issuesProvider.labels[index]["parent"] != null || - isChildAvailable(issuesProvider.labels[index]["id"]); + final label = labelProv.projectLabels.values.elementAt(index); + return label.id == widget.labelID || + label.parent == widget.labelID || + label.parent != null || + isChildAvailable(label.id); } @override Widget build(BuildContext context) { - final issuesProvider = ref.watch(ProviderList.issuesProvider); final themeProvider = ref.watch(ProviderList.themeProvider); + final labelProvider = ref.read(ProviderList.labelProvider); + final labelNotifier = ref.read(ProviderList.labelProvider.notifier); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final box = context.findRenderObject() as RenderBox; height = box.size.height; @@ -695,29 +670,20 @@ class _SingleLabelSelectState extends ConsumerState { isLabelsAvailable(iterate: true) ? EmptyPlaceholder.emptyLabels(context, ref) : ListView.builder( - itemCount: issuesProvider.labels.length, + itemCount: labelProvider.projectLabels.length, shrinkWrap: true, itemBuilder: (context, index) { + final label = + labelProvider.projectLabels.values.elementAt( + index, + ); return isLabelsAvailable(index: index) ? Container() : InkWell( onTap: () { - issuesProvider.issueLabels( - slug: ref - .watch( - ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .watch(ProviderList.projectProvider) - .currentProject['id'], - method: CRUD.update, - data: { - "parent": widget.labelID, - }, - labelId: issuesProvider.labels[index] - ["id"], - ref: ref); + labelNotifier.updateLabel({ + "parent": null, + }); Navigator.pop(context); }, @@ -731,16 +697,12 @@ class _SingleLabelSelectState extends ConsumerState { children: [ CircleAvatar( radius: 8, - backgroundColor: issuesProvider - .labels[index]['color'] - .toString() - .toColor(), + backgroundColor: + label.color.toColor(), ), Container(width: 10), CustomText( - issuesProvider.labels[index] - ['name'] - .toString(), + label.name, type: FontStyle.Small, ), const Spacer(), @@ -761,7 +723,7 @@ class _SingleLabelSelectState extends ConsumerState { ], ), ), - issuesProvider.labelState == StateEnum.loading + labelProvider.labelState == StateEnum.loading ? Container( height: height - 32, alignment: Alignment.center, diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/states_pages.dart b/lib/screens/project/settings/states_pages.dart similarity index 81% rename from lib/screens/MainScreens/Projects/ProjectDetail/Settings/states_pages.dart rename to lib/screens/project/settings/states_pages.dart index 2f71bf74..613c5aaa 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/Settings/states_pages.dart +++ b/lib/screens/project/settings/states_pages.dart @@ -1,24 +1,18 @@ // ignore_for_file: use_build_context_synchronously - import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:loading_indicator/loading_indicator.dart'; -import 'package:plane/bottom_sheets/delete_state_sheet.dart'; -import 'package:plane/provider/issues_provider.dart'; +import 'package:plane/bottom-sheets/delete_state_sheet.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/extensions/string_extensions.dart'; import 'package:plane/widgets/custom_button.dart'; - import 'package:plane/widgets/custom_text.dart'; -List states = ['backlog', 'unstarted', 'started', 'completed', 'cancelled']; - class StatesPage extends ConsumerStatefulWidget { const StatesPage({super.key}); @@ -35,12 +29,13 @@ class _StatesPageState extends ConsumerState { @override Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); + // final issuesProvider = ref.watch(ProviderList.issuesProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); return Container( color: themeProvider.themeManager.primaryBackgroundDefaultColor, child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16), - itemCount: states.length, + itemCount: statesProvider.stateGroups.length, itemBuilder: (context, index) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -49,8 +44,9 @@ class _StatesPageState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CustomText( - states[index].toString().replaceFirst(states[index][0], - states[index][0].toString().toUpperCase()), + statesProvider.stateGroups.keys + .toList()[index] + .fistLetterToUpper(), // values['group'].replaceFirst(values['group'][0], values['group'][0].toUpperCase()), type: FontStyle.H5, fontWeight: FontWeightt.Medium, @@ -76,9 +72,9 @@ class _StatesPageState extends ConsumerState { ), child: AddUpdateState( groupIndex: index, - stateKey: states[index].toString().replaceFirst( - states[index][0], - states[index][0].toString().toUpperCase()), + stateKey: statesProvider.stateGroups.keys + .toList()[index] + .fistLetterToUpper(), method: CRUD.create, stateId: '', name: '', @@ -95,14 +91,15 @@ class _StatesPageState extends ConsumerState { ) ], ), - issuesProvider.statesData[states[index]] == null + statesProvider.stateGroups.values.toList()[index].isEmpty ? const SizedBox.shrink() : ListView.builder( padding: EdgeInsets.zero, primary: false, shrinkWrap: true, - itemCount: - issuesProvider.statesData[states[index]].length, + itemCount: statesProvider.stateGroups.values + .toList()[index] + .length, itemBuilder: (context, idx) { return Container( margin: const EdgeInsets.only(bottom: 16), @@ -122,18 +119,26 @@ class _StatesPageState extends ConsumerState { Row( children: [ SvgPicture.asset( - states[index] == 'backlog' + statesProvider.stateGroups.keys + .toList()[index] == + 'backlog' ? 'assets/svg_images/circle.svg' - : states[index] == 'cancelled' + : statesProvider.stateGroups.keys + .toList()[index] == + 'cancelled' ? 'assets/svg_images/cancelled.svg' - : states[index] == 'completed' + : statesProvider.stateGroups.keys + .toList()[index] == + 'completed' ? 'assets/svg_images/done.svg' - : states[index] == 'started' + : statesProvider + .stateGroups.keys + .toList()[index] == + 'started' ? 'assets/svg_images/in_progress.svg' : 'assets/svg_images/circle.svg', colorFilter: ColorFilter.mode( - getColorFromIssueProvider( - issuesProvider, index, idx), + getColorFromIssueProvider(index, idx), BlendMode.srcIn), height: 20, width: 20, @@ -144,8 +149,10 @@ class _StatesPageState extends ConsumerState { SizedBox( width: width * 0.6, child: CustomText( - issuesProvider.statesData[states[index]] - [idx]['name'], + statesProvider.stateGroups.values + .toList()[index][idx] + .name + .toString(), overflow: TextOverflow.ellipsis, maxLines: 1, type: FontStyle.Medium, @@ -177,19 +184,25 @@ class _StatesPageState extends ConsumerState { .bottom), child: AddUpdateState( groupIndex: index, - stateKey: issuesProvider - .statesData[states[index]] - [idx]["group"], + stateKey: statesProvider + .stateGroups.keys + .toList()[index], method: CRUD.update, - stateId: issuesProvider - .statesData[states[index]] - [idx]['id'], - name: issuesProvider - .statesData[states[index]] - [idx]['name'], - color: issuesProvider - .statesData[states[index]] - [idx]['color'], + stateId: statesProvider + .stateGroups.values + .toList()[index][idx] + .id + .toString(), + name: statesProvider + .stateGroups.values + .toList()[index][idx] + .name + .toString(), + color: statesProvider + .stateGroups.values + .toList()[index][idx] + .color + .toString(), ), ); }, @@ -203,9 +216,9 @@ class _StatesPageState extends ConsumerState { ), IconButton( onPressed: () { - if (issuesProvider - .statesData[states[index]] - [idx]['default'] == + if (statesProvider.stateGroups.values + .toList()[index][idx] + .is_default == true) { CustomToast.showToast( context, @@ -213,8 +226,9 @@ class _StatesPageState extends ConsumerState { 'Cannot delete the default state', toastType: ToastType.failure, ); - } else if (issuesProvider - .statesData[states[index]] + } else if (statesProvider + .stateGroups.values + .toList()[index] .length == 1) { CustomToast.showToast( @@ -235,12 +249,16 @@ class _StatesPageState extends ConsumerState { context: context, builder: (context) { return DeleteStateSheet( - stateName: issuesProvider - .statesData[states[index]] - [idx]['name'], - stateId: issuesProvider - .statesData[states[index]] - [idx]['id'], + stateName: statesProvider + .stateGroups.values + .toList()[index][idx] + .name + .toString(), + stateId: statesProvider + .stateGroups.values + .toList()[index][idx] + .id + .toString(), ); }, ); @@ -270,21 +288,19 @@ class _StatesPageState extends ConsumerState { ); } - Color getColorFromIssueProvider( - IssuesProvider issuesProvider, int index, int idx) { + Color getColorFromIssueProvider(int index, int idx) { + final statesProvider = ref.watch(ProviderList.statesProvider); const Color colorToReturnOnApiError = Color.fromARGB(255, 200, 80, 80); - final String? colorData = - issuesProvider.statesData[states[index]][idx]['color']; - + final String colorData = + statesProvider.stateGroups.values.toList()[index][idx].color; return (colorData == null || colorData[0] != '#') ? colorToReturnOnApiError : Color(int.parse("FF${colorData.replaceAll('#', '')}", radix: 16)); } } -// ignore: must_be_immutable class AddUpdateState extends ConsumerStatefulWidget { - AddUpdateState( + const AddUpdateState( {required this.stateKey, required this.method, required this.groupIndex, @@ -296,7 +312,7 @@ class AddUpdateState extends ConsumerStatefulWidget { final CRUD method; final String stateId; final String name; - int groupIndex; + final int groupIndex; final String color; @override @@ -313,7 +329,7 @@ class _AddUpdateStateState extends ConsumerState { void initState() { super.initState(); nameController.text = widget.name.isNotEmpty ? widget.name : ''; - stateController.text = states[widget.groupIndex]; + stateController.text = widget.stateKey; if (widget.color.isNotEmpty) { colorController.text = widget.color.replaceAll('#', ''); @@ -322,8 +338,6 @@ class _AddUpdateStateState extends ConsumerState { colorsForLabel[Random().nextInt(colorsForLabel.length)] .replaceAll('#', ''); } - - // log(widget.stateKey); } double height = 0.0; @@ -331,7 +345,8 @@ class _AddUpdateStateState extends ConsumerState { Widget build(BuildContext context) { final themeProvider = ref.watch(ProviderList.themeProvider); final projectProvider = ref.watch(ProviderList.projectProvider); - final issuesProvider = ref.watch(ProviderList.issuesProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + final statesProviderRead = ref.watch(ProviderList.statesProvider.notifier); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final box = context.findRenderObject() as RenderBox; height = box.size.height; @@ -479,9 +494,8 @@ class _AddUpdateStateState extends ConsumerState { ? Container() : GestureDetector( onTap: - issuesProvider - .statesData[ - states[widget.groupIndex]] + statesProvider.stateGroups.values + .toList()[widget.groupIndex] .length == 1 ? null @@ -533,23 +547,31 @@ class _AddUpdateStateState extends ConsumerState { height: 50, ), for (int i = 0; - i < states.length; + i < + statesProvider + .stateGroups + .keys + .length; i++) GestureDetector( onTap: () async { setState(() { stateController .text = - states[i]; + statesProvider + .stateGroups + .keys + .toList()[i]; }); Navigator.pop( context); }, child: Container( - padding: const EdgeInsets + padding: + const EdgeInsets .symmetric( - vertical: - 2), + vertical: + 2), child: Column( children: [ Row( @@ -558,22 +580,24 @@ class _AddUpdateStateState extends ConsumerState { .center, children: [ Radio( - activeColor: stateController.text == states[i] + activeColor: stateController.text == statesProvider.stateGroups.keys.toList()[i] ? null : themeProvider.themeManager.primaryColour, - fillColor: stateController.text != states[i] + fillColor: stateController.text != statesProvider.stateGroups.keys.toList()[i] ? MaterialStateProperty.all(themeProvider.themeManager.borderSubtle01Color) : null, visualDensity: VisualDensity.compact, - value: - states[i], + value: statesProvider + .stateGroups + .keys + .toList()[i], groupValue: stateController.text, onChanged: (value) { setState(() { - stateController.text = states[i]; + stateController.text = statesProvider.stateGroups.keys.toList()[i]; }); Navigator.pop(context); }, @@ -583,7 +607,7 @@ class _AddUpdateStateState extends ConsumerState { width * 0.7, child: CustomText( - states[i], + statesProvider.stateGroups.keys.toList()[i], type: FontStyle.Medium, maxLines: @@ -630,7 +654,7 @@ class _AddUpdateStateState extends ConsumerState { Container( margin: const EdgeInsets - .only( + .only( left: 15), width: MediaQuery.of( context) @@ -698,33 +722,59 @@ class _AddUpdateStateState extends ConsumerState { : 'Update State', ontap: () async { if (nameController.text.isNotEmpty) { - await projectProvider.stateCrud( - slug: ref - .watch(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projId: ref - .watch(ProviderList.projectProvider) - .currentProject['id'], - stateId: widget.stateId.isEmpty ? '' : widget.stateId, - method: widget.method, - context: context, + if (widget.method == CRUD.create) { + statesProviderRead.createState( data: { "name": nameController.text, "color": '#${colorController.text}', "group": stateController.text.toLowerCase(), "description": "" }, - ref: ref); - issuesProvider.getStates( - slug: ref - .watch(ProviderList.workspaceProvider) - .selectedWorkspace - .workspaceSlug, - projID: ref - .watch(ProviderList.projectProvider) - .currentProject['id'], - ); + ); + } else { + statesProviderRead.updateState( + data: { + "name": nameController.text, + "color": '#${colorController.text}', + "group": stateController.text.toLowerCase(), + "description": "" + }, + slug: ref + .watch(ProviderList.workspaceProvider) + .selectedWorkspace + .workspaceSlug, + projectId: ref + .watch(ProviderList.projectProvider) + .currentProject['id'], + stateId: widget.stateId); + } + // await projectProvider.stateCrud( + // slug: ref + // .watch(ProviderList.workspaceProvider) + // .selectedWorkspace + // .workspaceSlug, + // projId: ref + // .watch(ProviderList.projectProvider) + // .currentProject['id'], + // stateId: widget.stateId.isEmpty ? '' : widget.stateId, + // method: widget.method, + // context: context, + // data: { + // "name": nameController.text, + // "color": '#${colorController.text}', + // "group": stateController.text.toLowerCase(), + // "description": "" + // }, + // ref: ref); + // statesProviderRead.getStates( + // slug: ref + // .watch(ProviderList.workspaceProvider) + // .selectedWorkspace + // .workspaceSlug, + // projectId: ref + // .watch(ProviderList.projectProvider) + // .currentProject['id'], + // ); } Navigator.of(context).pop(); }, diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views.dart b/lib/screens/project/views/views.dart similarity index 98% rename from lib/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views.dart rename to lib/screens/project/views/views.dart index 91d761f3..808fe7b1 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views.dart +++ b/lib/screens/project/views/views.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:lucide_icons/lucide_icons.dart'; -import 'package:plane/bottom_sheets/delete_cycle_sheet.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views_detail.dart'; +import 'package:plane/bottom-sheets/delete_cycle_sheet.dart'; import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/views/views_detail.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/empty.dart'; diff --git a/lib/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views_detail.dart b/lib/screens/project/views/views_detail.dart similarity index 97% rename from lib/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views_detail.dart rename to lib/screens/project/views/views_detail.dart index aabef5ed..a74cca81 100644 --- a/lib/screens/MainScreens/Projects/ProjectDetail/ViewsTab/views_detail.dart +++ b/lib/screens/project/views/views_detail.dart @@ -5,18 +5,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/models/issues.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/project_detail.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; import 'package:plane/utils/custom_toast.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'package:plane/widgets/loading_widget.dart'; - -import '../../../../../bottom_sheets/filters/filter_sheet.dart'; -import '../../../../../bottom_sheets/type_sheet.dart'; -import '../../../../../bottom_sheets/views_sheet.dart'; +import '../../../../../bottom-sheets/filters/filter_sheet.dart'; +import '../../../../../bottom-sheets/type_sheet.dart'; +import '../../../../../bottom-sheets/views_sheet.dart'; import '../../../../../utils/constants.dart'; import '../../../../../utils/enums.dart'; import '../../../../../widgets/custom_text.dart'; -import '../IssuesTab/CreateIssue/create_issue.dart'; class ViewsDetail extends ConsumerStatefulWidget { const ViewsDetail( @@ -64,7 +62,7 @@ class _ViewsDetailState extends ConsumerState { issuesProvider.tempIssueType = issuesProvider.issues.issueType; issuesProvider.tempFilters = issuesProvider.issues.filters; - issuesProvider.issues.projectView = ProjectView.kanban; + issuesProvider.issues.projectView = IssueLayout.kanban; issuesProvider.issues.groupBY = GroupBY.state; issuesProvider.issues.orderBY = OrderBY.lastCreated; @@ -98,10 +96,7 @@ class _ViewsDetailState extends ConsumerState { final issuesProvider = ref.watch(ProviderList.issuesProvider); final viewsProv = ref.watch(ProviderList.viewsProvider); final themeProvider = ref.watch(ProviderList.themeProvider); - - // viewData = viewsProv.viewDetail; - - log(issuesProvider.issues.filters.priorities.toString()); + final statesProvider = ref.watch(ProviderList.statesProvider); return WillPopScope( onWillPop: () async { if (!widget.fromGlobalSearch) { @@ -177,7 +172,7 @@ class _ViewsDetailState extends ConsumerState { body: LoadingWidget( loading: issuesProvider.issuePropertyState == StateEnum.loading || issuesProvider.issueState == StateEnum.loading || - issuesProvider.statesState == StateEnum.loading || + statesProvider.statesState == StateEnum.loading || issuesProvider.projectViewState == StateEnum.loading || issuesProvider.orderByState == StateEnum.loading || viewsProv.viewsState == StateEnum.loading, @@ -330,7 +325,8 @@ class _ViewsDetailState extends ConsumerState { color: themeProvider.themeManager.borderSubtle01Color, width: double.infinity, ), - Expanded(child: issues(context, ref, isViews: true)), + // TODO: Add views here + // Expanded(child: issues(context, ref, isViews: true)), SafeArea( child: Container( height: 50, diff --git a/lib/screens/project/widgets/assignee_widget.dart b/lib/screens/project/widgets/assignee_widget.dart new file mode 100644 index 00000000..9a0164bf --- /dev/null +++ b/lib/screens/project/widgets/assignee_widget.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/utils/constants.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_text.dart'; +import 'package:plane/widgets/member_logo_widget.dart'; + +Widget assigneeWidget({required WidgetRef ref, required Map detailData}) { + final themeProvider = ref.watch(ProviderList.themeProvider); + + return Container( + height: 45, + width: double.infinity, + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + //icon + Icon( + //two people icon + Icons.people_outline_outlined, + color: themeProvider.themeManager.placeholderTextColor, + ), + const SizedBox(width: 15), + CustomText( + 'Lead', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.placeholderTextColor, + ), + const Spacer(), + detailData['owned_by'] == null + ? CustomText( + 'No lead', + type: FontStyle.Medium, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.primaryTextColor, + ) + : Row( + children: [ + MemberLogoWidget( + fontType: FontStyle.Small, + boarderRadius: 50, + padding: EdgeInsets.zero, + size: 25, + imageUrl: (detailData['owned_by']['avatar']), + colorForErrorWidget: const Color.fromRGBO(55, 65, 81, 1), + memberNameFirstLetterForErrorWidget: + detailData['owned_by']['display_name'][0].toString(), + ), + const SizedBox(width: 5), + Container( + constraints: BoxConstraints(maxWidth: width * 0.4), + child: CustomText( + (detailData['owned_by'] != null && + detailData['owned_by']['display_name'] != null) + ? detailData['owned_by']['display_name'] ?? '' + : '', + type: FontStyle.Small, + maxLines: 1, + ), + ), + ], + ), + ], + ), + ), + ); +} diff --git a/lib/screens/project/widgets/assignees_widget.dart b/lib/screens/project/widgets/assignees_widget.dart new file mode 100644 index 00000000..c3af11a2 --- /dev/null +++ b/lib/screens/project/widgets/assignees_widget.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/completion_percentage.dart'; +import 'package:plane/widgets/custom_text.dart'; + +Widget assigneesWidget({required WidgetRef ref, required Map detailData}) { + final issuesProvider = ref.watch(ProviderList.issuesProvider); + final themeProvider = ref.watch(ProviderList.themeProvider); + + + return Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomText( + 'Assignees', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + )), + const SizedBox(height: 10), + detailData['assignees'].length == 0 + ? const CustomText('No data found.') + : Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: Column( + children: [ + ...List.generate( + detailData['assignees'].length, + (idx) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 8), + margin: const EdgeInsets.symmetric(vertical: 2), + decoration: BoxDecoration( + color: (issuesProvider.issues.filters.assignees + .contains(detailData['assignees'][idx] + ['assignee_id']) + ? themeProvider + .themeManager.secondaryBackgroundDefaultColor + : themeProvider + .themeManager.primaryBackgroundDefaultColor), + borderRadius: BorderRadius.circular(5), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 10), + child: detailData['assignees'][idx] + ['avatar'] != + null && + detailData['assignees'][idx] + ['avatar'] != + '' + ? CircleAvatar( + radius: 10, + backgroundImage: NetworkImage( + detailData['assignees'][idx] + ['avatar']), + ) + : CircleAvatar( + radius: 10, + backgroundColor: themeProvider + .themeManager + .tertiaryBackgroundDefaultColor, + child: Center( + child: CustomText( + detailData['assignees'][idx] + ['first_name'] != + null + ? detailData['assignees'] + [idx] + ['first_name'][0] + .toString() + .toUpperCase() + : '', + type: FontStyle.Small, + ), + ), + )), + CustomText( + detailData['assignees'][idx] + ['display_name'] ?? + 'No Assignees', + color: themeProvider + .themeManager.secondaryTextColor, + ), + ], + ), + CompletionPercentage( + value: detailData['assignees'][idx] + ['completed_issues'], + totalValue: detailData['assignees'][idx] + ['total_issues'], + ) + ], + ), + ); + }, + ), + ], + ), + ), + ], + ); +} diff --git a/lib/screens/project/widgets/floating_action_button.dart b/lib/screens/project/widgets/floating_action_button.dart new file mode 100644 index 00000000..ea458646 --- /dev/null +++ b/lib/screens/project/widgets/floating_action_button.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/create_view_screen.dart'; +import 'package:plane/screens/project/cycles/create_cycle.dart'; +import 'package:plane/screens/project/modules/create_module.dart'; +import 'package:plane/utils/enums.dart'; + +// ignore: non_constant_identifier_names +Widget FLActionButton( + BuildContext context, { + required WidgetRef ref, + required int selected, +}) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + final cycleProvider = ref.watch(ProviderList.cyclesProvider); + final moduleProvider = ref.watch(ProviderList.modulesProvider); + final viewsProvider = ref.watch(ProviderList.viewsProvider); + + return selected != 0 && + (projectProvider.role == Role.admin || + projectProvider.role == Role.member) && + ((selected == 1 && cycleProvider.showAddFloatingButton()) || + (selected == 2 && + moduleProvider.moduleState != StateEnum.loading && + (moduleProvider.modules.isNotEmpty || + moduleProvider.favModules.isNotEmpty)) || + (selected == 3 && + viewsProvider.viewsState != StateEnum.loading && + viewsProvider.views.isNotEmpty)) + ? FloatingActionButton( + backgroundColor: themeProvider.themeManager.primaryColour, + child: Icon( + Icons.add, + color: themeProvider.themeManager.textonColor, + ), + onPressed: () { + if (selected == 1 && projectProvider.features[1]['show']) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const CreateCycle(), + ), + ); + } + + if (selected == 1 && + !projectProvider.features[1]['show'] && + projectProvider.features[2]['show']) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const CreateModule(), + ), + ); + } + + if (selected == 1 && + !projectProvider.features[1]['show'] && + !projectProvider.features[2]['show']) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const CreateView(), + ), + ); + } + + if (selected == 2 && + projectProvider.features[2]['show'] && + projectProvider.features[1]['show']) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const CreateModule(), + ), + ); + } + + if ((selected == 2 && + projectProvider.features[2]['show'] && + !projectProvider.features[1]['show']) || + (selected == 2 && + !projectProvider.features[2]['show'] && + projectProvider.features[1]['show'])) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const CreateView(), + ), + ); + } + if (selected == 3) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const CreateView(), + ), + ); + } + // if (selected == 4) { + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (ctx) => const CreatePage(), + // ), + // ); + // } + }, + ) + : Container(); +} diff --git a/lib/screens/project/widgets/links_widget.dart b/lib/screens/project/widgets/links_widget.dart new file mode 100644 index 00000000..617dcd34 --- /dev/null +++ b/lib/screens/project/widgets/links_widget.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/bottom-sheets/add_link_sheet.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_text.dart'; + +Widget links({required WidgetRef ref, required BuildContext context}) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final moduleProvider = ref.read(ProviderList.modulesProvider); + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText( + 'Links', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + ), + GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.85), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return SingleChildScrollView( + child: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom), + child: const AddLinkSheet(), + ), + ); + }, + ); + }, + child: Icon( + Icons.add, + color: themeProvider.themeManager.primaryTextColor, + )) + ], + ), + const SizedBox(height: 10), + moduleProvider.moduleDetailsData['link_module'].isNotEmpty + ? Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: + moduleProvider.moduleDetailsData['link_module'].length, + itemBuilder: (ctx, index) { + return SizedBox( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.only(top: 5), + child: Transform.rotate( + angle: -20, + child: Icon( + Icons.link, + color: + themeProvider.themeManager.primaryTextColor, + size: 20, + ), + ), + ), + const SizedBox( + width: 10, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + moduleProvider.moduleDetailsData['link_module'] + [index]['title'] != + null + ? CustomText( + moduleProvider + .moduleDetailsData['link_module'][index] + ['title'] + .toString(), + type: FontStyle.Medium, + ) + : Container(), + CustomText( + 'by ${moduleProvider.moduleDetailsData['link_module'][index]['created_by_detail']['display_name']}', + type: FontStyle.Small, + color: themeProvider + .themeManager.placeholderTextColor, + ) + ], + ), + const Spacer(), + GestureDetector( + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context).size.height * + 0.85), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + )), + context: context, + builder: (ctx) { + return SingleChildScrollView( + child: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context) + .viewInsets + .bottom), + child: AddLinkSheet( + id: moduleProvider + .moduleDetailsData['link_module'] + [index]['id'], + ), + ), + ); + }, + ); + }, + child: Icon( + Icons.edit_outlined, + color: + themeProvider.themeManager.placeholderTextColor, + size: 20, + ), + ), + const SizedBox( + width: 10, + ), + GestureDetector( + onTap: () { + moduleProvider.handleLinks( + linkID: moduleProvider + .moduleDetailsData['link_module'][index] + ['id'], + data: {}, + method: HttpMethod.delete, + context: context); + }, + child: Icon( + Icons.delete_outline, + color: + themeProvider.themeManager.placeholderTextColor, + size: 20, + ), + ), + ], + ), + ); + }), + ) + : Container() + ]); +} diff --git a/lib/screens/project/widgets/progress_chart.dart b/lib/screens/project/widgets/progress_chart.dart new file mode 100644 index 00000000..7106d164 --- /dev/null +++ b/lib/screens/project/widgets/progress_chart.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:plane/models/chart_model.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_text.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + + Widget progressWidget({required WidgetRef ref, required List chartData}) { + final themeProvider = ref.watch(ProviderList.themeProvider); + return Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomText( + 'Progress', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + ), + ), + const SizedBox(height: 10), + Container( + padding: + const EdgeInsets.only(left: 20, right: 30, top: 35, bottom: 20), + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: SizedBox( + height: 200, + child: SfCartesianChart( + plotAreaBorderColor: Colors.transparent, + margin: EdgeInsets.zero, + primaryYAxis: NumericAxis( + majorGridLines: + const MajorGridLines(width: 0), // Remove major grid lines + ), + primaryXAxis: CategoryAxis( + labelPlacement: + LabelPlacement.betweenTicks, // Adjust label placement + interval: chartData.length > 5 ? 3 : 1, + majorGridLines: const MajorGridLines( + width: 0, + ), // Remove major grid lines + minorGridLines: const MinorGridLines(width: 0), + axisLabelFormatter: (axisLabelRenderArgs) { + return ChartAxisLabel( + DateFormat('dd MMM').format( + DateTime.parse(axisLabelRenderArgs.text), + ), + const TextStyle(fontWeight: FontWeight.normal), + ); + }, + ), + series: [ + // Renders area chart + AreaSeries( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.transparent, + themeProvider.themeManager.primaryColour.withOpacity(0.2), + themeProvider.themeManager.primaryColour.withOpacity(0.3), + ], + ), + dataSource: chartData, + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y, + ), + LineSeries( + dashArray: [5.0, 5.0], + dataSource: chartData.isNotEmpty + ? [ + ChartData(chartData.first.x, + chartData.first.y), // First data point + ChartData(chartData.last.x, + 0.0), // Data point at current time with Y-value of last data point + ] + : [], + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y, + ), + ], + ), + ), + ), + ], + ); + + } diff --git a/lib/screens/project/widgets/project_detail_appbar.dart b/lib/screens/project/widgets/project_detail_appbar.dart new file mode 100644 index 00000000..6f2143ed --- /dev/null +++ b/lib/screens/project/widgets/project_detail_appbar.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/archived_issues.dart'; +import 'package:plane/screens/settings_screen.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_app_bar.dart'; + +// ignore: non_constant_identifier_names +PreferredSizeWidget ProjectDetailAppbar( + BuildContext context, { + required WidgetRef ref, + required VoidCallback settingsOntap, +}) { + final themeProvider = ref.read(ProviderList.themeProvider); + final projectProvider = ref.read(ProviderList.projectProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + return CustomAppBar( + elevation: false, + onPressed: () { + Navigator.pop(context); + }, + text: projectProvider.currentProject['name'], + actions: [ + (projectProvider.currentProject['archive_in'] > 0 && + (projectProvider.role == Role.admin || + projectProvider.role == Role.member) && + (statesProvider.statesState == StateEnum.success)) + ? IconButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => const ArchivedIssues(), + ), + ); + }, + icon: Icon( + Icons.archive_outlined, + color: themeProvider.themeManager.placeholderTextColor, + )) + : Container(), + (statesProvider.statesState == StateEnum.success) + ? IconButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const SettingScreen(), + ), + ).then((value) { + settingsOntap(); + }); + }, + icon: statesProvider.statesState == StateEnum.restricted + ? Container() + : Icon( + Icons.settings_outlined, + color: themeProvider.themeManager.placeholderTextColor, + ), + ) + : Container(), + ], + ); +} diff --git a/lib/screens/project/widgets/project_detail_bottom_actions.dart b/lib/screens/project/widgets/project_detail_bottom_actions.dart new file mode 100644 index 00000000..28f3d7b3 --- /dev/null +++ b/lib/screens/project/widgets/project_detail_bottom_actions.dart @@ -0,0 +1,295 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/bottom-sheets/filters/filter_sheet.dart'; +import 'package:plane/bottom-sheets/page_filter_sheet.dart'; +import 'package:plane/bottom-sheets/type_sheet.dart'; +import 'package:plane/bottom-sheets/views_sheet.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/pages/create_page_screen.dart'; +import 'package:plane/utils/bottom_sheet.helper.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_text.dart'; + +class ProjectDetailBottomActions extends ConsumerStatefulWidget { + const ProjectDetailBottomActions({required this.selectedTab, super.key}); + final int selectedTab; + @override + ConsumerState createState() => + _PDBottomActionsState(); +} + +class _PDBottomActionsState extends ConsumerState { + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + final issueProvider = ref.watch(ProviderList.issuesProvider); + final pageProvider = ref.watch(ProviderList.pageProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + + return Row( + children: [ + statesProvider.statesState == StateEnum.loading || + issueProvider.issueState == StateEnum.loading + ? Container() + : widget.selectedTab == 0 && + statesProvider.statesState == StateEnum.restricted + ? Container() + : widget.selectedTab == 0 && + statesProvider.statesState == StateEnum.success + ? Container( + decoration: BoxDecoration( + color: themeProvider + .themeManager.primaryBackgroundDefaultColor, + boxShadow: themeProvider + .themeManager.shadowBottomControlButtons, + ), + height: 50, + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + projectProvider.role == Role.admin || + projectProvider.role == Role.member + ? Expanded( + child: InkWell( + onTap: () { + issueProvider.createIssuedata['state'] = + statesProvider + .projectStates.keys.first; + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const CreateIssue(), + ), + ); + }, + child: SizedBox.expand( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.add, + color: themeProvider.themeManager + .primaryTextColor, + size: 20, + ), + const CustomText( + ' Issue', + type: FontStyle.Medium, + ) + ], + ), + ), + ), + ) + : Container(), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + Expanded( + child: InkWell( + onTap: () { + BottomSheetHelper.showBottomSheet( + context, + const TypeSheet( + issueCategory: IssueCategory.issues, + ), + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context).size.height * + 0.5), + ); + }, + child: SizedBox.expand( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.list_outlined, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Layout', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + issueProvider.issues.projectView == + IssueLayout.calendar + ? Container() + : Expanded( + child: InkWell( + onTap: () { + BottomSheetHelper.showBottomSheet( + context, + ViewsSheet( + projectView: issueProvider + .issues.projectView, + issueCategory: IssueCategory.issues, + )); + }, + child: SizedBox.expand( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.wysiwyg_outlined, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Display', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + Expanded( + child: InkWell( + onTap: () { + BottomSheetHelper.showBottomSheet( + context, + FilterSheet( + issueCategory: IssueCategory.issues, + )); + }, + child: SizedBox.expand( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.filter_list_outlined, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Filters', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + ], + ), + ) + : Container(), + widget.selectedTab == 4 + ? widget.selectedTab == 4 && + pageProvider.pages[pageProvider.selectedFilter]!.isEmpty + ? Container() + : Container( + height: 51, + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: themeProvider + .themeManager.primaryBackgroundDefaultColor, + boxShadow: + themeProvider.themeManager.shadowBottomControlButtons, + ), + child: Column( + children: [ + SizedBox( + height: 50, + child: Row( + children: [ + projectProvider.role == Role.admin + ? Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const CreatePage(), + ), + ); + }, + child: SizedBox.expand( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.add, + color: themeProvider + .themeManager + .primaryTextColor, + size: 20, + ), + const CustomText( + ' Page', + type: FontStyle.Medium, + ) + ], + ), + ), + ), + ) + : Container(), + Container( + height: 50, + width: 0.5, + color: themeProvider + .themeManager.borderSubtle01Color, + ), + Expanded( + child: InkWell( + onTap: () { + BottomSheetHelper.showBottomSheet( + context, const FilterPageSheet()); + }, + child: SizedBox.expand( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.filter_list_outlined, + color: themeProvider + .themeManager.primaryTextColor, + size: 19, + ), + const CustomText( + ' Filters', + type: FontStyle.Medium, + ) + ], + ), + ), + )), + ], + ), + ), + ], + ), + ) + : Container(), + ], + ); + } +} diff --git a/lib/screens/project/widgets/project_detail_root.dart b/lib/screens/project/widgets/project_detail_root.dart new file mode 100644 index 00000000..60f3a6c7 --- /dev/null +++ b/lib/screens/project/widgets/project_detail_root.dart @@ -0,0 +1,106 @@ +// ignore_for_file: non_constant_identifier_names + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/cycles/project_details_cycles.dart'; +import 'package:plane/screens/project/issues/issues_tab.dart'; +import 'package:plane/screens/project/models/project_detail_models.dart'; +import 'package:plane/screens/project/modules/module_screen.dart'; +import 'package:plane/screens/project/views/views.dart'; +import 'package:plane/screens/project/widgets/project_detail_bottom_actions.dart'; +import 'package:plane/screens/project/widgets/project_detail_tabs.dart'; + +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/error_state.dart'; + +class ProjectDetailRoot extends ConsumerStatefulWidget { + const ProjectDetailRoot( + {required this.onTabChange, required this.selectedTab, super.key}); + final int selectedTab; + final void Function(int index) onTabChange; + + @override + ConsumerState createState() => _ProjectDetailRootState(); +} + +class _ProjectDetailRootState extends ConsumerState { + List TABS = []; + final controller = PageController(); + + void initializeTabs() { + final projectProvider = ref.read(ProviderList.projectProvider); + TABS = [ + ProjectDetailTab(title: 'Issues', width: 60, show: true), + ProjectDetailTab( + title: 'Cycles', + width: 60, + show: projectProvider.features[1]['show']), + ProjectDetailTab( + title: 'Modules', + width: 75, + show: projectProvider.features[2]['show']), + ProjectDetailTab( + title: 'Views', width: 60, show: projectProvider.features[3]['show']), + ]; + } + + @override + void initState() { + initializeTabs(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final themeProvider = ref.watch(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); + final statesProvider = ref.watch(ProviderList.statesProvider); + + return projectProvider.projectDetailState == StateEnum.error + ? errorState( + context: context, + ontap: () { + ref + .read(ProviderList.projectProvider) + .initializeProject(ref: ref); + }) + : Column( + children: [ + statesProvider.statesState != StateEnum.loading + ? ProjectDetailTabs( + selectedTab: widget.selectedTab, + onTabChange: widget.onTabChange, + TABS: TABS, + ) + : Container(), + Container( + height: 2, + width: MediaQuery.of(context).size.width, + color: themeProvider.themeManager.borderSubtle01Color, + ), + Expanded( + child: PageView.builder( + controller: controller, + onPageChanged: (page) { + widget.onTabChange(page); + }, + itemBuilder: (ctx, index) { + return widget.selectedTab == 0 + ? const IssuesTab() + : widget.selectedTab == 1 + ? const CycleWidget() + : widget.selectedTab == 2 + ? const ModuleScreen() + : widget.selectedTab == 3 + ? const Views() + : Container(); + }, + itemCount: TABS.length, + ), + ), + ProjectDetailBottomActions(selectedTab: widget.selectedTab) + ], + ); + } +} diff --git a/lib/screens/project/widgets/project_detail_tabs.dart b/lib/screens/project/widgets/project_detail_tabs.dart new file mode 100644 index 00000000..7b3678c2 --- /dev/null +++ b/lib/screens/project/widgets/project_detail_tabs.dart @@ -0,0 +1,69 @@ +// ignore_for_file: non_constant_identifier_names + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/models/project_detail_models.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/custom_text.dart'; + +class ProjectDetailTabs extends ConsumerWidget { + const ProjectDetailTabs( + {required this.TABS, + required this.selectedTab, + required this.onTabChange, + super.key}); + final List TABS; + final int selectedTab; + final void Function(int) onTabChange; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final themeProvider = ref.watch(ProviderList.themeProvider); + return Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: TABS.map((tab) { + final tabIndex = TABS.indexOf(tab); + return tab.show + ? Expanded( + child: InkWell( + onTap: () { + onTabChange(tabIndex); + }, + child: Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 8), + child: CustomText( + tab.title, + color: tabIndex == selectedTab + ? themeProvider.themeManager.primaryColour + : themeProvider.themeManager.placeholderTextColor, + overrride: true, + type: FontStyle.Medium, + fontWeight: tabIndex == selectedTab + ? FontWeightt.Medium + : null, + ), + ), + selectedTab == TABS.indexOf(tab) && + TABS.elementAt(tabIndex).show + ? Container( + height: 6, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: themeProvider.themeManager.primaryColour, + ), + ) + : Container( + height: 6, + ) + ], + ), + ), + ) + : Container(); + }).toList(), + ); + } +} diff --git a/lib/screens/project/widgets/states_widget.dart b/lib/screens/project/widgets/states_widget.dart new file mode 100644 index 00000000..e8f4196c --- /dev/null +++ b/lib/screens/project/widgets/states_widget.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:plane/provider/provider_list.dart'; +import 'package:plane/utils/constants.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/widgets/completion_percentage.dart'; +import 'package:plane/widgets/custom_text.dart'; + +Widget statesWidget({required WidgetRef ref, required Map detailData}) { + final List states = [ + "Backlog", + "Unstarted", + "Started", + "Cancelled", + "Completed", + ]; + final themeProvider = ref.watch(ProviderList.themeProvider); + + return Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomText( + 'States', + type: FontStyle.Medium, + fontWeight: FontWeightt.Medium, + color: themeProvider.themeManager.primaryTextColor, + )), + const SizedBox(height: 10), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: themeProvider.themeManager.primaryBackgroundDefaultColor, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: themeProvider.themeManager.borderSubtle01Color, + ), + ), + child: Column( + children: [ + ...List.generate( + states.length, + (index) => Row( + children: [ + const SizedBox(height: 50), + Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + states[index] == 'Backlog' + ? 'assets/svg_images/circle.svg' + : states[index] == 'Unstarted' + ? 'assets/svg_images/in_progress.svg' + : states[index] == 'Started' + ? 'assets/svg_images/done.svg' + : states[index] == 'Cancelled' + ? 'assets/svg_images/cancelled.svg' + : 'assets/svg_images/circle.svg', + height: 22, + width: 22, + colorFilter: ColorFilter.mode( + index == 0 + ? const Color(0xFFCED4DA) + : index == 1 + ? const Color(0xFF26B5CE) + : index == 2 + ? const Color(0xFFF7AE59) + : index == 3 + ? const Color(0xFFD687FF) + : greenHighLight, + BlendMode.srcIn)), + ), + CustomText( + states[index], + type: FontStyle.Large, + fontWeight: FontWeightt.Regular, + color: themeProvider.themeManager.secondaryTextColor, + ), + const Spacer(), + index == 0 + ? CompletionPercentage( + value: detailData['backlog_issues'], + totalValue: detailData['total_issues']) + : index == 1 + ? CompletionPercentage( + value: detailData['unstarted_issues'], + totalValue: detailData['total_issues']) + : index == 2 + ? CompletionPercentage( + value: detailData['started_issues'], + totalValue: detailData['total_issues']) + : index == 3 + ? CompletionPercentage( + value: detailData['cancelled_issues'], + totalValue: detailData['total_issues']) + : CompletionPercentage( + value: detailData['completed_issues'], + totalValue: detailData['total_issues'], + ), + ], + ), + ), + ], + ), + ), + ], + ); +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 31574fc6..c8cfcf61 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,23 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:plane/bottom_sheets/create_estimate.dart'; -import 'package:plane/bottom_sheets/project_invite_memebers_sheet.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/automations_page.dart'; +import 'package:plane/bottom-sheets/create_estimate.dart'; +import 'package:plane/bottom-sheets/project_invite_memebers_sheet.dart'; +import 'package:plane/screens/profile/workpsace-settings/members.dart'; +import 'package:plane/screens/project/settings/control_page.dart'; +import 'package:plane/screens/project/settings/create_label.dart'; +import 'package:plane/screens/project/settings/estimates_page.dart'; +import 'package:plane/screens/project/settings/features_page.dart'; +import 'package:plane/screens/project/settings/general_page.dart'; +import 'package:plane/screens/project/settings/lables_page.dart'; +import 'package:plane/screens/project/settings/states_pages.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/control_page.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/estimates_page.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/features_page.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/general_page.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/integrations_page.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/lables_page.dart'; -import 'package:plane/screens/MainScreens/Profile/WorkpsaceSettings/members.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/states_pages.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/widgets/custom_text.dart'; import 'package:plane/widgets/loading_widget.dart'; -import 'MainScreens/Projects/ProjectDetail/Settings/create_label.dart'; +import 'project/settings/integrations_page.dart'; + class SettingScreen extends ConsumerStatefulWidget { const SettingScreen({super.key}); @@ -37,7 +37,7 @@ class _SettingScreenState extends ConsumerState 'Labels', 'Integrations', 'Estimates', - 'Automations', + // 'Automations', ]; final pageViewController = PageController(initialPage: 0); @@ -313,7 +313,7 @@ class _SettingScreenState extends ConsumerState LablesPage(), IntegrationsWidget(), EstimatsPage(), - AutomationsPage(), + // AutomationsPage(), ]), ), ], diff --git a/lib/screens/Theming/custom_theme.dart b/lib/screens/themes/custom_theme.dart similarity index 100% rename from lib/screens/Theming/custom_theme.dart rename to lib/screens/themes/custom_theme.dart diff --git a/lib/screens/Theming/prefrences.dart b/lib/screens/themes/prefrences.dart similarity index 99% rename from lib/screens/Theming/prefrences.dart rename to lib/screens/themes/prefrences.dart index c1a10ef8..a2bf4474 100644 --- a/lib/screens/Theming/prefrences.dart +++ b/lib/screens/themes/prefrences.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/screens/Theming/custom_theme.dart'; +import 'package:plane/screens/themes/custom_theme.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart'; diff --git a/lib/screens/unused_screens/issue_detail_unused.dart b/lib/screens/unused-screens/issue_detail_unused.dart similarity index 99% rename from lib/screens/unused_screens/issue_detail_unused.dart rename to lib/screens/unused-screens/issue_detail_unused.dart index 6bd46a9a..58e0a631 100644 --- a/lib/screens/unused_screens/issue_detail_unused.dart +++ b/lib/screens/unused-screens/issue_detail_unused.dart @@ -5,16 +5,16 @@ // import 'package:flutter_svg/svg.dart'; // import 'package:intl/intl.dart'; // import 'package:loading_indicator/loading_indicator.dart'; -// import 'package:plane/bottom_sheets/add_attachment_sheet.dart'; -// import 'package:plane/bottom_sheets/add_link_sheet.dart'; -// import 'package:plane/bottom_sheets/issue_detail_cycles_sheet.dart'; -// import 'package:plane/bottom_sheets/issue_detail_modules_list.dart'; -// import 'package:plane/bottom_sheets/issues_list_sheet.dart'; -// import 'package:plane/bottom_sheets/select_estimate.dart'; -// import 'package:plane/bottom_sheets/select_issue_labels.dart'; -// import 'package:plane/bottom_sheets/select_priority.dart'; -// import 'package:plane/bottom_sheets/select_project_members.dart'; -// import 'package:plane/bottom_sheets/select_states.dart'; +// import 'package:plane/bottom-sheets/add_attachment_sheet.dart'; +// import 'package:plane/bottom-sheets/add_link_sheet.dart'; +// import 'package:plane/bottom-sheets/issue_detail_cycles_sheet.dart'; +// import 'package:plane/bottom-sheets/issue_detail_modules_list.dart'; +// import 'package:plane/bottom-sheets/issues_list_sheet.dart'; +// import 'package:plane/bottom-sheets/select_estimate.dart'; +// import 'package:plane/bottom-sheets/select_issue_labels.dart'; +// import 'package:plane/bottom-sheets/select_priority.dart'; +// import 'package:plane/bottom-sheets/select_project_members.dart'; +// import 'package:plane/bottom-sheets/select_states.dart'; // import 'package:plane/provider/theme_provider.dart'; // import 'package:plane/utils/color_manager.dart'; // import 'package:plane/utils/custom_toast.dart'; diff --git a/lib/services/dio_service.dart b/lib/services/dio_service.dart index 9a33e26a..e48e6b30 100644 --- a/lib/services/dio_service.dart +++ b/lib/services/dio_service.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:plane/config/const.dart'; -import 'package:plane/screens/on_boarding/on_boarding_screen.dart'; +import 'package:plane/screens/onboarding/on_boarding_screen.dart'; import 'package:plane/services/shared_preference_service.dart'; import 'package:plane/utils/enums.dart'; import 'package:retry/retry.dart'; diff --git a/lib/startup/dependency_resolver.dart b/lib/startup/dependency_resolver.dart index 677f7cdd..334be556 100644 --- a/lib/startup/dependency_resolver.dart +++ b/lib/startup/dependency_resolver.dart @@ -19,7 +19,7 @@ import '../utils/theme_manager.dart'; class DependencyResolver { static Future resolve({required WidgetRef ref}) async { - await _resolveConfig(ref).then((_) async { + // await _resolveConfig(ref).then((_) async { if (Const.accessToken == null) { CustomToast(manager: ThemeManager(THEME.light)); FlutterNativeSplash.remove(); @@ -68,7 +68,7 @@ class DependencyResolver { _resolveNotifications(ref); _resolveWhatsNew(ref); FlutterNativeSplash.remove(); - }); + // }); } static Future _resolveConfig(WidgetRef ref) async { diff --git a/lib/utils/bottom_sheet.helper.dart b/lib/utils/bottom_sheet.helper.dart new file mode 100644 index 00000000..a82ecb2e --- /dev/null +++ b/lib/utils/bottom_sheet.helper.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class BottomSheetHelper { + static void showBottomSheet( + BuildContext context, + Widget child, { + BoxConstraints? constraints, + Color? barrierColor, + ShapeBorder? shape, + bool? isScrollControlled = false, + bool? isDismissible = true, + bool? enableDrag = true, + }) { + showModalBottomSheet( + constraints: constraints ?? + BoxConstraints(maxHeight: MediaQuery.sizeOf(context).height * 0.8), + shape: shape ?? + const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20))), + isScrollControlled: isScrollControlled ?? false, + isDismissible: isDismissible ?? true, + enableDrag: enableDrag ?? true, + context: context, + builder: (BuildContext context) { + return child; + }); + } +} diff --git a/lib/utils/editor.dart b/lib/utils/editor.dart index 6db73deb..21cf38fb 100644 --- a/lib/utils/editor.dart +++ b/lib/utils/editor.dart @@ -10,12 +10,11 @@ import 'package:loading_indicator/loading_indicator.dart'; import 'package:plane/config/config_variables.dart'; import 'package:plane/provider/issues_provider.dart'; import 'package:plane/provider/provider_list.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_app_bar.dart'; import 'dart:developer'; -import '../screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; - class EDITOR extends ConsumerStatefulWidget { const EDITOR( {required this.url, diff --git a/lib/utils/enums.dart b/lib/utils/enums.dart index 8b11fbb3..3d0dd38d 100644 --- a/lib/utils/enums.dart +++ b/lib/utils/enums.dart @@ -18,7 +18,7 @@ enum PageFilters { all, recent, favourites, createdByMe, createdByOthers } enum HttpMethod { connect, delete, get, head, options, patch, post, put, trace } -enum ProjectView { kanban, list, calendar, spreadsheet } +enum IssueLayout { kanban, list, calendar, spreadsheet } enum GroupBY { state, diff --git a/lib/utils/extensions/list_extensions.dart b/lib/utils/extensions/list_extensions.dart index 9c7c2e8f..a7fb92ba 100644 --- a/lib/utils/extensions/list_extensions.dart +++ b/lib/utils/extensions/list_extensions.dart @@ -6,4 +6,10 @@ extension ListExtension on List? { bool isNotNullOrEmpty() { return !isNullOrEmpty(); } + + String toQueryParam(String param) { + if (this!.isEmpty) return ''; + return param + + toString().replaceAll('[', '').replaceAll(']', '').replaceAll(' ', ''); + } } diff --git a/lib/utils/global_functions.dart b/lib/utils/global_functions.dart index 7c7af173..52f4b7f6 100644 --- a/lib/utils/global_functions.dart +++ b/lib/utils/global_functions.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:plane/config/config_variables.dart'; import 'package:plane/main.dart'; -import 'package:plane/provider/provider_list.dart'; import 'package:posthog_flutter/posthog_flutter.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -22,13 +21,13 @@ void sentryService() { void postHogService( {required String eventName, required Map properties, - required WidgetRef ref}) { + required String userID, + required String userEmail}) { if (Config.posthogApiKey != null && Config.posthogApiKey != '') { - final profileProvider = ref.watch(ProviderList.profileProvider); properties.addAll( { - 'USER_ID': profileProvider.userProfile.id, - 'USER_EMAIL': profileProvider.userProfile.email, + 'USER_ID': userID, + 'USER_EMAIL': userEmail, }, ); try { diff --git a/lib/utils/issues_filter/group_by_issues.dart b/lib/utils/issues_filter/group_by_issues.dart new file mode 100644 index 00000000..96674de8 --- /dev/null +++ b/lib/utils/issues_filter/group_by_issues.dart @@ -0,0 +1,140 @@ +import 'package:plane/models/project/state/state_model.dart'; +import 'package:plane/utils/enums.dart'; + +class IssuesGroupBYHelper { + static List defaultStateGroups = [ + 'backlog', + 'unstarted', + 'started', + 'completed', + 'cancelled', + ]; + + static Map> _groupByState( + List issues, Map states) { + Map> groupedIssues = {}; + Map> stateGroups = {}; + for (final state in defaultStateGroups) { + stateGroups[state] = states.values.where((e) => e.group == state).toList(); + stateGroups[state]! + .sort((a, b) => a.sequence.compareTo(b.sequence)); + } + + for (final stateGroup in stateGroups.keys) { + for (final state in stateGroups[stateGroup]!) { + groupedIssues[state.id] = + issues.where((issue) => issue['state_id'] == state.id).toList(); + } + } + return groupedIssues; + } + + static Map> _groupByStateGroups(List issues) { + Map> groupedIssues = {}; + + for (final stateGroup in defaultStateGroups) { + groupedIssues[stateGroup] = []; + } + for (final issue in issues) { + groupedIssues[issue['state_detail']['group']]!.add(issue); + } + + return groupedIssues; + } + + static Map> _groupByPriority(List issues) { + Map> groupedIssues = {}; + final priorities = ['urgent', 'high', 'medium', 'low', 'none']; + for (final priority in priorities) { + groupedIssues[priority] = + issues.where((issue) => issue['priority'] == priority).toList(); + } + return groupedIssues; + } + + static Map> _groupByLabels( + List issues, List labelIDs) { + Map> groupedIssues = {}; + for (final label in labelIDs) { + groupedIssues[label] = issues + .where((issue) => (issue['label_ids'] as List).contains(label)) + .toList(); + } + return groupedIssues; + } + + static Map> _groupByAssignees( + List issues, List memberIDs) { + Map> groupedIssues = {}; + for (final memberID in memberIDs) { + groupedIssues[memberID] = issues + .where((issue) => (issue['assignee_ids'] as List).contains(memberID)) + .toList(); + } + return groupedIssues; + } + + static Map> _groupByCreator( + List issues, List memberIDs) { + Map> groupedIssues = {}; + for (final memberID in memberIDs) { + groupedIssues[memberID] = + issues.where((issue) => (issue['created_by'] == memberID)).toList(); + } + return groupedIssues; + } + + static Map> _groupByProject( + List issues, List projectIDs) { + Map> groupedIssues = {}; + for (final project in projectIDs) { + groupedIssues[project['id']] = + issues.where((issue) => (issue['project'] == project['id'])).toList(); + } + return groupedIssues; + } + + static Map> _groupByNone(List issues) { + Map> groupedIssues = {}; + groupedIssues['All Issues'] = issues; + return groupedIssues; + } + + static Map> groupIssues( + List issues, + GroupBY groupBY, { + Map? filter, + required List labelIDs, + required List memberIDs, + required dynamic stateIDs, + }) { + Map> groupedIssues = {}; + switch (groupBY) { + case GroupBY.state: + groupedIssues = _groupByState(issues, stateIDs); + break; + case GroupBY.stateGroups: + groupedIssues = _groupByStateGroups(issues); + break; + case GroupBY.priority: + groupedIssues = _groupByPriority(issues); + break; + case GroupBY.labels: + groupedIssues = _groupByLabels(issues, labelIDs); + break; + case GroupBY.assignees: + groupedIssues = _groupByAssignees(issues, memberIDs); + break; + case GroupBY.createdBY: + groupedIssues = _groupByCreator(issues, memberIDs); + break; + // case GroupBY.project: + // groupedIssues = _groupByProject(issues, projectIDs); + // break; + case GroupBY.none: + groupedIssues = _groupByNone(issues); + break; + } + return groupedIssues; + } +} diff --git a/lib/utils/issues_filter/issue_filter.helper.dart b/lib/utils/issues_filter/issue_filter.helper.dart new file mode 100644 index 00000000..c60ea8d6 --- /dev/null +++ b/lib/utils/issues_filter/issue_filter.helper.dart @@ -0,0 +1,41 @@ +import 'package:plane/models/issues.dart'; +import 'package:plane/utils/enums.dart'; +import 'package:plane/utils/extensions/list_extensions.dart'; +import 'package:plane/utils/issues_filter/group_by_issues.dart'; +import 'package:plane/utils/issues_filter/order_by_issues.dart'; + +class IssueFilterHelper { + static String getFilterQueryParams(Filters filters) { + String url = ''; + url = '$url${filters.priorities.toQueryParam("&priority=")}'; + url = '$url${filters.states.toQueryParam("&state=")}'; + url = '$url${filters.assignees.toQueryParam("&assignees=")}'; + url = '$url${filters.createdBy.toQueryParam("&created_by=")}'; + url = '$url${filters.labels.toQueryParam("&labels=")}'; + url = '$url${filters.targetDate.toQueryParam("&target_date=")}'; + url = '$url${filters.startDate.toQueryParam("&start_date=")}'; + return url; + } + + static Map organizeIssues( + List issues, + GroupBY groupBY, + OrderBY orderBY, { + Map? filter, + required List labelIDs, + required List memberIDs, + required dynamic states, + }) { + Map> groupedIssues = IssuesGroupBYHelper.groupIssues( + issues, + groupBY, + filter: filter, + labelIDs: labelIDs, + memberIDs: memberIDs, + stateIDs: states, + ); + Map organizedIssues = + IssuesOrderBYHelper.orderIssues(groupedIssues, orderBY); + return organizedIssues; + } +} diff --git a/lib/utils/issues_filter/order_by_issues.dart b/lib/utils/issues_filter/order_by_issues.dart new file mode 100644 index 00000000..a9b7bd41 --- /dev/null +++ b/lib/utils/issues_filter/order_by_issues.dart @@ -0,0 +1,101 @@ +// ignore_for_file: non_constant_identifier_names +import 'package:plane/utils/enums.dart'; + +class IssuesOrderBYHelper { + static Map> _orderBYManual( + Map> issues) { + issues.forEach((key, value) { + value.sort((a, b) => b['sort_order'].compareTo(a['sort_order'])); + }); + return issues; + } + + static Map> _orderBYLastCreated( + Map> issues) { + issues.forEach((key, value) { + value.sort((a, b) => DateTime.parse(b['created_at']) + .compareTo(DateTime.parse(a['created_at']))); + }); + return issues; + } + + static Map> _orderBYLastUpdated( + Map> issues) { + issues.forEach((key, value) { + value.sort((a, b) => DateTime.parse(b['updated_at']) + .compareTo(DateTime.parse(a['updated_at']))); + }); + return issues; + } + + static Map> _orderBYStartDate( + Map> issues) { + issues.forEach((key, value) { + value.sort((a, b) { + if (a['start_date'] == null && b['start_date'] == null) { + return 0; + } else if (b['start_date'] == null && a['start_date'] != null) { + return -1; + } else if (a['start_date'] == null && b['start_date'] != null) { + return 1; + } + return DateTime.parse(a['start_date']) + .compareTo(DateTime.parse(b['start_date'])); + }); + }); + return issues; + } + + static Map> _orderBYPriority( + Map> issues) { + final ISSUE_PRIORITIES = { + 'urgent': 0, + 'high': 1, + 'medium': 2, + 'low': 3, + 'none': 4 + }; + issues.forEach((key, value) { + value.sort((a, b) { + return ISSUE_PRIORITIES[a['priority']]! + .compareTo(ISSUE_PRIORITIES[b['priority']]!); + }); + }); + return issues; + } + + static Map> orderIssues( + Map> issues, + OrderBY orderBY, { + Map? filter, + dynamic labels, + dynamic members, + dynamic projects, + dynamic states, + }) { + // base issue order + Map> orderedIssues = _orderBYLastCreated(issues); + // order by selected filter + switch (orderBY) { + case OrderBY.manual: + orderedIssues = _orderBYManual(issues); + break; + case OrderBY.lastCreated: + orderedIssues = _orderBYLastCreated(issues); + break; + case OrderBY.lastUpdated: + orderedIssues = _orderBYLastUpdated(issues); + break; + case OrderBY.startDate: + orderedIssues = _orderBYStartDate(issues); + break; + case OrderBY.priority: + orderedIssues = _orderBYPriority(issues); + break; + default: + orderedIssues = _orderBYManual(issues); + break; + } + return orderedIssues; + } +} diff --git a/lib/widgets/cycle_card_widget.dart b/lib/widgets/cycle_card_widget.dart index f58a0bfd..7e25115b 100644 --- a/lib/widgets/cycle_card_widget.dart +++ b/lib/widgets/cycle_card_widget.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:lucide_icons/lucide_icons.dart'; -import 'package:plane/bottom_sheets/delete_cycle_sheet.dart'; +import 'package:plane/bottom-sheets/delete_cycle_sheet.dart'; import 'package:plane/provider/cycles_provider.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/theme_provider.dart'; diff --git a/lib/widgets/empty.dart b/lib/widgets/empty.dart index b14e2296..8eb3fda8 100644 --- a/lib/widgets/empty.dart +++ b/lib/widgets/empty.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:plane/bottom_sheets/issues_list_sheet.dart'; +import 'package:plane/bottom-sheets/issues_list_sheet.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/theme_provider.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/CyclesTab/create_cycle.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/IssuesTab/CreateIssue/create_issue.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/ModulesTab/create_module.dart'; -import 'package:plane/screens/MainScreens/Projects/ProjectDetail/Settings/create_label.dart'; -import 'package:plane/screens/MainScreens/Projects/create_page_screen.dart'; -import 'package:plane/screens/MainScreens/Projects/create_project_screen.dart'; +import 'package:plane/screens/project/create_project_screen.dart'; +import 'package:plane/screens/project/cycles/create_cycle.dart'; +import 'package:plane/screens/project/issues/create_issue.dart'; +import 'package:plane/screens/project/modules/create_module.dart'; +import 'package:plane/screens/project/pages/create_page_screen.dart'; +import 'package:plane/screens/project/settings/create_label.dart'; import 'package:plane/screens/create_view_screen.dart'; import 'package:plane/utils/constants.dart'; import 'package:plane/utils/custom_toast.dart'; diff --git a/lib/widgets/issue_card_widget.dart b/lib/widgets/issue_card_widget.dart index b0908b1f..5312328f 100644 --- a/lib/widgets/issue_card_widget.dart +++ b/lib/widgets/issue_card_widget.dart @@ -4,13 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:intl/intl.dart'; import 'package:plane/config/const.dart'; import 'package:plane/provider/provider_list.dart'; -import 'package:plane/utils/constants.dart'; +import 'package:plane/screens/project/issues/issue_detail.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/utils/extensions/string_extensions.dart'; - import 'package:plane/widgets/square_avatar_widget.dart'; - -import '../screens/MainScreens/Projects/ProjectDetail/IssuesTab/issue_detail.dart'; import 'custom_rich_text.dart'; import 'custom_text.dart'; @@ -87,12 +84,12 @@ class _IssueCardWidgetState extends ConsumerState { .watch(ProviderList.myIssuesProvider) .issues .projectView == - ProjectView.list + IssueLayout.list : ref .watch(ProviderList.issuesProvider) .issues .projectView == - ProjectView.list) + IssueLayout.list) ? const EdgeInsets.only(bottom: 1) : const EdgeInsets.only(bottom: 15, right: 5, left: 5, top: 5), decoration: BoxDecoration( @@ -102,12 +99,12 @@ class _IssueCardWidgetState extends ConsumerState { .watch(ProviderList.myIssuesProvider) .issues .projectView == - ProjectView.kanban + IssueLayout.kanban : ref .watch(ProviderList.issuesProvider) .issues .projectView == - ProjectView.kanban) + IssueLayout.kanban) ? [ BoxShadow( blurRadius: 2, @@ -118,28 +115,28 @@ class _IssueCardWidgetState extends ConsumerState { : null, ), child: Container( - width: widget.issueCategory == IssueCategory.cycleIssues && - ref - .watch(ProviderList.issuesProvider) - .issues - .projectView == - ProjectView.list - ? width - : widget.issueCategory == IssueCategory.moduleIssues - ? width - : widget.issueCategory == IssueCategory.cycleIssues - ? width - : widget.issueCategory == IssueCategory.myIssues - ? ref - .watch(ProviderList.myIssuesProvider) - .issues - .issues[widget.listIndex] - .width - : ref - .watch(ProviderList.issuesProvider) - .issues - .issues[0] - .width, + // width: widget.issueCategory == IssueCategory.cycleIssues && + // ref + // .watch(ProviderList.issuesProvider) + // .issues + // .projectView == + // IssueLayout.list + // ? width + // : widget.issueCategory == IssueCategory.moduleIssues + // ? width + // : widget.issueCategory == IssueCategory.cycleIssues + // ? width + // : widget.issueCategory == IssueCategory.myIssues + // ? ref + // .watch(ProviderList.myIssuesProvider) + // .issues + // .issues[widget.listIndex] + // .width + // : ref + // .watch(ProviderList.issuesProvider) + // .issues + // .issues[0] + // .width, padding: const EdgeInsets.only( left: 15.0, right: 10, @@ -149,12 +146,12 @@ class _IssueCardWidgetState extends ConsumerState { .watch(ProviderList.myIssuesProvider) .issues .projectView == - ProjectView.list + IssueLayout.list : ref .watch(ProviderList.issuesProvider) .issues .projectView == - ProjectView.list) + IssueLayout.list) ? listCard() : kanbanCard()), ), @@ -163,9 +160,9 @@ class _IssueCardWidgetState extends ConsumerState { .watch(ProviderList.myIssuesProvider) .issues .projectView == - ProjectView.list + IssueLayout.list : ref.watch(ProviderList.issuesProvider).issues.projectView == - ProjectView.list) + IssueLayout.list) ? Divider( height: 1, thickness: 1, @@ -181,6 +178,7 @@ class _IssueCardWidgetState extends ConsumerState { final themeProvider = ref.read(ProviderList.themeProvider); dynamic provider; final projectProvider = ref.watch(ProviderList.projectProvider); + final labelProvider = ref.watch(ProviderList.labelProvider); if (widget.issueCategory == IssueCategory.cycleIssues) { provider = ref.watch(ProviderList.cyclesProvider); @@ -191,6 +189,8 @@ class _IssueCardWidgetState extends ConsumerState { } else { provider = ref.watch(ProviderList.issuesProvider); } + final issue = provider.issuesResponse[widget.cardIndex]; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -198,16 +198,15 @@ class _IssueCardWidgetState extends ConsumerState { ? Container( margin: const EdgeInsets.only(top: 15), child: CustomRichText( - type: FontStyle.Small, - color: themeProvider.themeManager.placeholderTextColor, - widgets: [ - TextSpan( - text: provider.issuesResponse[widget.cardIndex] - ['project_detail']['identifier']), - TextSpan( - text: - '-${provider.issuesResponse[widget.cardIndex]['sequence_id']}'), - ])) + type: FontStyle.Small, + color: themeProvider.themeManager.placeholderTextColor, + widgets: [ + TextSpan( + text: projectProvider.currentProject['identifier']), + TextSpan(text: '-${issue['sequence_id']}'), + ], + ), + ) : Container(), const SizedBox( height: 10, @@ -236,9 +235,7 @@ class _IssueCardWidgetState extends ConsumerState { ? Container( alignment: Alignment.center, decoration: BoxDecoration( - color: provider.issuesResponse[widget.cardIndex] - ['priority'] == - 'urgent' + color: issue['priority'] == 'urgent' ? Colors.red : null, border: Border.all( @@ -248,34 +245,28 @@ class _IssueCardWidgetState extends ConsumerState { margin: const EdgeInsets.only(right: 5), height: 30, width: 30, - child: provider.issuesResponse[widget.cardIndex]['priority'] == null || - provider.issuesResponse[widget.cardIndex] - ['priority'] == - 'none' + child: issue['priority'] == null || + issue['priority'] == 'none' ? Icon( Icons.do_disturb_alt_outlined, size: 18, color: themeProvider .themeManager.tertiaryTextColor, ) - : provider.issuesResponse[widget.cardIndex] - ['priority'] == - 'urgent' + : issue['priority'] == 'urgent' ? const Icon( Icons.error_outline_rounded, color: Colors.white, size: 18, ) - : provider.issuesResponse[widget.cardIndex] - ['priority'] == - 'high' + : issue['priority'] == 'high' ? const Icon( Icons.signal_cellular_alt, color: Color.fromRGBO(249, 115, 23, 1), size: 18, ) - : provider.issuesResponse[widget.cardIndex]['priority'] == 'medium' + : issue['priority'] == 'medium' ? const Icon( Icons.signal_cellular_alt_2_bar, color: Color.fromRGBO( @@ -292,19 +283,12 @@ class _IssueCardWidgetState extends ConsumerState { width: 0, ), provider.issues.displayProperties.assignee == true - ? (provider.issuesResponse[widget.cardIndex] - ['assignee_details'] != - null && - provider - .issuesResponse[widget.cardIndex] - ['assignee_details'] - .isNotEmpty) + ? (issue['assignee_details'] != null && + issue['assignee_details'].isNotEmpty) ? Container( margin: const EdgeInsets.only(right: 5), child: SquareAvatarWidget( - details: - provider.issuesResponse[widget.cardIndex] - ['assignee_details'], + details: issue['assignee_details'], ), ) : Container( @@ -329,156 +313,101 @@ class _IssueCardWidgetState extends ConsumerState { width: 0, ), (provider.issues.displayProperties.label == true && - provider.issuesResponse[widget.cardIndex] - ['label_details'] != - null && - provider - .issuesResponse[widget.cardIndex] - ['label_details'] - .isNotEmpty) - ? SizedBox( - height: 30, - child: provider - .issuesResponse[widget.cardIndex] - ['label_details'] - .length > - 1 - ? Container( - width: 80, - margin: const EdgeInsets.only(right: 5), - padding: const EdgeInsets.only( - left: 8, - right: 8, - ), - decoration: BoxDecoration( - border: Border.all( - color: themeProvider - .themeManager.borderSubtle01Color, - ), - borderRadius: BorderRadius.circular(4)), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - CircleAvatar( - radius: 5, - backgroundColor: provider - .issuesResponse[widget.cardIndex] - ['label_details'][0]['color'] - .toString() - .toColor(), - ), - const SizedBox( - width: 5, - ), - CustomText( - '${provider.issuesResponse[widget.cardIndex]['label_details'].length} Labels', - type: FontStyle.XSmall, - height: 1, - color: themeProvider - .themeManager.tertiaryTextColor, - ), - ], - ), - ) - : Container( - margin: const EdgeInsets.only(right: 5), - child: ListView.builder( - padding: EdgeInsets.zero, - scrollDirection: Axis.horizontal, - physics: - const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: provider - .issuesResponse[widget.cardIndex] - ['label_details'] - .length, - itemBuilder: (context, idx) { - return Container( - height: 30, - padding: const EdgeInsets.only( - left: 8, - right: 8, - ), - decoration: BoxDecoration( - border: Border.all( - color: themeProvider - .themeManager - .borderSubtle01Color), - borderRadius: - BorderRadius.circular(4)), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - CircleAvatar( - radius: 5, - backgroundColor: provider - .issuesResponse[ - widget.cardIndex] - ['label_details'][idx] - ['color'] - .toString() - .toColor(), - ), - const SizedBox( - width: 5, - ), - Container( - constraints: - const BoxConstraints( - maxWidth: 120), - child: CustomText( - provider.issuesResponse[ - widget.cardIndex] - ['label_details'][idx] - ['name'], - type: FontStyle.XSmall, - overflow: - TextOverflow.ellipsis, - maxLines: 1, - height: 1, - color: themeProvider - .themeManager - .tertiaryTextColor, - ), - ), - ], - ), - ); - }, - ), - ), - ) - : (provider.issues.displayProperties.label == true && - provider - .issuesResponse[widget.cardIndex] - ['label_details'] - .isEmpty) + issue['label_ids'].isNotEmpty) + ? issue['label_ids'].length == 1 ? Container( - height: 30, + width: 80, margin: const EdgeInsets.only(right: 5), - alignment: Alignment.center, padding: const EdgeInsets.only( left: 8, right: 8, ), decoration: BoxDecoration( border: Border.all( - color: themeProvider - .themeManager.borderSubtle01Color), + color: themeProvider + .themeManager.borderSubtle01Color, + ), borderRadius: BorderRadius.circular(4)), - child: CustomText( - 'No Label', - type: FontStyle.XSmall, - height: 1, - color: themeProvider - .themeManager.tertiaryTextColor, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircleAvatar( + radius: 5, + backgroundColor: + (labelProvider.projectLabels[ + issue['label_ids'][0]])! + .color + .toColor(), + ), + const SizedBox( + width: 5, + ), + CustomText( + '${issue['label_ids'].length} Labels', + type: FontStyle.XSmall, + height: 1, + color: themeProvider + .themeManager.tertiaryTextColor, + ), + ], ), ) : Container( - width: 0, - ), + margin: const EdgeInsets.only(right: 5), + child: ListView.builder( + padding: EdgeInsets.zero, + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: issue['label_ids'].length, + itemBuilder: (context, idx) { + final label = labelProvider + .projectLabels.values + .elementAt(idx); + return Container( + height: 30, + padding: const EdgeInsets.only( + left: 8, + right: 8, + ), + decoration: BoxDecoration( + border: Border.all( + color: themeProvider.themeManager + .borderSubtle01Color), + borderRadius: + BorderRadius.circular(4)), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + CircleAvatar( + radius: 5, + backgroundColor: + label.color.toColor(), + ), + const SizedBox( + width: 5, + ), + Container( + constraints: const BoxConstraints( + maxWidth: 120), + child: CustomText( + label.name, + type: FontStyle.XSmall, + overflow: TextOverflow.ellipsis, + maxLines: 1, + height: 1, + color: themeProvider.themeManager + .tertiaryTextColor, + ), + ), + ], + ), + ); + }, + ), + ) + : Container(), provider.issues.displayProperties.dueDate == true ? Container( height: 30, @@ -495,15 +424,11 @@ class _IssueCardWidgetState extends ConsumerState { ), borderRadius: BorderRadius.circular(4)), child: CustomText( - provider.issuesResponse[widget.cardIndex] - ['start_date'] != - null + issue['start_date'] != null ? //convert yyyy-mm-dd to Aug 12, 2021 DateFormat('MMM dd, yyyy').format( - DateTime.parse(provider - .issuesResponse[widget.cardIndex] - ['start_date'])) + DateTime.parse(issue['start_date'])) : 'Start date', type: FontStyle.XSmall, height: 1, @@ -526,15 +451,11 @@ class _IssueCardWidgetState extends ConsumerState { .themeManager.borderSubtle01Color), borderRadius: BorderRadius.circular(4)), child: CustomText( - provider.issuesResponse[widget.cardIndex] - ['target_date'] != - null + issue['target_date'] != null ? //convert yyyy-mm-dd to Aug 12, 2021 DateFormat('MMM dd, yyyy').format( - DateTime.parse(provider - .issuesResponse[widget.cardIndex] - ['target_date'])) + DateTime.parse(issue['target_date'])) : 'Due date', type: FontStyle.XSmall, height: 1, @@ -573,17 +494,9 @@ class _IssueCardWidgetState extends ConsumerState { width: 5, ), CustomText( - provider.issuesResponse[widget.cardIndex] - ['sub_issues_count'] != - '' && - provider.issuesResponse[ - widget.cardIndex] - ['sub_issues_count'] != - null - ? provider - .issuesResponse[widget.cardIndex] - ['sub_issues_count'] - .toString() + issue['sub_issues_count'] != '' && + issue['sub_issues_count'] != null + ? issue['sub_issues_count'].toString() : '0', type: FontStyle.XSmall, height: 1, @@ -621,16 +534,9 @@ class _IssueCardWidgetState extends ConsumerState { width: 5, ), CustomText( - provider.issuesResponse[widget.cardIndex] - ['link_count'] != - '' && - provider.issuesResponse[widget - .cardIndex]['link_count'] != - null - ? provider - .issuesResponse[widget.cardIndex] - ['link_count'] - .toString() + issue['link_count'] != '' && + issue['link_count'] != null + ? issue['link_count'].toString() : '0', type: FontStyle.XSmall, height: 1, @@ -669,17 +575,9 @@ class _IssueCardWidgetState extends ConsumerState { width: 5, ), CustomText( - provider.issuesResponse[widget.cardIndex] - ['attachment_count'] != - '' && - provider.issuesResponse[ - widget.cardIndex] - ['attachment_count'] != - null - ? provider - .issuesResponse[widget.cardIndex] - ['attachment_count'] - .toString() + issue['attachment_count'] != '' && + issue['attachment_count'] != null + ? issue['attachment_count'].toString() : '0', type: FontStyle.XSmall, height: 1, @@ -719,13 +617,8 @@ class _IssueCardWidgetState extends ConsumerState { width: 5, ), CustomText( - provider.issuesResponse[widget.cardIndex] - ['estimate_point'] != - '' && - provider.issuesResponse[ - widget.cardIndex] - ['estimate_point'] != - null + issue['estimate_point'] != '' && + issue['estimate_point'] != null ? ref .read(ProviderList.estimatesProvider) .estimates @@ -735,8 +628,7 @@ class _IssueCardWidgetState extends ConsumerState { .currentProject['estimate']; })['points'].firstWhere((element) { return element['key'] == - provider.issuesResponse[widget - .cardIndex]['estimate_point']; + issue['estimate_point']; })['value'] : 'Estimate', type: FontStyle.XSmall, @@ -757,6 +649,7 @@ class _IssueCardWidgetState extends ConsumerState { Widget listCard() { final themeProvider = ref.read(ProviderList.themeProvider); + final projectProvider = ref.watch(ProviderList.projectProvider); dynamic provider; if (widget.issueCategory == IssueCategory.cycleIssues) { provider = ref.watch(ProviderList.cyclesProvider); @@ -835,8 +728,7 @@ class _IssueCardWidgetState extends ConsumerState { color: themeProvider.themeManager.placeholderTextColor, widgets: [ TextSpan( - text: provider.issuesResponse[widget.cardIndex] - ['project_detail']['identifier'], + text: projectProvider.currentProject['identifier'], style: TextStyle( color: themeProvider .themeManager.placeholderTextColor, diff --git a/pubspec.yaml b/pubspec.yaml index b4734e5f..69f68095 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,11 +74,14 @@ dependencies: dartz: ^0.10.1 upgrader: ^8.1.0 flutter_native_splash: ^2.3.2 + freezed_annotation: ^2.4.1 dev_dependencies: - build_runner: ^2.1.7 + freezed: ^2.4.6 + build_runner: ^2.4.8 + json_serializable: ^6.7.1 flutter_lints: ^2.0.0 - + flutter_test: sdk: flutter integration_test: diff --git a/test/providers/dashboard/dash_board_screen_test.dart b/test/providers/dashboard/dash_board_screen_test.dart index f2444a79..3205289a 100644 --- a/test/providers/dashboard/dash_board_screen_test.dart +++ b/test/providers/dashboard/dash_board_screen_test.dart @@ -4,18 +4,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:plane/bottom_sheets/global_search_sheet.dart'; -import 'package:plane/bottom_sheets/select_workspace.dart'; +import 'package:plane/bottom-sheets/global_search_sheet.dart'; +import 'package:plane/bottom-sheets/select_workspace.dart'; import 'package:plane/models/user_profile_model.dart'; -import 'package:plane/models/workspace_model.dart'; +import 'package:plane/models/Workspace/workspace_model.dart'; import 'package:plane/provider/dashboard_provider.dart'; import 'package:plane/provider/profile_provider.dart'; import 'package:plane/provider/projects_provider.dart'; import 'package:plane/provider/provider_list.dart'; import 'package:plane/provider/workspace_provider.dart'; import 'package:plane/repository/dashboard_service.dart'; -import 'package:plane/screens/MainScreens/Home/Dashboard/dash_board_screen.dart'; -import 'package:plane/screens/on_boarding/auth/setup_workspace.dart'; +import 'package:plane/screens/dashboard/dash_board_screen.dart'; +import 'package:plane/screens/onboarding/auth/setup_workspace.dart'; import 'package:plane/services/dio_service.dart'; import 'package:plane/utils/enums.dart'; import 'package:plane/widgets/custom_text.dart';