diff --git a/README.md b/README.md index 95aeaf8a..97815799 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ Read more at: https://solvro.pwr.edu.pl/portfolio/to-pwr/. ## Links +[![docs.solvro.pl](https://i.imgur.com/fuV0gra.png)](https://docs.solvro.pl/guides/flutter-mobile) + - https://www.figma.com/file/33ofdGYbBzWvDi2MabxIc1/ToPWR-(imported)?type=design&node-id=2%3A2091&mode=design&t=qILflhzpbN8xW8F6-1 - https://solvro.pwr.edu.pl/blog/fix-flutter-android-back-btn @@ -154,7 +156,7 @@ Don't worry if you've forgotten about the steps, automatic gh action will run th ### Github Solvro Handbook 🔥 -https://docs.google.com/document/d/1Sb5lYqYLnYuecS1Essn3YwietsbuLPCTsTuW0EMpG5o/edit?usp=sharing +https://docs.solvro.pl/guides/github ### SSH diff --git a/assets/animations/sks_closed.json b/assets/animations/sks_closed.json new file mode 100644 index 00000000..664b2cb5 --- /dev/null +++ b/assets/animations/sks_closed.json @@ -0,0 +1 @@ +{"v":"5.5.5","fr":30,"ip":0,"op":60,"w":1920,"h":1920,"nm":"general","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"forchetta contorni","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":11.795,"s":[-44]},{"t":23.0000009368092,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":11.795,"s":[1141.996,962.459,0],"to":[-20,-0.667,0],"ti":[20,0.667,0]},{"t":23.0000009368092,"s":[1021.9960000000001,958.4589999999998,0]}],"ix":2},"a":{"a":0,"k":[391.246,401.786,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[11.979,-12.302],[0,0],[46.202,28.114],[0,0],[0,0],[0,0],[-39.784,40.856],[0,0],[-11.979,-12.302],[11.979,-12.303],[0,0],[-5.99,-6.151],[-5.991,6.151],[0,0],[-11.979,-12.302],[11.98,-12.302],[0,0],[-5.989,-6.151],[-5.991,6.151],[0,0],[-11.979,-12.302]],"o":[[0,0],[-39.784,40.856],[0,0],[0,0],[0,0],[-27.376,-47.447],[0,0],[11.979,-12.302],[11.979,12.303],[0,0],[-5.99,6.151],[5.989,6.151],[0,0],[11.979,-12.302],[11.98,12.303],[0,0],[-5.989,6.151],[5.989,6.151],[0,0],[11.979,-12.302],[11.979,12.302]],"v":[[379.017,-213.506],[143.734,28.119],[-2.997,47.01],[-348.217,401.535],[-390.996,357.602],[-45.776,3.078],[-27.381,-147.608],[207.902,-389.233],[250.681,-389.233],[250.681,-345.301],[42.143,-131.142],[42.143,-109.176],[63.533,-109.176],[272.07,-323.335],[314.849,-323.335],[314.849,-279.403],[106.311,-65.244],[106.311,-43.278],[127.701,-43.278],[336.238,-257.437],[379.017,-257.437]],"c":true},"ix":2},"nm":"Tracciato 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5569,0.2275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Riempimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[391.246,401.786],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Trasformazione"}],"nm":"Gruppo 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600.000024438501,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Livello forma 2","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1121.945,308,0],"ix":2},"a":{"a":0,"k":[-318.055,-652,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[165,0,100]},{"t":12.3850005044514,"s":[165,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[747.891,1277.953],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Tracciato rettangolo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.1255,0.1529,0.1725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":64,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Traccia 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5569,0.2275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Riempimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-318.055,-13.023],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Trasformazione"}],"nm":"Rettangolo 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600.000024438501,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"coltello contorni","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":11.795,"s":[45]},{"t":23.0000009368092,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":11.795,"s":[755.866,986.576,0],"to":[20,0,0],"ti":[-20,0,0]},{"t":23.0000009368092,"s":[875.8659999999999,986.576,0]}],"ix":2},"a":{"a":0,"k":[389.749,373.668,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-94.542,97.09],[0,0],[0,0],[0,0],[20.964,-5.709],[63.312,65.018]],"o":[[0,0],[0,0],[0,0],[-19.251,11.86],[-80.852,22.406],[-94.542,-97.091]],"v":[[-294.956,-373.418],[389.498,329.486],[346.72,373.418],[-1.066,16.257],[-61.386,42.175],[-294.956,-21.963]],"c":true},"ix":2},"nm":"Tracciato 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5569,0.2275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Riempimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[389.748,373.668],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Trasformazione"}],"nm":"Gruppo 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600.000024438501,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Livello forma 1","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[641.945,1585.953,0],"ix":2},"a":{"a":0,"k":[-318.055,625.953,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[165,0,100]},{"t":12.3850005044514,"s":[165,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[747.891,1277.953],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Tracciato rettangolo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5569,0.2275,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":64,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Traccia 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5569,0.2275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Riempimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-318.055,-13.023],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Trasformazione"}],"nm":"Rettangolo 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600.000024438501,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Livello forma 9","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":31,"s":[100]},{"t":58.0000023623884,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[960,960,0],"ix":2},"a":{"a":0,"k":[8.219,100.219,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":31,"s":[0,0,100]},{"t":58.0000023623884,"s":[150,150,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[1096.438,1096.438],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracciato ellisse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9922,0.949,0.9922,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Riempimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[8.219,100.219],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Trasformazione"}],"nm":"Ellisse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600.000024438501,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/lib/config/nav_bar_config.dart b/lib/config/nav_bar_config.dart index 2b401628..a49b20ad 100644 --- a/lib/config/nav_bar_config.dart +++ b/lib/config/nav_bar_config.dart @@ -5,6 +5,7 @@ import "package:flutter/material.dart"; import "../features/bottom_nav_bar/bottom_nav_bar_icon_icons.icons.dart"; import "../features/navigator/app_router.dart"; import "../features/parkings_view/widgets/parkings_icons.icons.dart"; +import "../utils/context_extensions.dart"; enum NavBarEnum { home(BottomNavBarIcon.home_icon, 26, "Home"), @@ -43,4 +44,19 @@ extension IsRouteATabViewX on PageRouteInfo { NavBarEnum? get tabBarEnum => routeName.tabBarEnum; bool get isTabView => routeName.isTabView; + + String? getFormatedRouteName(BuildContext context) { + return switch (routeName) { + HomeRoute.name => context.localize.home_screen, + NavigationTabRoute.name => context.localize.other_view, + DepartmentsRoute.name => context.localize.departments, + SksMenuRoute.name => context.localize.sks_menu, + ScienceClubsRoute.name => context.localize.scientific_cirlces, + GuideRoute.name => context.localize.guide, + BuildingsRoute.name => context.localize.buildings_title, + ParkingsRoute.name => context.localize.parkings_title, + DepartmentDetailRoute.name => context.localize.department, + _ => null, + }; + } } diff --git a/lib/config/ui_config.dart b/lib/config/ui_config.dart index 080af28c..64d44bed 100644 --- a/lib/config/ui_config.dart +++ b/lib/config/ui_config.dart @@ -147,10 +147,6 @@ abstract class AboutUsConfig { static const borderRadius = 8.0; static const iconPadding = 10.0; static const photoSize = 92.0; - - static const dialogHorizontalPadding = 14.0; - static const dialogVerticalPadding = 20.0; - static const dialogButtonFontSize = 16.0; static const dialogTitleFontSize = 24.0; } @@ -201,6 +197,7 @@ abstract class SksConfig { static const sizedBoxWidth = 5.0; static const radius = 8.0; static const innerPadding = EdgeInsets.symmetric(horizontal: 8, vertical: 4); + static const outerPaddingLarge = EdgeInsets.only(right: 24, bottom: 2); static const outerPadding = EdgeInsets.only(right: 12, bottom: 2); } @@ -218,3 +215,9 @@ abstract class DigitalGuideConfig { static const heightBig = 24.0; static const heightHuge = 48.0; } + +abstract class AlertDialogConfig { + static const horizontalPadding = 14.0; + static const verticalPadding = 20.0; + static const buttonFontSize = 16.0; +} diff --git a/lib/features/about_us_view/about_us_view.dart b/lib/features/about_us_view/about_us_view.dart index 9fc787c2..c0b36f6e 100644 --- a/lib/features/about_us_view/about_us_view.dart +++ b/lib/features/about_us_view/about_us_view.dart @@ -24,7 +24,7 @@ class AboutUsView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: DetailViewAppBar(title: context.localize.guide), + appBar: DetailViewAppBar(), body: const _AboutUsView(), ); } diff --git a/lib/features/about_us_view/utils/custom_license_dialog.dart b/lib/features/about_us_view/utils/custom_license_dialog.dart index 1fe88c00..2403e65d 100644 --- a/lib/features/about_us_view/utils/custom_license_dialog.dart +++ b/lib/features/about_us_view/utils/custom_license_dialog.dart @@ -3,6 +3,7 @@ import "package:flutter/material.dart"; import "../../../config/ui_config.dart"; import "../../../theme/app_theme.dart"; import "../../../utils/context_extensions.dart"; +import "../../../widgets/my_alert_dialog.dart"; Future showCustomLicenseDialog({ required BuildContext context, @@ -11,86 +12,28 @@ Future showCustomLicenseDialog({ required String applicationLegalese, required Widget applicationIcon, }) async { - await showDialog( + await showCustomDialog( context: context, - builder: (BuildContext context) { - return _AlertDialog( + onConfirmTapped: (context) { + Navigator.of(context).pop(); + showLicensePage( + context: context, applicationName: applicationName, + applicationIcon: applicationIcon, applicationVersion: applicationVersion, applicationLegalese: applicationLegalese, - applicationIcon: applicationIcon, ); }, + confirmText: context.localize.show_license, + dialogContent: _DialogContent( + applicationName: applicationName, + applicationVersion: applicationVersion, + applicationIcon: applicationIcon, + applicationLegalese: applicationLegalese, + ), ); } -class _AlertDialog extends StatelessWidget { - final String applicationName; - final String applicationVersion; - final String applicationLegalese; - final Widget applicationIcon; - - const _AlertDialog({ - required this.applicationName, - required this.applicationVersion, - required this.applicationLegalese, - required this.applicationIcon, - }); - - @override - Widget build(BuildContext context) { - return AlertDialog( - content: Padding( - padding: const EdgeInsets.symmetric( - horizontal: AboutUsConfig.dialogHorizontalPadding, - vertical: AboutUsConfig.dialogVerticalPadding, - ), - child: _DialogContent( - applicationName: applicationName, - applicationVersion: applicationVersion, - applicationIcon: applicationIcon, - applicationLegalese: applicationLegalese, - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - child: Text( - context.localize.show_license, - style: context.textTheme.bodyOrange.copyWith( - fontSize: AboutUsConfig.dialogButtonFontSize, - ), - ), - onPressed: () { - showLicensePage( - context: context, - applicationName: applicationName, - applicationIcon: applicationIcon, - applicationVersion: applicationVersion, - applicationLegalese: applicationLegalese, - ); - }, - ), - TextButton( - child: Text( - context.localize.close, - style: context.textTheme.body.copyWith( - fontSize: AboutUsConfig.dialogButtonFontSize, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ), - ], - ); - } -} - class _DialogContent extends StatelessWidget { const _DialogContent({ required this.applicationName, diff --git a/lib/features/buildings_view/building_tile.dart b/lib/features/buildings_view/building_tile.dart index 83b17804..4ea9574d 100644 --- a/lib/features/buildings_view/building_tile.dart +++ b/lib/features/buildings_view/building_tile.dart @@ -8,7 +8,7 @@ import "../../utils/context_extensions.dart"; import "../../widgets/wide_tile_card.dart"; import "controllers.dart"; import "model/building_model.dart"; -import "utils.dart"; +import "utils/utils.dart"; class BuildingTile extends ConsumerWidget { const BuildingTile( diff --git a/lib/features/buildings_view/controllers.dart b/lib/features/buildings_view/controllers.dart index 76930717..e1fb560f 100644 --- a/lib/features/buildings_view/controllers.dart +++ b/lib/features/buildings_view/controllers.dart @@ -8,9 +8,10 @@ import "../map_view/controllers/bottom_sheet_controller.dart"; import "../map_view/controllers/controllers_set.dart"; import "../map_view/controllers/map_controller.dart"; import "../map_view/controllers/map_data_controller.dart"; +import "./utils/building_codes_utils.dart"; import "model/building_model.dart"; import "repository/buildings_repository.dart"; -import "utils.dart"; +import "utils/utils.dart"; part "controllers.g.dart"; @@ -42,9 +43,21 @@ class BuildingsViewController extends _$BuildingsViewController @override bool filterMethod(BuildingModel item, String filterStr) { - return item.name.containsBuildingCode(filterStr) || - item.addres.containsLowerCase(filterStr) || - item.naturalName.containsLowerCase(filterStr); + switch (filterStr.length) { + case 0: + return true; + case 1: + if (ref.isStringABuildingCode(filterStr)) { + return item.name.containsBuildingCode(filterStr); + } else { + return item.addres.containsLowerCase(filterStr) || + item.naturalName.containsLowerCase(filterStr); + } + default: + return item.name.containsBuildingCode(filterStr) || + item.addres.containsLowerCase(filterStr) || + item.naturalName.containsLowerCase(filterStr); + } } } diff --git a/lib/features/buildings_view/model/building_model.dart b/lib/features/buildings_view/model/building_model.dart index 11b2ea24..a78f19de 100644 --- a/lib/features/buildings_view/model/building_model.dart +++ b/lib/features/buildings_view/model/building_model.dart @@ -1,5 +1,6 @@ import "package:latlong2/latlong.dart"; +import "../../../config/map_view_config.dart"; import "../../map_view/controllers/controllers_set.dart"; import "../repository/buildings_repository.dart"; @@ -21,4 +22,15 @@ class BuildingModel extends Building implements GoogleNavigable { String? get addressFormatted => addres?.replaceFirst(",", "\n").replaceAll("\n ", "\n"); + + String? get parseBuildingCode { + final List separatedBuildingName = + name.split(BuildingSearchConfig.buildingCodeSeperator); + + if (separatedBuildingName.length < 2) { + return null; + } else { + return separatedBuildingName[0]; + } + } } diff --git a/lib/features/buildings_view/repository/buildings_repository.dart b/lib/features/buildings_view/repository/buildings_repository.dart index 625e0e93..463f2818 100644 --- a/lib/features/buildings_view/repository/buildings_repository.dart +++ b/lib/features/buildings_view/repository/buildings_repository.dart @@ -6,7 +6,7 @@ import "../../../../config/ttl_config.dart"; import "../../../api_base/query_adapter.dart"; import "../../../utils/ilist_nonempty.dart"; import "../model/building_model.dart"; -import "../utils.dart"; +import "../utils/utils.dart"; import "getBuildings.graphql.dart"; part "buildings_repository.g.dart"; diff --git a/lib/features/buildings_view/utils/building_codes_utils.dart b/lib/features/buildings_view/utils/building_codes_utils.dart new file mode 100644 index 00000000..1c588699 --- /dev/null +++ b/lib/features/buildings_view/utils/building_codes_utils.dart @@ -0,0 +1,27 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:riverpod_annotation/riverpod_annotation.dart"; + +import "../../../utils/contains_number.dart"; +import "../repository/buildings_repository.dart"; + +part "building_codes_utils.g.dart"; + +@riverpod +Future> allBuildingsCodesLowerCase(Ref ref) async { + final buildings = await ref.watch(buildingsRepositoryProvider.future); + return buildings + .map((e) => e.parseBuildingCode?.toLowerCase()) + .whereType() + .toISet(); +} + +extension IsStringABuildingCodeX on Ref { + bool isStringABuildingCode(String potentialBuildingCode) { + final allLowerCaseBuildingsCodes = + watch(allBuildingsCodesLowerCaseProvider).requireValue; + return allLowerCaseBuildingsCodes + .contains(potentialBuildingCode.toLowerCase()) || + potentialBuildingCode.containsNumber(); + } +} diff --git a/lib/features/buildings_view/utils.dart b/lib/features/buildings_view/utils/utils.dart similarity index 88% rename from lib/features/buildings_view/utils.dart rename to lib/features/buildings_view/utils/utils.dart index 7a9c1cbc..e792c108 100644 --- a/lib/features/buildings_view/utils.dart +++ b/lib/features/buildings_view/utils/utils.dart @@ -1,10 +1,10 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/widgets.dart"; -import "../../config/map_view_config.dart"; -import "../../utils/contains_lower_case.dart"; -import "../../utils/context_extensions.dart"; -import "model/building_model.dart"; +import "../../../config/map_view_config.dart"; +import "../../../utils/contains_lower_case.dart"; +import "../../../utils/context_extensions.dart"; +import "../model/building_model.dart"; extension ContainsCaseUnsensitiveX on String? { bool containsBuildingCode(String buildingCode) { diff --git a/lib/features/department_detail_view/department_detail_view.dart b/lib/features/department_detail_view/department_detail_view.dart index c419484f..83bbe0ba 100644 --- a/lib/features/department_detail_view/department_detail_view.dart +++ b/lib/features/department_detail_view/department_detail_view.dart @@ -28,7 +28,7 @@ class DepartmentDetailView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(departmentDetailsRepositoryProvider(id)); return Scaffold( - appBar: DetailViewAppBar(title: context.localize.departments), + appBar: DetailViewAppBar(), body: switch (state) { AsyncError(:final error) => MyErrorWidget(error), AsyncValue(:final DepartmentDetails value) => CustomScrollView( diff --git a/lib/features/departments_view/departments_view.dart b/lib/features/departments_view/departments_view.dart index 558d1b31..ab7d7fce 100644 --- a/lib/features/departments_view/departments_view.dart +++ b/lib/features/departments_view/departments_view.dart @@ -37,7 +37,6 @@ class _DepartmentsView extends ConsumerWidget { return Scaffold( appBar: SearchBoxAppBar( addLeadingPopButton: true, - leadingButtonTitle: context.localize.other_view, context, title: context.localize.departments, onQueryChanged: ref diff --git a/lib/features/guide_detail_view/guide_detail_view.dart b/lib/features/guide_detail_view/guide_detail_view.dart index b114b379..182b3aa3 100644 --- a/lib/features/guide_detail_view/guide_detail_view.dart +++ b/lib/features/guide_detail_view/guide_detail_view.dart @@ -31,7 +31,7 @@ class GuideDetailView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: DetailViewAppBar(title: context.localize.guide), + appBar: DetailViewAppBar(), body: _GuideDetailDataView(id: id), ); } diff --git a/lib/features/guide_view/guide_view.dart b/lib/features/guide_view/guide_view.dart index b3cf7c0f..b0da88a3 100644 --- a/lib/features/guide_view/guide_view.dart +++ b/lib/features/guide_view/guide_view.dart @@ -6,6 +6,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "../../../../widgets/my_error_widget.dart"; import "../../config/ui_config.dart"; import "../../utils/context_extensions.dart"; +import "../../utils/launch_url_util.dart"; import "../../widgets/search_box_app_bar.dart"; import "../../widgets/wide_tile_card.dart"; import "../departments_view/widgets/departments_view_loading.dart"; @@ -58,7 +59,10 @@ class _GuideViewContent extends ConsumerWidget { AsyncValue(:final IList value) => GuideGrid( children: [ for (final item in value) GuideTile(item), - const _GuideInfo(), + _GuideInfo( + emailAddress: "kn.solvro@pwr.edu.pl", + subject: context.localize.guide_subject_default_content, + ), ].lock, ), _ => const Padding( @@ -69,14 +73,31 @@ class _GuideViewContent extends ConsumerWidget { } } -class _GuideInfo extends StatelessWidget { - const _GuideInfo(); +class _GuideInfo extends ConsumerWidget { + final String emailAddress; + final String? subject; + late final Uri emailLaunchUri; + + _GuideInfo({ + required this.emailAddress, + this.subject, + }) { + emailLaunchUri = Uri( + scheme: "mailto", + path: emailAddress, + query: "subject=$subject", + ); + } @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return WideTileCard( title: context.localize.hi_student, - subtitle: context.localize.guide_development_info, + subtitle: context.localize.guide_ideas_info, + secondSubtitle: context.localize.guide_click_here, + onTap: () async { + await ref.launch(emailLaunchUri.toString()); + }, ); } } diff --git a/lib/features/home_view/home_view.dart b/lib/features/home_view/home_view.dart index 0266a27d..d0f1eef7 100644 --- a/lib/features/home_view/home_view.dart +++ b/lib/features/home_view/home_view.dart @@ -23,7 +23,7 @@ class HomeView extends StatelessWidget { final sections = [ const AcademicCalendarConsumer(), const Padding( - padding: EdgeInsets.only(top: 24, bottom: 8), + padding: EdgeInsets.only(top: 12, bottom: 4), child: NavActionsSection(), ), const ScienceClubsSection(), @@ -46,7 +46,6 @@ class HomeView extends StatelessWidget { padding: EdgeInsets.only( left: horizontalPadding, // Align with the top bar right: safeAreaInsets.right, - bottom: HomeViewConfig.bottomPadding, ), child: KeepAliveHomeViewProviders( child: ListView.separated( diff --git a/lib/features/home_view/widgets/buildings_section/buildings_section.dart b/lib/features/home_view/widgets/buildings_section/buildings_section.dart index 2a1f888a..5ef96c76 100644 --- a/lib/features/home_view/widgets/buildings_section/buildings_section.dart +++ b/lib/features/home_view/widgets/buildings_section/buildings_section.dart @@ -37,9 +37,11 @@ class _BuildingsList extends ConsumerWidget { return switch (state) { AsyncError(:final error) => MyErrorWidget(error), AsyncValue(:final IList value) => SmallHorizontalPadding( - child: SizedBox( - height: 120, - child: _DataListBuildingsTiles(value), + child: MediumBottomPadding( + child: SizedBox( + height: 120, + child: _DataListBuildingsTiles(value), + ), ), ), _ => const MediumLeftPadding( diff --git a/lib/features/home_view/widgets/nav_actions_section.dart b/lib/features/home_view/widgets/nav_actions_section.dart index f181cfbf..a6571438 100644 --- a/lib/features/home_view/widgets/nav_actions_section.dart +++ b/lib/features/home_view/widgets/nav_actions_section.dart @@ -60,7 +60,7 @@ class _NavActionButton extends StatelessWidget { child: Ink( decoration: BoxDecoration( shape: BoxShape.circle, - gradient: context.colorTheme.toPwrGradient, + color: context.colorTheme.orangePomegranade, ), child: InkWell( onTap: onTap, diff --git a/lib/features/home_view/widgets/paddings.dart b/lib/features/home_view/widgets/paddings.dart index 8acb0a2f..908863a9 100644 --- a/lib/features/home_view/widgets/paddings.dart +++ b/lib/features/home_view/widgets/paddings.dart @@ -29,3 +29,12 @@ class MediumHorizontalPadding extends Padding { ), ); } + +class MediumBottomPadding extends Padding { + const MediumBottomPadding({super.key, super.child}) + : super( + padding: const EdgeInsets.only( + bottom: HomeViewConfig.paddingMedium, + ), + ); +} diff --git a/lib/features/home_view/widgets/science_clubs_section.dart b/lib/features/home_view/widgets/science_clubs_section.dart index 008c08b4..b7b57859 100644 --- a/lib/features/home_view/widgets/science_clubs_section.dart +++ b/lib/features/home_view/widgets/science_clubs_section.dart @@ -9,6 +9,7 @@ import "../../../utils/context_extensions.dart"; import "../../../widgets/big_preview_card.dart"; import "../../../widgets/my_error_widget.dart"; import "../../../widgets/subsection_header.dart"; + import "../../navigator/utils/navigation_commands.dart"; import "../../science_clubs_view/repository/science_clubs_repository.dart"; import "loading_widgets/big_scrollable_section_loading.dart"; @@ -112,8 +113,8 @@ class _BuildScienceClubCard extends StatelessWidget { directusUrl: (sciClub.useCoverAsPreviewPhoto ?? false) ? sciClub.cover?.filename_disk : sciClub.logo?.filename_disk, - onClick: () async => ref.navigateSciClubsDetail(sciClub.id), showBadge: sciClub.source == ScienceClubsViewConfig.source, + onClick: () async => ref.navigateSciClubsDetail(sciClub.id), ); } } diff --git a/lib/features/in_app_review/business/in_app_rating_service.dart b/lib/features/in_app_review/business/in_app_rating_service.dart index b7a54be5..bab0eaa9 100644 --- a/lib/features/in_app_review/business/in_app_rating_service.dart +++ b/lib/features/in_app_review/business/in_app_rating_service.dart @@ -19,8 +19,8 @@ class InAppRatingService { return await ref.read(getUsageDaysUseCaseProvider.future); } - SharedPreferences get _sharedPrefs => - ref.read(sharedPreferencesSingletonProvider).requireValue; + Future get _sharedPrefs => + ref.read(sharedPreferencesSingletonProvider.future); Future requestReviewIfNeeded() async { if (await _canRequestReview()) { @@ -30,11 +30,12 @@ class InAppRatingService { } Future _canRequestReview() async { - final lastReviewPrompt = _sharedPrefs + final sharedPreferences = await _sharedPrefs; + final lastReviewPrompt = sharedPreferences .getTimestamp(InAppReviewConfig.lastReviewPromptDateAsString) .daysLeftFromNow; final reviewCount = - _sharedPrefs.getInt(InAppReviewConfig.reviewCountKey) ?? 0; + sharedPreferences.getInt(InAppReviewConfig.reviewCountKey) ?? 0; final usageDays = await _getUsageDays(); if (usageDays > 2 && @@ -47,13 +48,14 @@ class InAppRatingService { } Future _updatePreferences() async { - await _sharedPrefs.saveTimestamp( + final sharedPreferences = await _sharedPrefs; + await sharedPreferences.saveTimestamp( InAppReviewConfig.lastReviewPromptDateAsString, Timestamp.now(), ); - await _sharedPrefs.setInt( + await sharedPreferences.setInt( InAppReviewConfig.reviewCountKey, - (_sharedPrefs.getInt(InAppReviewConfig.reviewCountKey) ?? 0) + 1, + (sharedPreferences.getInt(InAppReviewConfig.reviewCountKey) ?? 0) + 1, ); } diff --git a/lib/features/map_view/controllers/map_controller.dart b/lib/features/map_view/controllers/map_controller.dart index 652eaf30..3169191f 100644 --- a/lib/features/map_view/controllers/map_controller.dart +++ b/lib/features/map_view/controllers/map_controller.dart @@ -17,7 +17,9 @@ class MyMapController { final _controllerCompleter = Completer(); Future get _controller => _controllerCompleter.future; void completeController(AnimatedMapController controller) { - _controllerCompleter.complete(controller); + if (!_controllerCompleter.isCompleted) { + _controllerCompleter.complete(controller); + } } Future zoomOnMarker(T item) async { diff --git a/lib/features/navigation_tab_view/navigation_tab_view.dart b/lib/features/navigation_tab_view/navigation_tab_view.dart index 20129b19..6c16fb26 100644 --- a/lib/features/navigation_tab_view/navigation_tab_view.dart +++ b/lib/features/navigation_tab_view/navigation_tab_view.dart @@ -48,9 +48,7 @@ class NavigationTabView extends ConsumerWidget { ), _NavigationRow( child1: SmallTileCard( - onTap: () async => ref.navigateToSksMenu( - appBarPopTitle: context.localize.other_view, - ), + onTap: ref.navigateToSksMenu, title: context.localize.sks_full_name, icon: const Icon( Icons.restaurant_menu, diff --git a/lib/features/navigation_tab_view/widgets/notification_button.dart b/lib/features/navigation_tab_view/widgets/notification_button.dart index 08c3c97f..f852cd91 100644 --- a/lib/features/navigation_tab_view/widgets/notification_button.dart +++ b/lib/features/navigation_tab_view/widgets/notification_button.dart @@ -4,6 +4,7 @@ import "../../../config/ui_config.dart"; import "../../../firebase_init.dart"; import "../../../theme/app_theme.dart"; import "../../../widgets/my_splash_tile.dart"; +import "notification_dialog.dart"; class NotificationButton extends StatelessWidget { const NotificationButton({super.key}); @@ -11,10 +12,14 @@ class NotificationButton extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: SksConfig.outerPadding, + padding: SksConfig.outerPaddingLarge, child: MySplashTile( - // TODO(simon-the-shark): show dialog about our future intented use of notifications - onTap: requestFCMPermission, + onTap: () async { + await showNotificationDialog( + context: context, + onConfirmTapped: (_) => requestFCMPermission(), + ); + }, child: Container( padding: SksConfig.innerPadding, decoration: BoxDecoration( diff --git a/lib/features/navigation_tab_view/widgets/notification_dialog.dart b/lib/features/navigation_tab_view/widgets/notification_dialog.dart new file mode 100644 index 00000000..b6731490 --- /dev/null +++ b/lib/features/navigation_tab_view/widgets/notification_dialog.dart @@ -0,0 +1,20 @@ +import "package:flutter/material.dart"; + +import "../../../theme/app_theme.dart"; +import "../../../utils/context_extensions.dart"; +import "../../../widgets/my_alert_dialog.dart"; + +Future showNotificationDialog({ + required BuildContext context, + required void Function(BuildContext context) onConfirmTapped, +}) async { + await showCustomDialog( + context: context, + onConfirmTapped: onConfirmTapped, + confirmText: context.localize.confirm, + dialogContent: Text( + context.localize.push_notifications_dialog_info, + style: context.textTheme.body, + ), + ); +} diff --git a/lib/features/navigator/app_router.dart b/lib/features/navigator/app_router.dart index 3178a73c..ab86a511 100644 --- a/lib/features/navigator/app_router.dart +++ b/lib/features/navigator/app_router.dart @@ -79,7 +79,7 @@ class AppRouter extends RootStackRouter { path: "/sks-menu", page: SksMenuRoute.page, ), - _NoTransitionRoute( + AutoRoute( path: "/departments", page: DepartmentsRoute.page, ), @@ -87,7 +87,7 @@ class AppRouter extends RootStackRouter { path: "/departments/:id", page: DepartmentDetailRoute.page, ), - _NoTransitionRoute( + AutoRoute( path: "/sci-clubs", page: ScienceClubsRoute.page, ), diff --git a/lib/features/navigator/navigation_controller.dart b/lib/features/navigator/navigation_controller.dart index 805fe29d..81c0222a 100644 --- a/lib/features/navigator/navigation_controller.dart +++ b/lib/features/navigator/navigation_controller.dart @@ -2,6 +2,7 @@ import "dart:async"; import "package:auto_route/auto_route.dart"; import "package:collection/collection.dart"; +import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; @@ -15,6 +16,7 @@ typedef TRoute = PageRouteInfo; typedef NavigationState = ({ bool isStackPoppable, NavBarEnum activeTab, + IList fullNavigationStack, }); @riverpod @@ -79,6 +81,15 @@ class NavigationController extends _$NavigationController { state = build(); } + // so the problem is that we use both nested and global navigator and they have separate navigation stacks + List get fullStack => [ + ..._stack, // nested navigator stack + ..._router?.root.stackData.sublist(1).map( + (e) => e.route.toPageRouteInfo(), // global navigator stack + ) ?? + [], + ]; // we spread both stacks to get proper combined stack + @override NavigationState build() { return ( @@ -86,6 +97,19 @@ class NavigationController extends _$NavigationController { activeTab: _stack.lastWhereOrNull((element) => element.isTabView)?.tabBarEnum ?? NavBarEnum.home, + fullNavigationStack: fullStack.toIList(), ); } } + +@riverpod +TRoute? previousRouteOnStack(Ref ref, String currentRouteName) { + final fullStack = ref.watch(navigationControllerProvider).fullNavigationStack; + final currentRouteIndex = fullStack.lastIndexWhere( + (element) => element.routeName == currentRouteName, + ); + if (currentRouteIndex > 0) { + return fullStack[currentRouteIndex - 1]; + } + return null; +} diff --git a/lib/features/navigator/utils/navigation_commands.dart b/lib/features/navigator/utils/navigation_commands.dart index 7b2fd09e..5c492ab6 100644 --- a/lib/features/navigator/utils/navigation_commands.dart +++ b/lib/features/navigator/utils/navigation_commands.dart @@ -73,12 +73,8 @@ extension NavigationX on WidgetRef { await _router.pushNamed(uri); } - Future navigateToSksMenu({String? appBarPopTitle}) async { - if (appBarPopTitle != null) { - await _router.push(SksMenuRoute(appBarPopTitle: appBarPopTitle)); - } else { - await _router.push(SksMenuRoute()); - } + Future navigateToSksMenu() async { + await _router.push(const SksMenuRoute()); } Future navigateDigitalGuide(int id) async { diff --git a/lib/features/parkings_view/widgets/parking_wide_tile_card.dart b/lib/features/parkings_view/widgets/parking_wide_tile_card.dart index 56e7f48a..c1dc889f 100644 --- a/lib/features/parkings_view/widgets/parking_wide_tile_card.dart +++ b/lib/features/parkings_view/widgets/parking_wide_tile_card.dart @@ -24,7 +24,7 @@ class ParkingWideTileCard extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: onTap, + onTap: isActive ? null : onTap, child: Container( height: isActive ? 300 : WideTileCardConfig.imageSize, decoration: BoxDecoration( @@ -51,7 +51,10 @@ class ParkingWideTileCard extends StatelessWidget { width: double.infinity, height: WideTileCardConfig.imageSize, padding: ParkingsConfig.padding, - child: _RightColumn(parking, isActive: isActive), + child: _RightColumn( + parking, + isActive: isActive, + ), ), if (!isActive) Positioned( @@ -59,6 +62,20 @@ class ParkingWideTileCard extends StatelessWidget { right: 2, child: FavouriteParkingWidget(parking), ), + if (isActive) + Positioned( + top: 0, + right: 0, + child: IconButton( + padding: EdgeInsets.zero, + onPressed: onTap, + icon: Icon( + Icons.close, + color: context.colorTheme.whiteSoap, + size: 22, + ), + ), + ), ], ), ), @@ -127,12 +144,6 @@ class _RightColumn extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end, children: [ - if (isActive) - Icon( - Icons.close, - color: context.colorTheme.whiteSoap, - size: 22, - ), const Spacer(), Row( mainAxisAlignment: MainAxisAlignment.end, diff --git a/lib/features/science_club_detail_view/science_club_detail_view.dart b/lib/features/science_club_detail_view/science_club_detail_view.dart index 0170f6fe..7f3001aa 100644 --- a/lib/features/science_club_detail_view/science_club_detail_view.dart +++ b/lib/features/science_club_detail_view/science_club_detail_view.dart @@ -31,7 +31,7 @@ class ScienceClubDetailView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: DetailViewAppBar(title: context.localize.study_circles), + appBar: DetailViewAppBar(), body: _SciClubDetailDataView(id), ); } diff --git a/lib/features/science_clubs_filters/widgets/apply_filters_button.dart b/lib/features/science_clubs_filters/widgets/apply_filters_button.dart index 98b9a647..5ac68481 100644 --- a/lib/features/science_clubs_filters/widgets/apply_filters_button.dart +++ b/lib/features/science_clubs_filters/widgets/apply_filters_button.dart @@ -14,35 +14,38 @@ class ApplyFiltersButton extends StatelessWidget { @override Widget build(BuildContext context) { - return ElevatedButton( - onPressed: onPressed, - style: ElevatedButton.styleFrom( - backgroundColor: context.colorTheme.orangePomegranade, - elevation: 4, - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), - shape: RoundedRectangleBorder( - borderRadius: - FilterConfig.radius, // Change this value to adjust the radius - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox( - width: 5, - ), - Text( - context.localize.apply, - style: context.textTheme.titleWhite, - ), - const SizedBox( - width: 5, + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: context.colorTheme.orangePomegranade, + elevation: 4, + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + shape: RoundedRectangleBorder( + borderRadius: + FilterConfig.radius, // Change this value to adjust the radius ), - Icon( - Icons.check, - color: context.colorTheme.whiteSoap, - ), - ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox( + width: 5, + ), + Text( + context.localize.apply, + style: context.textTheme.titleWhite, + ), + const SizedBox( + width: 5, + ), + Icon( + Icons.check, + color: context.colorTheme.whiteSoap, + ), + ], + ), ), ); } diff --git a/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart b/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart index 3a4337af..05497b8b 100644 --- a/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart +++ b/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart @@ -15,7 +15,6 @@ class SciClubsScaffold extends ConsumerWidget { return Scaffold( appBar: SearchBoxAppBar( addLeadingPopButton: true, - leadingButtonTitle: context.localize.other_view, context, title: context.localize.study_circles, onQueryChanged: ref diff --git a/lib/features/sks-menu/presentation/sks_menu_screen.dart b/lib/features/sks-menu/presentation/sks_menu_screen.dart index 072d4018..c06e5622 100644 --- a/lib/features/sks-menu/presentation/sks_menu_screen.dart +++ b/lib/features/sks-menu/presentation/sks_menu_screen.dart @@ -3,8 +3,12 @@ import "dart:core"; import "package:auto_route/annotations.dart"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:logger/logger.dart"; +import "package:lottie/lottie.dart"; +import "../../../../theme/app_theme.dart"; import "../../../config/ui_config.dart"; +import "../../../gen/assets.gen.dart"; import "../../../utils/context_extensions.dart"; import "../../../widgets/detail_views/detail_view_app_bar.dart"; import "../../home_view/widgets/paddings.dart"; @@ -17,17 +21,12 @@ import "widgets/sks_menu_section.dart"; @RoutePage() class SksMenuView extends ConsumerWidget { - const SksMenuView({ - super.key, - this.appBarPopTitle, - }); - final String? appBarPopTitle; + const SksMenuView({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final asyncSksMenuData = ref.watch(getSksMenuDataProvider); - // TODO(mikolaj-jalocha): Add lottie animation on: error and when data is empty (sks's closed) return asyncSksMenuData.when( data: (sksMenuData) => _SksMenuView( asyncSksMenuData.value ?? @@ -36,16 +35,8 @@ class SksMenuView extends ConsumerWidget { lastUpdate: DateTime.now(), meals: List.empty(), ), - appBarPopTitle, - ), - error: (error, stackTrace) => Scaffold( - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: Text("Error with SKS menu API: $error"), - ), - ), ), + error: (error, stackTrace) => _SKSMenuLottieAnimation(error: error), loading: () => const Scaffold( body: Center( child: CircularProgressIndicator(), @@ -56,22 +47,16 @@ class SksMenuView extends ConsumerWidget { } class _SksMenuView extends StatelessWidget { - const _SksMenuView(this.sksMenuData, this.appBarPopTitle); + const _SksMenuView(this.sksMenuData); final SksMenuResponse sksMenuData; - final String? appBarPopTitle; @override Widget build(BuildContext context) { - if (sksMenuData.meals.isEmpty) { - return const Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); + if (!sksMenuData.isMenuOnline) { + return const _SKSMenuLottieAnimation(); } return Scaffold( appBar: DetailViewAppBar( - title: appBarPopTitle ?? context.localize.home_screen, actions: const [ SksUserDataButton(), ], @@ -98,3 +83,53 @@ class _SksMenuView extends StatelessWidget { ); } } + +class _SKSMenuLottieAnimation extends StatelessWidget { + const _SKSMenuLottieAnimation({ + this.error, + }); + + final Object? error; + @override + Widget build(BuildContext context) { + Logger().e(error.toString()); + return Scaffold( + appBar: DetailViewAppBar( + actions: const [ + SksUserDataButton(), + ], + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox.square( + dimension: 200, + child: Lottie.asset( + Assets.animations.sksClosed, + fit: BoxFit.cover, + repeat: false, + frameRate: const FrameRate(LottieAnimationConfig.frameRate), + renderCache: RenderCache.drawingCommands, + ), + ), + Align( + child: Text( + context.localize.sks_menu_closed, + style: context.textTheme.headline, + textAlign: TextAlign.center, + ), + ), + if (error != null) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + error.toString(), + style: context.textTheme.titleGrey, + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/splash_screen/widgets/fade_in_splash_gradient.dart b/lib/features/splash_screen/widgets/fade_in_splash_gradient.dart index 4465c9c5..a0fcab26 100644 --- a/lib/features/splash_screen/widgets/fade_in_splash_gradient.dart +++ b/lib/features/splash_screen/widgets/fade_in_splash_gradient.dart @@ -5,7 +5,7 @@ import "../../../theme/colors.dart"; import "fade_in_gradient_animation.dart"; class FadeInSplashGradient extends FadeInGradientAnimation { - /// Fades in from Android's single color splash to ToPwr LinearGradient on first build + /// Fades in from Android's single color splash to ToPWR LinearGradient on first build const FadeInSplashGradient({super.key}) : super( gradientStart: const LinearGradient( diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 906f777d..354f8435 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -98,13 +98,16 @@ "until_the_end_of_the_semester_break": "do końca przerwy międzysemestralnej", "until_the_session_ends": "do zakończenia sesji", "app_info": "(informacje o aplikacji)", - "show_license": "Wyświelt licencję", + "show_license": "Wyświetl licencję", "close": "Zamknij", "no_version": "Brak wersji", "places_currently_available": "Aktualnie wolne miejsca", "student_councils": "Samorząd", "hi_student": "Hej studencie!", "guide_development_info": "Pamiętaj, że przewodnik jest wciąż w fazie rozwoju :)", + "guide_ideas_info": "Masz ciekawy pomysł? Podziel się z nami!", + "guide_click_here": "Zgłoś swój pomysł", + "guide_subject_default_content": "Pomsył na rozwój ToPWR", "streak_counter": "Używasz ToPWR nieprzerwanie od {days, plural, =1{1 dnia} other{{days} dni}}🔥", "@streak_counter": { "description": "A message with a single parameter", @@ -181,4 +184,10 @@ "emergency_chairs": "W budynku zamieszczone zostały krzesła ewakuacyjne", "parking_location" : "Miejsca parkingowe znajdują się {location}", "digital_guide_website" : "www.przewodnik.pwr.edu.pl" - } \ No newline at end of file + }, + "sks_old_menu": "Zobacz ostatnie menu", + "sks_menu_closed" : "SKS Menu jest teraz niedostępne", + "confirm": "Zatwierdź", + "push_notifications_dialog_info": "Obecnie nie korzystamy z powiadomień push, ale planujemy dodać je w przyszłości. Możesz wyrazić na nie zgodę już teraz." +} + diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 4a5ae3cc..e1515805 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -20,7 +20,7 @@ abstract class ColorsConsts { // Azure #3F6499 static const blueAzure = HexColor.consts(0xff3f6499); - // ToPwr LinearGradient #f67448, #ee6645, #df371b + // ToPWR LinearGradient #f67448, #ee6645, #df371b static const toPwrGradient = LinearGradient( begin: Alignment(-1, -0.1), end: Alignment(1, 0.1), diff --git a/lib/utils/contains_number.dart b/lib/utils/contains_number.dart new file mode 100644 index 00000000..ad795e6d --- /dev/null +++ b/lib/utils/contains_number.dart @@ -0,0 +1,5 @@ +extension ContainsNumberX on String { + bool containsNumber() { + return contains(RegExp(r"\d")); + } +} diff --git a/lib/widgets/big_preview_card.dart b/lib/widgets/big_preview_card.dart index 044bbd23..08eef82d 100644 --- a/lib/widgets/big_preview_card.dart +++ b/lib/widgets/big_preview_card.dart @@ -29,74 +29,80 @@ class BigPreviewCard extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: context.colorTheme.greyLight, - borderRadius: BorderRadius.circular(8), - ), - width: BigPreviewCardConfig.cardWidth, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 135, - child: ClipRRect( - borderRadius: - const BorderRadius.vertical(top: Radius.circular(8)), - child: Stack( - alignment: Alignment.topRight, - children: [ - SizedBox( - width: double.maxFinite, - child: Center( - child: OptimizedDirectusImage( - directusUrl, - boxFit: boxFit, - ), - ), - ), - if (date != null) - DateChip(date: date!) - else - const SizedBox.shrink(), - ], - ), + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Material( + child: InkWell( + onTap: onClick, + child: Ink( + decoration: BoxDecoration( + color: context.colorTheme.greyLight, + borderRadius: BorderRadius.circular(8), ), - ), - Expanded( - flex: 210, - child: Container( - padding: const EdgeInsets.all(16), + child: SizedBox( + width: BigPreviewCardConfig.cardWidth, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - DualTextMaxLines( - title: title, - titleStyle: context.textTheme.title, - subtitle: shortDescription, - subtitleStyle: context.textTheme.body, - spacing: 7, - maxTotalLines: 8, - showBadge: showBadge, + Expanded( + flex: 135, + child: Stack( + alignment: Alignment.topRight, + children: [ + SizedBox( + width: double.maxFinite, + child: Center( + child: OptimizedDirectusImage( + directusUrl, + boxFit: boxFit, + ), + ), + ), + if (date != null) + DateChip(date: date!) + else + const SizedBox.shrink(), + ], + ), ), - const Spacer(), - MaterialButton( - elevation: 1, - padding: const EdgeInsets.all(8), - onPressed: onClick, - color: context.colorTheme.orangePomegranade, - textColor: context.colorTheme.whiteSoap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), + Expanded( + flex: 210, + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + DualTextMaxLines( + title: title, + titleStyle: context.textTheme.title, + subtitle: shortDescription, + subtitleStyle: context.textTheme.body, + spacing: 7, + maxTotalLines: 8, + showBadge: showBadge, + ), + const Spacer(), + MaterialButton( + elevation: 1, + padding: const EdgeInsets.all(8), + onPressed: onClick, + color: context.colorTheme.orangePomegranade, + textColor: context.colorTheme.whiteSoap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + child: Text(context.localize.read_more), + ), + ], + ), ), - child: Text(context.localize.read_more), ), ], ), ), ), - ], + ), ), ); } diff --git a/lib/widgets/detail_views/detail_view_app_bar.dart b/lib/widgets/detail_views/detail_view_app_bar.dart index a0cb7181..dc49e5d1 100644 --- a/lib/widgets/detail_views/detail_view_app_bar.dart +++ b/lib/widgets/detail_views/detail_view_app_bar.dart @@ -5,13 +5,12 @@ import "pop_button.dart"; class DetailViewAppBar extends AppBar { DetailViewAppBar({ super.key, - required String title, super.actions, }) : super( centerTitle: false, automaticallyImplyLeading: false, scrolledUnderElevation: 0, titleSpacing: 4, - title: DetailViewPopButton(title), + title: const DetailViewPopButton(), ); } diff --git a/lib/widgets/detail_views/pop_button.dart b/lib/widgets/detail_views/pop_button.dart index 8fddd3d1..3f8b08ba 100644 --- a/lib/widgets/detail_views/pop_button.dart +++ b/lib/widgets/detail_views/pop_button.dart @@ -1,15 +1,23 @@ import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "../../config/nav_bar_config.dart"; +import "../../features/navigator/navigation_controller.dart"; import "../../theme/app_theme.dart"; -class DetailViewPopButton extends StatelessWidget { - const DetailViewPopButton( - this.title, { +class DetailViewPopButton extends ConsumerWidget { + const DetailViewPopButton({ super.key, }); - final String? title; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final currentRoute = ModalRoute.of(context)?.settings.name; + final title = currentRoute == null + ? null + : ref + .watch(previousRouteOnStackProvider(currentRoute)) + ?.getFormatedRouteName(context); + return TextButton( onPressed: () { Navigator.pop(context); diff --git a/lib/widgets/my_alert_dialog.dart b/lib/widgets/my_alert_dialog.dart new file mode 100644 index 00000000..2f60271b --- /dev/null +++ b/lib/widgets/my_alert_dialog.dart @@ -0,0 +1,87 @@ +import "package:flutter/material.dart"; + +import "../../../config/ui_config.dart"; +import "../../../theme/app_theme.dart"; +import "../../../utils/context_extensions.dart"; + +Future showCustomDialog({ + required BuildContext context, + required void Function(BuildContext context) onConfirmTapped, + required String confirmText, + required Widget dialogContent, +}) async { + await showDialog( + context: context, + builder: (BuildContext context) { + final double maxWidth = MediaQuery.sizeOf(context).width * 0.89; + return Center( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: maxWidth, + ), + child: _MyAlertDialog( + dialogContent: dialogContent, + onConfirmTapped: () => onConfirmTapped(context), + confirmText: confirmText, + ), + ), + ); + }, + ); +} + +class _MyAlertDialog extends StatelessWidget { + final Widget dialogContent; + final VoidCallback onConfirmTapped; + final String confirmText; + + const _MyAlertDialog({ + required this.dialogContent, + required this.onConfirmTapped, + required this.confirmText, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Padding( + padding: const EdgeInsets.symmetric( + horizontal: AlertDialogConfig.horizontalPadding, + vertical: AlertDialogConfig.verticalPadding, + ), + child: dialogContent, + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: TextButton( + onPressed: onConfirmTapped, + child: Text( + confirmText, + style: context.textTheme.bodyOrange.copyWith( + fontSize: AlertDialogConfig.buttonFontSize, + ), + ), + ), + ), + Flexible( + child: TextButton( + child: Text( + context.localize.close, + style: context.textTheme.body.copyWith( + fontSize: AlertDialogConfig.buttonFontSize, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/widgets/search_box_app_bar.dart b/lib/widgets/search_box_app_bar.dart index 3809448e..3a63951d 100644 --- a/lib/widgets/search_box_app_bar.dart +++ b/lib/widgets/search_box_app_bar.dart @@ -19,7 +19,6 @@ class SearchBoxAppBar extends AppBar { VoidCallback? onSearchBoxTap, double bottomPadding = defaultBottomPadding, bool addLeadingPopButton = false, - String? leadingButtonTitle, }) : super( title: Text(title), titleTextStyle: context.textTheme.headline, @@ -28,12 +27,12 @@ class SearchBoxAppBar extends AppBar { centerTitle: addLeadingPopButton, titleSpacing: addLeadingPopButton ? 0 : defaultHorizontalPadding, automaticallyImplyLeading: false, - leading: addLeadingPopButton && leadingButtonTitle != null - ? Align( + leading: addLeadingPopButton + ? const Align( alignment: Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.only(left: 4), - child: DetailViewPopButton(leadingButtonTitle), + padding: EdgeInsets.only(left: 4), + child: DetailViewPopButton(), ), ) : null, diff --git a/lib/widgets/tile_splash.dart b/lib/widgets/tile_splash.dart index 567c9410..b35013a8 100644 --- a/lib/widgets/tile_splash.dart +++ b/lib/widgets/tile_splash.dart @@ -18,3 +18,6 @@ class TileSplash extends StatelessWidget { ); } } + +// If tile splash doesn't work read this article +// https://therdm.medium.com/ripple-effect-not-working-on-inkwell-try-the-ink-widget-44b6a1964de0 diff --git a/pubspec.yaml b/pubspec.yaml index a39ec07d..143a7767 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -150,6 +150,7 @@ flutter: - assets/animations/search.json - assets/animations/offline.json - assets/animations/bug.json + - assets/animations/sks_closed.json # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware