From c51771dadfaf175634e501fc85b49e19aff6a303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Robert?= Date: Sat, 7 Mar 2026 21:10:03 +0100 Subject: [PATCH 1/8] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 5085d0a4a13dbbfa8ef1645e02097c864106b3ab Author: FastSpyder Date: Sun Mar 1 02:41:45 2026 +0100 Changing names in the deleting xxx admin interfaces (users from groups, groups and schools) to add name of the deleted entity (#656) When deleting someone from the admin interface, it is unclear and misleading which entity is deleted - [x] Changing message from deleting Group - [x] Changing message from deleting User from a Group - [x] Changing message from deleting School
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (non-breaking change which adds functionality) - [x] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [x] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [x] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [x] `//` Comments - [ ] No documentation needed
commit d9f982836797cc36a9ea3236daf6f01295046c73 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Sat Feb 28 13:39:45 2026 +0100 Fix README.md and various related things (#655) Analogous to aeecleclair/Hyperion#926
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [x] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [x] Other: README - [x] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly This *is* documentation.
commit 603a6ac5c6e741513cdf2af6a4aa3bac54efba60 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Fri Feb 20 23:38:05 2026 +0100 Optimize(Web): gzip pre-compression (#653) The web version can be unbearably slow. One way to fix it is compression. - Compression = fewer data is transferred over the network = faster loading times (even more blatant in slow networks) - Pre-compression = compressed once (here I included this in the building of the Docker image), served instantly from disk, no CPU overhead, and allows more aggressive levels without downsides for the client (source: the creator of gzip himself said so on StackOverflow) = even faster - gzip = already in Nginx by default. Brotli may be even faster to decompress, and supported everywhere as well, but building a Nginx+Brotli image really is unmaintainable for us. Sources: - https://stackoverflow.com/a/37892135/30331616 A video is better than 1000 words: ~30% faster with a fast connection. Can go up to 2x faster in slower networks. Consistently 12.0-12.2s for https://titan.dev.eclair.ec-lyon.fr and 8.7-9.0s for https://titan-1.dev.eclair.ec-lyon.fr https://github.com/user-attachments/assets/a3d00882-fd46-4221-b337-1857829246a3 Subset of the draft #646 for Nginx pre-compression. - [x] Nginx config: gzip config: gzip & gunzip, static, level 9, >512 bytes, mime types to take text-based files & avoid images, - [x] Dockerfile: include the compression there, for >512 bytes file, take text-based files & avoid image - [x] Consistent params between the Nginx config and the compression during image build. - [x] Re-run `flutter pub run pdfx:install_web` to regenerate the PDF script
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [x] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [x] Optimization - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [x] Other: Dockerfile, Nginx config, index.html - [x] 1. Tested this locally: built and run my Docker image - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [x] 3. Tested in a local client using a pre-prod backend - [x] 4. Created the 1st Titan pre-prod for the occasion - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit d06e20cf9da52405a68766f6dff6e67353901674 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Tue Feb 10 14:30:14 2026 +0100 Update README.md and .env.template (#638) Titan counterpart of aeecleclair/Hyperion#845 - [x] Follow aeecleclair/Hyperion#845 style: working environment > dependencies > fill the config file (`.env`) > launch it > beyond - [x] Pre-fill the `.env` Go on https://github.com/aeecleclair/Titan/blob/update/readme/README.md to see the Markdown preview.
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [x] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [x] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [x] Other: Installation guide This PR *is* documentation.
commit 27a10d928a7484eef634aa356b9c2edd3ebf559a Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Feb 9 13:48:05 2026 +0100 Use a `web_dev_config.yaml` (a Flutter 3.38 novelty) (#651) `web_dev_config.yaml` is a brand-new config file, added in Flutter 3.38. Here it's first use will be to use the port 3000 without having to type it ever again, even in command line! Also, sync-ed headers with the Nginx config. Fixing the port and headers here means **reproducible** runs **aligned** with the way (static) builds are served by Nginx. Source: https://docs.flutter.dev/platform-integration/web/web-dev-config-file - [x] Add a `web_dev_config.yaml` for port 3000 - [x] Remove the `--web-port 3000` from the launch.json - [x] Add `Cache-Control` headers and sync-ed with the Nginx config - [x] Fix: the pubspec.lock automatically sync-ed the Dart SDK version on the pubspec.yaml (someone modified it by hand and didn't `pub get` afterward)
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [x] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [x] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [x] Other: Development only (file only used locally by devs) - [x] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [x] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [x] No documentation needed
commit cb570b28b3cb4228c4c3d83bb24d3b389ab8ac7b Author: Armand Didierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Mon Feb 9 12:56:18 2026 +0100 Release 2.0.6 (#650) ... - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit 02a9741224250cc925d14696c499c0df9db3be42 Author: Timothée Robert <114694873+Rotheem@users.noreply.github.com> Date: Thu Feb 5 15:21:20 2026 +0100 Fix web worker (#649) ... - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit f19e3475f42ae1b571da5e091548cfd45e073527 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Fri Jan 30 16:03:33 2026 +0100 Release v2.0.5+163-alpha (#645) commit 5e73e1324f31d4c6d235dd080b2f058926b87745 Author: Timothée Robert <114694873+Rotheem@users.noreply.github.com> Date: Fri Jan 30 15:58:14 2026 +0100 Correct member filtering (#644) Fix member filtering in phonebook - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit 4f8095aefc69dd500788cb240bc125acb63c501e Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Fri Jan 30 15:53:25 2026 +0100 Feat(phonebook): button to copy a pastable email list (#619) As asked by the clients of this module, a button to copy to clipboard the email list of an association's term, so that it can be pasted in and parsed by mail clients Not sure it's the best UI rendering, tho image commit 36e8580e55a8a9ae370bd9015b89e0c83ccde008 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Fri Jan 30 14:39:27 2026 +0100 Association groupement (#643) Assuming this is the front-end of aeecleclair/Hyperion#759 - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [x] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [x] 1. Tested this locally - [x] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
--------- Co-authored-by: Thonyk commit e42acfb3145fc7be7ef7aaacb8f6208faf2af218 Author: NakoGH Date: Tue Jan 27 19:30:08 2026 +0100 Myeclpay export history to csv (#641) image - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [x] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit 98b648686e44eb79e9172db5cb1ab46e5b6140ce Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Tue Jan 27 15:58:31 2026 +0100 Feat(Booking): manage from dialog (#617) [Requested by Sid] The point is to make the booking module more intuitive to manage a booking you're authorized to manage (if you're manager ~~or if it's a booking you requested~~). You click on the colored cells in the calendar, and the popup lets you henceforth manage it (i.e. edit, copy information, and crucially: accept and decline). Previously, the popup was there but with read-only stuff, now there's "write" stuff as well. See screen recording below: on the manager page, pending booking requests are now displayed, but translucent to distinguish them from the accepted ones. In any case, the manager, can tap the booking to manage it from the dialog instead of scrolling through dozens of cards, making its life far easier. https://github.com/user-attachments/assets/82f680ab-0583-41f0-892f-9e32eee6714b commit 2dd11330fb00f4377a7503f3b3f77be0108cea11 Author: Warix <39554785+warix8@users.noreply.github.com> Date: Fri Jan 16 14:34:03 2026 +0100 [.env] Minimal Hyperion Version (#480) These changes introduce a **minimal Hyperion version** to ensure Titan can work properly. When a new Titan version with a new Hyperion including breaking changes (e.g. login system) is introduced, there will be a downtime because the stores take a few hours to review the version. Thus, the new version is submitted to the store, this version will work because it will fall back to the alpha version. No downtime anymore for introducing breaking changes. --------- Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> Co-authored-by: Marc-Andrieu Co-authored-by: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> commit f7c7831102a76428d29b958546ee404eb06d0e4b Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Jan 12 10:00:03 2026 +0100 Release v2.0.4+162-alpha (#639) commit 02c352883d527933644fceb80366840e57d75f06 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Jan 12 09:51:04 2026 +0100 Fix(Flutter web deprecation): use worker version special token (+ HTML formatting) (#637) For a long time we've had an annoying deprecation warning everytime we launch the project locally, and today I'm fed up with it. Source: https://docs.flutter.dev/platform-integration/web/initialization - [x] Use the template `{{flutter_service_worker_version}}` as intended per the docs - [x] Format the index.html
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [x] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [x] Other: follow deprecation warning - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [x] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [x] No documentation needed
commit cc72b0bd383f1d51caa767dad52fa14a33a40061 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Sun Jan 11 23:46:06 2026 +0100 Fix: bump google_fonts to use the new Flutter API to find fonts (#636) notes](https://docs.flutter.dev/release/release-notes/release-notes-3.38.0), has the following breaking change: > Remove deprecated AssetManifest.json file by @matanlurey in [172594](https://github.com/flutter/flutter/pull/172594) But `google_fonts 6.2.1` relied on this file, until 6.3.0 where, according to the [changelog](https://pub.dev/packages/google_fonts/changelog#630), they: > Update AssetManifest to use the builtin Flutter API. That's why `google_fonts` version ought to be bumped. NB: 7.0.0 was released 4 days ago, without breaking changes according to the changelog, I decided to test it and it works as before #635 - [x] Use `google_fonts 7.0.0`
- [x] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [x] 😶‍🌫️ No impact for the end-users: that's why an alpha was for: testing - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [x] Other: dependency - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [x] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [x] No documentation needed
commit 517fcc8b6515162eecad658733c3ef9d5ceabaf9 Author: Armand Didierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Sun Jan 11 15:53:03 2026 +0100 feat: Flutter 3.38.6 and ios generated files (#635) ... - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit d48f6fd69706f0e8cc0d78bc72661a935a2d079e Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Sat Jan 10 19:46:32 2026 +0100 Fix: lower the minimum Android API level to Flutter's (keep the targeted SDK version to 36) (#634) Accidentally bumped the Android API instead of only the SDK + Gradle-related stuff - [x] Change Android's so-called minSdk (they name things weirdly) - [x] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [x] 😶‍🌫️ No impact for the end-users: because we can't release an app compatible with only about 2% of the market (according to the Play Store console) - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [x] Other: build-related stuff to ensure we're still compatible with most of our users' phones - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [x] 0. Untestable (exceptionally), will be tested in alpha directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [x] No documentation needed --------- Co-authored-by: Armand Didierjean <95971503+armanddidierjean@users.noreply.github.com> commit be5d7d9a1041bf2e266cb76528310f03f9367128 Author: Timothée Robert <114694873+Rotheem@users.noreply.github.com> Date: Sat Jan 10 02:13:56 2026 +0100 Fix mypayment root (#633) ... - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit efd6b695af701e61140ceecdb9dd4d9aa9fe5a93 Author: Timothée Robert <114694873+Rotheem@users.noreply.github.com> Date: Sat Jan 10 01:45:11 2026 +0100 Fix root comparison for permission (#632) ... - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [ ] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [ ] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [ ] No documentation needed
commit 2bc512464d50dd742a7c0b6405bc4c07b18dffd4 Author: Flaim-X2 Date: Wed Jan 7 01:20:40 2026 +0100 AMAP in cents (#496) Keeps amounts, accounts and balances in cents and calculates them in cents, i.e. in int. Then the front-end displays in euros and takes in euros. --------- Co-authored-by: Maxime Roucher Co-authored-by: Lühmos Co-authored-by: Maillard Antoine <145469528+cotanoine@users.noreply.github.com> Co-authored-by: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> commit 954c031fa7d7345447e141b808ed71b474e1f421 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Wed Jan 7 01:02:22 2026 +0100 Myeclpay to mypayment (#623) - rename "MyECLPay" to "MyPayment", excepted for what is displayed in the UI - rename the `paiement` module to `mypayment` commit 75c9c3536bb844d7cddc966508b5f3daa2748e75 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Jan 5 23:12:48 2026 +0100 Fix: Android namespace (#631) commit ef81d43dd2214350ff2260fd5e0cab30d3f762ca Author: Timothée Robert <114694873+Rotheem@users.noreply.github.com> Date: Sat Jan 3 23:38:49 2026 +0100 Modular permissions (#618) image image image commit 8c93ccf968c38781fbf90c8b29bf0d4236271a41 Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Sat Jan 3 22:46:50 2026 +0100 Test build with google services (#630) test commit 41b0cf64a63934db8bfdd33e4dea7fde4ef125df Author: Marc Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Wed Dec 31 22:47:24 2025 +0100 Release 1.4.1+157 (#627) commit 38b119a5a5c943a978293c466d3ad83e314be90b Author: Foucauld Bellanger <63885990+foucblg@users.noreply.github.com> Date: Wed Dec 31 21:43:22 2025 +0100 bump mobile_scanner (#602) Need to bump mobile_scanner in order to reach 16ko compatibility. Since v.7.0.0 scanner needs to be stopped properly before a new start(). Co-authored-by: Foucauld Bellanger <63885990+Foukki@users.noreply.github.com> commit 87ceca726a733fb138196cf583ea0105a620b49d Author: NakoGH Date: Wed Dec 31 21:38:29 2025 +0100 add : button to show/hide revoked devices (#624) image image ... - [x] ... - [ ] ...
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [x] ✨ New feature (non-breaking change which adds functionality) - [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) - [ ] 🔧 Infra CI/CD (changes to configs of workflows) - [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) - [ ] 😶‍🌫️ No impact for the end-users - [ ] Core functionality changes - [x] Single module changes - [ ] Multiple modules changes - [ ] Other: ... - [x] 1. Tested this locally - [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) - [ ] 3. Tested in a local client using a pre-prod backend - [ ] 0. Untestable (exceptionally), will be tested in prod directly - [ ] Updated [the docs](docs.myecl.fr) accordingly : - [ ] `//` Comments - [x] No documentation needed
commit a9e179b0d8ad46fd52800e747eacdc35d45d0045 Author: Maillard Antoine <145469528+cotanoine@users.noreply.github.com> Date: Sat Dec 6 13:28:24 2025 +0100 Reloads saved preferences for module order (#622) Should allow module order to be retained when reopening the app. Co-authored-by: NakoGH commit 99be83049216a892d270833b6df822b14c7ede60 Author: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Sun Nov 30 05:56:33 2025 +0100 New issue template and actual PR template (#620) Change to the pull request template to improve reviews and enable quick understanding of the nature of the changes to be reviewed. Put it in a path that GitHub understands. Add issue template BTW. commit ab34a6dcc4eb7b8d762c82bc5c3b6cc77c072d5d Author: NakoGH Date: Sun Nov 30 05:20:48 2025 +0100 Show purchases per year in Module Purchases (#621) image commit 52c12832152e5ea95669792e44593f06ec11ce13 Author: julien4215 <120588494+julien4215@users.noreply.github.com> Date: Fri Nov 21 16:42:34 2025 +0100 Handle error when getting QR code content (#608) commit 8c3cc8fbd26a44d788e95a20f694fc400fbcd0b5 Author: julien4215 <120588494+julien4215@users.noreply.github.com> Date: Fri Nov 21 16:31:32 2025 +0100 Try/catch authentication for paiement module (#607) To handle the exceptions in a better way we should use the exceptions of the package local_auth when this gets released: https://github.com/flutter/packages/pull/10147. commit 671041727ab4e3807b34a6298f3cb48496a7d4ec Author: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Fri Nov 7 16:38:41 2025 +0100 UI: Reduce the size of the module name in TopBar and drawer (#616) As a consequence of the new module "Centralassocation" whose name is too long for the drawer image commit 3ba130bea60245887758d5263a1ab2dd738f0ce8 Author: Timothée Robert <114694873+Rotheem@users.noreply.github.com> Date: Tue Nov 4 02:01:45 2025 +0100 Add myeclpay invoices (#613) commit 33cfaf96670a0a179e98f6503fddb70ef00e181b Author: Luhmos Date: Tue Oct 21 12:34:53 2025 +0200 New module: Centralassociation (#609) New module based on Centralisation. It adapts the code to use the links from the Centralassociation website instead, using more appropriate classes. commit 7f92e22721b7542095db4558c36f13407544070b Author: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Sep 8 20:26:10 2025 +0200 Release 1.3.0+155 (#599) commit 5b5b300af29456df8d2d3563bf801b8cf681de8c Author: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Sep 8 20:25:27 2025 +0200 Fix QR code scanner of purchases (strictly) (#600) * [x] Fix `onScan` argument type to actually pass the scanned secret downstream down to the request to Hyperion * [x] replace "validate" button with "next" commit b5a3aeb2bcb6d10390f904f16f1125c98533118e Author: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Sep 8 14:32:26 2025 +0200 Account management - Part I (#575) Fix #396 Fix #305 Fix #222 commit 02f951714ebc77ecd6a9ba95da302bcdafba1353 Author: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Sep 8 13:14:14 2025 +0200 Remember what store the user selected last (#595) Feedback: several people scanned several times for the wrong store because the default one was always the same. Now we put in the storage the ID of the last store selected by the user, so that when s/he comes back on MyECL during an event on behalf of one club, the virtual money goes the right store. commit 10279a16ce45a7872b809133d0e48a7bdff313f8 Author: Maxime Roucher Date: Mon Sep 8 13:08:24 2025 +0200 fix: migrating to new key encoding (#598) commit 983bc29a72833ed7f94989f9564eecf5f7844f58 Author: Marc-Andrieu <146140470+Marc-Andrieu@users.noreply.github.com> Date: Mon Sep 1 18:41:58 2025 +0200 Fix: access memberships from admin UI (#590) commit 04f9da31b44d68222c2879a61cc13e2cf7acd927 Author: Maxime Roucher Date: Mon Sep 1 09:47:34 2025 +0200 fix: setting interval default end time to 23:59:59 (#589) commit e5adffa505b888228f1678665fb55bc15643772a Author: NakoGH Date: Wed Jul 30 21:57:14 2025 +0200 rm .yaml add .yml (#579) commit d5e3bc4fb5947d9f1c32171484d1c80c6116e383 Author: Timothée Robert <114694873+Rotheem@users.noreply.github.com> Date: Sat Jul 19 19:57:16 2025 +0200 New async child (#567) This PR is meant to extend the current AsyncChild to handle multiple AsyncValues simultaneously to avoid having to use AsyncChild in AsyncChild. For example with Phonebook we need to fetch associations and associationKinds and both are mixed up, thus we can't show any of them separately and we need to wait for both to be available. With this one widget would be sufficient. --- .env.template | 13 +- .github/ISSUE_TEMPLATE.md | 20 ++ .github/PULL_REQUEST_TEMPLATE.md | 73 ++++ .../Titan_PR_Template.md | 16 - .github/{dependabot.yaml => dependabot.yml} | 32 +- .github/workflows/build.yml | 5 + .github/workflows/lintandformat.yml | 5 + .github/workflows/release-mobile.yml | 7 +- .github/workflows/release-web.yml | 5 + .github/workflows/test.yml | 7 +- .vscode/extensions.json | 3 + .vscode/settings.json | 15 +- Dockerfile | 5 +- LICENSE => LICENSE.md | 0 README.md | 209 ++++++++++- android/app/build.gradle | 9 +- lib/admin/class/permissions.dart | 114 ++++++ lib/admin/providers/is_admin_provider.dart | 6 + .../permission_name_list_provider.dart | 32 ++ .../providers/permissions_list_provider.dart | 183 ++++++++++ lib/admin/providers/structure_provider.dart | 2 +- lib/admin/repositories/group_repository.dart | 17 +- .../repositories/permission_repository.dart | 40 +++ lib/admin/router.dart | 9 +- lib/admin/tools/functions.dart | 10 + .../permissions/module_expansion_panel.dart | 61 ++++ .../permission_expansion_panel.dart | 122 +++++++ .../ui/pages/permissions/permission_row.dart | 41 +++ .../ui/pages/permissions/permissions.dart | 74 ++++ .../add_edit_structure_page.dart | 4 +- .../pages/structure_page/structure_page.dart | 7 +- lib/advert/tools/constants.dart | 5 + lib/amap/class/cash.dart | 25 +- lib/amap/class/delivery.dart | 9 +- lib/amap/class/order.dart | 15 +- lib/amap/class/product.dart | 4 +- lib/amap/providers/cash_list_provider.dart | 2 +- .../providers/is_amap_admin_provider.dart | 10 +- lib/amap/providers/user_amount_provider.dart | 2 +- lib/amap/ui/components/order_ui.dart | 54 ++- lib/amap/ui/components/product_ui.dart | 2 +- .../ui/pages/admin_page/account_handler.dart | 4 +- .../ui/pages/admin_page/adding_user_card.dart | 4 +- .../ui/pages/admin_page/cash_container.dart | 9 +- .../ui/pages/admin_page/delivery_handler.dart | 2 +- lib/amap/ui/pages/admin_page/delivery_ui.dart | 49 ++- .../ui/pages/admin_page/user_cash_ui.dart | 15 +- .../pages/admin_page/user_cash_ui_layout.dart | 2 +- .../add_edit_delivery_cmd_page.dart | 20 +- .../delivery_pages/product_ui_check.dart | 4 +- .../detail_delivery_page/detail_page.dart | 7 + .../detail_delivery_page/order_detail_ui.dart | 8 +- .../product_detail_ui.dart | 4 +- .../ui/pages/detail_page/detail_page.dart | 2 +- .../product_choice_button.dart | 2 +- .../list_products_page/product_ui_list.dart | 2 +- lib/amap/ui/pages/main_page/delivery_ui.dart | 43 ++- lib/amap/ui/pages/main_page/main_page.dart | 8 +- .../ui/pages/main_page/orders_section.dart | 2 +- .../pages/product_pages/add_edit_product.dart | 16 +- lib/auth/providers/openid_provider.dart | 2 +- lib/booking/providers/is_admin_provider.dart | 31 +- .../providers/is_manager_provider.dart | 12 - lib/booking/router.dart | 7 +- lib/booking/tools/constants.dart | 6 + .../ui/calendar/appointment_data_source.dart | 5 +- lib/booking/ui/calendar/calendar.dart | 120 ++++++- lib/booking/ui/calendar/calendar_dialog.dart | 69 +++- .../ui/pages/admin_pages/admin_page.dart | 227 ++++++------ lib/booking/ui/pages/main_page/main_page.dart | 3 +- .../ui/pages/manager_page/list_booking.dart | 7 +- lib/centralassociation/class/asso.dart | 27 ++ lib/centralassociation/class/link.dart | 12 + .../centralassociation_asso_provider.dart | 29 ++ .../repositories/asso_repository.dart | 39 +++ lib/centralassociation/router.dart | 32 ++ lib/centralassociation/tools/constants.dart | 8 + lib/centralassociation/tools/functions.dart | 10 + .../ui/centralassociation.dart | 30 ++ .../ui/pages/asso_list.dart | 31 ++ .../ui/pages/link_card.dart | 73 ++++ .../ui/pages/main_page.dart | 27 ++ lib/cinema/providers/is_cinema_admin.dart | 10 +- lib/cinema/tools/constants.dart | 5 + lib/event/providers/is_admin_provider.dart | 10 +- lib/event/tools/constants.dart | 5 + lib/loan/tools/constants.dart | 5 + lib/login/class/account_type.dart | 1 - lib/login/router.dart | 1 + lib/login/tools/functions.dart | 16 - lib/login/ui/app_sign_in.dart | 7 +- lib/login/ui/web/left_panel.dart | 7 +- lib/main.dart | 2 + .../class/create_device.dart | 0 .../class/funding_url.dart | 0 .../class/history.dart | 2 +- .../class/history_interval.dart | 0 .../class/history_refund.dart | 0 .../class/init_info.dart | 0 .../class/invoice.dart | 4 +- .../class/qr_code_data.dart | 0 .../class/qr_code_signature_data.dart | 0 lib/{paiement => mypayment}/class/refund.dart | 0 lib/{paiement => mypayment}/class/seller.dart | 0 lib/{paiement => mypayment}/class/store.dart | 2 +- .../class/structure.dart | 0 lib/{paiement => mypayment}/class/tos.dart | 0 .../class/transaction.dart | 2 +- .../class/transfert.dart | 2 +- .../class/user_store.dart | 4 +- lib/{paiement => mypayment}/class/wallet.dart | 2 +- .../class/wallet_device.dart | 0 .../bank_account_holder_provider.dart | 4 +- .../providers/barcode_provider.dart | 2 +- .../providers/bypass_provider.dart | 0 .../providers/device_list_provider.dart | 4 +- .../providers/device_provider.dart | 6 +- .../providers/fund_amount_provider.dart | 0 .../providers/funding_url_provider.dart | 6 +- .../providers/has_accepted_tos_provider.dart | 2 +- .../history_export_csv_provider.dart | 27 ++ .../providers/invoice_list_provider.dart | 6 +- .../providers/invoice_pdf_provider.dart | 2 +- .../providers/invoice_provider.dart | 2 +- .../providers/is_payment_admin.dart | 4 +- .../providers/key_service_provider.dart | 2 +- .../providers/last_time_scanned.dart | 0 .../last_used_store_id_provider.dart | 0 .../providers/my_history_provider.dart | 4 +- .../providers/my_stores_provider.dart | 4 +- .../providers/my_structures_provider.dart | 6 +- .../providers/my_wallet_provider.dart | 4 +- .../providers/new_admin_provider.dart | 0 .../providers/ongoing_transaction.dart | 2 +- .../providers/pay_amount_provider.dart | 0 .../providers/refund_amount_provider.dart | 0 .../providers/register_provider.dart | 2 +- .../providers/scan_provider.dart | 6 +- .../providers/selected_interval_provider.dart | 2 +- .../providers/selected_store_history.dart | 10 +- .../providers/selected_store_provider.dart | 51 +++ .../selected_structure_provider.dart | 2 +- .../selected_transactions_provider.dart | 4 +- .../seller_rights_list_providder.dart | 0 .../providers/should_display_tos_dialog.dart | 2 +- .../providers/store_provider.dart | 2 +- .../store_sellers_list_provider.dart | 4 +- .../providers/stores_list_provider.dart | 10 +- .../providers/structure_list_provider.dart | 4 +- .../providers/tos_provider.dart | 4 +- .../providers/transaction_provider.dart | 4 +- .../transfer_structure_provider.dart | 4 +- .../bank_account_holder_repository.dart | 2 +- .../repositories/csv_stores_repository.dart | 32 ++ .../repositories/devices_repository.dart | 4 +- .../repositories/funding_repository.dart | 6 +- .../repositories/invoice_pdf_repository.dart | 2 +- .../repositories/invoices_repository.dart | 2 +- .../store_sellers_repository.dart | 2 +- .../repositories/stores_repository.dart | 8 +- .../repositories/structures_repository.dart | 4 +- .../repositories/tos_repository.dart | 2 +- .../repositories/transaction_repository.dart | 2 +- .../repositories/users_me_repository.dart | 6 +- lib/{paiement => mypayment}/router.dart | 36 +- .../tools/functions.dart | 10 +- .../tools/key_service.dart | 0 .../tools/platform_info.dart | 0 .../components/digit_fade_in_animation.dart | 0 .../ui/components/keyboard.dart | 0 .../ui/components/transaction_card.dart | 4 +- .../ui/mypayment.dart} | 2 +- .../pages/devices_page/add_device_button.dart | 0 .../ui/pages/devices_page/device_item.dart | 93 +++++ .../ui/pages/devices_page/devices_page.dart | 328 ++++++++++++++++++ .../ui/pages/fund_page/confirm_button.dart | 16 +- .../ui/pages/fund_page/fund_page.dart | 12 +- .../ui/pages/fund_page/web_view_modal.dart | 0 .../invoices_admin_page/invoice_card.dart | 240 +++++++++++++ .../invoices_admin_page.dart | 193 +++++++++++ .../invoices_structure_page.dart | 104 ++++++ .../main_page/account_card/account_card.dart | 28 +- .../main_page/account_card/day_divider.dart | 0 .../account_card/device_dialog_box.dart | 0 .../account_card/last_transactions.dart | 8 +- .../ui/pages/main_page/flip_card.dart | 4 +- .../ui/pages/main_page/main_card_button.dart | 0 .../pages/main_page/main_card_template.dart | 2 +- .../ui/pages/main_page/main_page.dart | 34 +- .../seller_card/admin_invoice_card.dart | 4 +- .../main_page/seller_card/store_card.dart | 14 +- .../main_page/seller_card/store_divider.dart | 0 .../main_page/seller_card/store_list.dart | 14 +- .../seller_card/store_seller_card.dart | 4 +- .../seller_card/structure_admin_card.dart | 12 +- .../ui/pages/main_page/tos_dialog.dart | 0 .../ui/pages/pay_page/confirm_button.dart | 13 +- .../ui/pages/pay_page/info_card.dart | 0 .../ui/pages/pay_page/pay_page.dart | 10 +- lib/mypayment/ui/pages/pay_page/qr_code.dart | 59 ++++ .../ui/pages/scan_page/cancel_button.dart | 0 .../pages/scan_page/scan_overlay_shape.dart | 0 .../ui/pages/scan_page/scan_page.dart | 14 +- .../ui/pages/scan_page/scanner.dart | 14 +- .../pages/stats_page/description_shape.dart | 0 .../ui/pages/stats_page/month_bar.dart | 4 +- .../stats_page/month_section_summary.dart | 0 .../ui/pages/stats_page/stats_page.dart | 10 +- .../ui/pages/stats_page/sum_up_card.dart | 2 +- .../ui/pages/stats_page/sum_up_chart.dart | 10 +- .../pages/stats_page/transaction_chart.dart | 8 +- .../pages/stats_page/transactions_detail.dart | 4 +- .../store_admin_page/right_check_box.dart | 2 +- .../pages/store_admin_page/search_result.dart | 14 +- .../store_admin_page/seller_right_card.dart | 6 +- .../store_admin_page/seller_right_dialog.dart | 0 .../store_admin_page/store_admin_page.dart | 10 +- .../ui/pages/store_pages/add_edit_store.dart | 14 +- .../pages/store_stats_page/refund_page.dart | 14 +- .../store_stats_page/store_stats_page.dart | 17 +- .../store_transactions_detail.dart | 10 +- .../pages/store_stats_page/summary_card.dart | 2 +- .../ui/pages/store_stats_page/tool_bar.dart | 303 ++++++++++++++++ .../structure_admin_page/add_store_card.dart | 54 +++ .../admin_store_card.dart | 131 +++++++ .../structure_admin_page.dart | 72 ++++ .../search_result.dart | 4 +- .../transfer_structure_page.dart | 4 +- .../providers/email_popup_state_provider.dart | 18 + lib/others/ui/loading_page.dart | 37 +- lib/others/ui/no_internet_page.dart | 5 +- lib/others/ui/no_module.dart | 6 +- lib/others/ui/rollback_page.dart | 72 ++++ .../providers/selected_store_provider.dart | 36 -- .../ui/pages/devices_page/device_item.dart | 81 ----- .../ui/pages/devices_page/devices_page.dart | 223 ------------ lib/paiement/ui/pages/pay_page/qr_code.dart | 51 --- .../store_stats_page/interval_selector.dart | 240 ------------- .../structure_admin_page/add_store_card.dart | 6 +- .../admin_store_card.dart | 7 + .../structure_admin_page.dart | 13 + lib/ph/providers/is_ph_admin_provider.dart | 8 +- lib/ph/tools/constants.dart | 4 + .../class/association_groupement.dart | 17 +- .../association_filtered_list_provider.dart | 3 +- .../association_groupement_list_provider.dart | 16 + ...sociation_member_sorted_list_provider.dart | 2 +- .../is_phonebook_admin_provider.dart | 59 ++-- lib/phonebook/router.dart | 11 +- lib/phonebook/tools/constant.dart | 8 + lib/phonebook/tools/function.dart | 52 ++- .../add_edit_groupement_page.dart | 172 +++++++++ .../ui/pages/admin_page/admin_page.dart | 2 +- .../association_page/association_page.dart | 2 + .../ui/pages/main_page/main_page.dart | 6 +- .../membership_editor_page.dart | 10 +- lib/phonebook/ui/phonebook.dart | 4 +- lib/purchases/class/product.dart | 9 +- .../providers/purchase_list_provider.dart | 6 +- .../user_purchase_repository.dart | 2 +- .../ui/pages/history_page/history_page.dart | 2 +- .../ui/pages/scan_page/qr_code_scanner.dart | 9 +- .../ui/pages/scan_page/scan_dialog.dart | 108 +++--- lib/raffle/providers/is_raffle_admin.dart | 20 +- lib/raffle/router.dart | 2 +- lib/raffle/tools/constants.dart | 6 + .../is_recommendation_admin_provider.dart | 11 +- lib/recommendation/tools/constants.dart | 5 +- lib/router.dart | 12 +- .../is_seed_library_admin_provider.dart | 11 +- lib/seed-library/tools/constants.dart | 5 + .../providers/module_list_provider.dart | 45 ++- lib/settings/tools/functions.dart | 9 + lib/super_admin/class/module_visibility.dart | 49 --- .../all_my_module_roots_list_provider.dart | 8 - .../providers/is_expanded_list_provider.dart | 9 +- .../providers/module_root_list_provider.dart | 32 -- .../module_visibility_list_provider.dart | 99 ------ .../module_visibility_repository.dart | 45 --- .../edit_module_visibility.dart | 66 ---- .../modules_expansion_panel.dart | 219 ------------ lib/tools/constants.dart | 7 - lib/tools/functions.dart | 96 ++++- .../middlewares/authenticated_middleware.dart | 52 ++- lib/tools/repository/csv_repository.dart | 67 ++++ lib/tools/repository/repository.dart | 8 +- lib/tools/ui/builders/async_child.dart | 7 +- lib/tools/ui/layouts/app_template.dart | 23 +- .../ui/layouts/bottom_modal_template.dart | 118 +++++++ lib/tools/ui/layouts/button.dart | 116 +++++++ lib/tools/ui/layouts/item_chip.dart | 13 +- lib/user/providers/user_provider.dart | 12 - lib/user/repositories/user_repository.dart | 20 -- .../minimal_hyperion_version_provider.dart | 20 ++ lib/vote/providers/can_vote_provider.dart | 13 - .../providers/is_vote_admin_provider.dart | 12 +- lib/vote/tools/constants.dart | 96 +++++ lib/vote/ui/pages/main_page/main_page.dart | 1 - nginx.conf | 22 ++ pubspec.lock | 18 +- pubspec.yaml | 10 +- test/amap/amap_test.dart | 64 ++-- test/amap/is_amap_admin_provider_test.dart | 17 + test/amap/product_provider_test.dart | 10 +- test/amap/user_amount_provider_test.dart | 20 +- .../is_booking_admin_provider_test.dart | 50 --- test/cinema/is_cinema_admin_test.dart | 17 + test/event/is_admin_provider_test.dart | 17 + test/user/user_provider_test.dart | 11 - test/user/user_test.dart | 26 -- test/vote/is_vote_admin_provider_test.dart | 17 + web/index.html | 21 +- web_dev_config.yaml | 5 + 313 files changed, 5373 insertions(+), 2201 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/Titan_PR_Template.md rename .github/{dependabot.yaml => dependabot.yml} (96%) create mode 100644 .vscode/extensions.json rename LICENSE => LICENSE.md (100%) create mode 100644 lib/admin/class/permissions.dart create mode 100644 lib/admin/providers/permission_name_list_provider.dart create mode 100644 lib/admin/providers/permissions_list_provider.dart create mode 100644 lib/admin/repositories/permission_repository.dart create mode 100644 lib/admin/ui/pages/permissions/module_expansion_panel.dart create mode 100644 lib/admin/ui/pages/permissions/permission_expansion_panel.dart create mode 100644 lib/admin/ui/pages/permissions/permission_row.dart create mode 100644 lib/admin/ui/pages/permissions/permissions.dart delete mode 100644 lib/booking/providers/is_manager_provider.dart create mode 100644 lib/centralassociation/class/asso.dart create mode 100644 lib/centralassociation/class/link.dart create mode 100644 lib/centralassociation/providers/centralassociation_asso_provider.dart create mode 100644 lib/centralassociation/repositories/asso_repository.dart create mode 100644 lib/centralassociation/router.dart create mode 100644 lib/centralassociation/tools/constants.dart create mode 100644 lib/centralassociation/tools/functions.dart create mode 100644 lib/centralassociation/ui/centralassociation.dart create mode 100644 lib/centralassociation/ui/pages/asso_list.dart create mode 100644 lib/centralassociation/ui/pages/link_card.dart create mode 100644 lib/centralassociation/ui/pages/main_page.dart delete mode 100644 lib/login/class/account_type.dart delete mode 100644 lib/login/tools/functions.dart rename lib/{paiement => mypayment}/class/create_device.dart (100%) rename lib/{paiement => mypayment}/class/funding_url.dart (100%) rename lib/{paiement => mypayment}/class/history.dart (97%) rename lib/{paiement => mypayment}/class/history_interval.dart (100%) rename lib/{paiement => mypayment}/class/history_refund.dart (100%) rename lib/{paiement => mypayment}/class/init_info.dart (100%) rename lib/{paiement => mypayment}/class/invoice.dart (96%) rename lib/{paiement => mypayment}/class/qr_code_data.dart (100%) rename lib/{paiement => mypayment}/class/qr_code_signature_data.dart (100%) rename lib/{paiement => mypayment}/class/refund.dart (100%) rename lib/{paiement => mypayment}/class/seller.dart (100%) rename lib/{paiement => mypayment}/class/store.dart (96%) rename lib/{paiement => mypayment}/class/structure.dart (100%) rename lib/{paiement => mypayment}/class/tos.dart (100%) rename lib/{paiement => mypayment}/class/transaction.dart (97%) rename lib/{paiement => mypayment}/class/transfert.dart (94%) rename lib/{paiement => mypayment}/class/user_store.dart (95%) rename lib/{paiement => mypayment}/class/wallet.dart (96%) rename lib/{paiement => mypayment}/class/wallet_device.dart (100%) rename lib/{paiement => mypayment}/providers/bank_account_holder_provider.dart (88%) rename lib/{paiement => mypayment}/providers/barcode_provider.dart (88%) rename lib/{paiement => mypayment}/providers/bypass_provider.dart (100%) rename lib/{paiement => mypayment}/providers/device_list_provider.dart (88%) rename lib/{paiement => mypayment}/providers/device_provider.dart (84%) rename lib/{paiement => mypayment}/providers/fund_amount_provider.dart (100%) rename lib/{paiement => mypayment}/providers/funding_url_provider.dart (80%) rename lib/{paiement => mypayment}/providers/has_accepted_tos_provider.dart (91%) create mode 100644 lib/mypayment/providers/history_export_csv_provider.dart rename lib/{paiement => mypayment}/providers/invoice_list_provider.dart (93%) rename lib/{paiement => mypayment}/providers/invoice_pdf_provider.dart (87%) rename lib/{paiement => mypayment}/providers/invoice_provider.dart (84%) rename lib/{paiement => mypayment}/providers/is_payment_admin.dart (79%) rename lib/{paiement => mypayment}/providers/key_service_provider.dart (67%) rename lib/{paiement => mypayment}/providers/last_time_scanned.dart (100%) rename lib/{paiement => mypayment}/providers/last_used_store_id_provider.dart (100%) rename lib/{paiement => mypayment}/providers/my_history_provider.dart (85%) rename lib/{paiement => mypayment}/providers/my_stores_provider.dart (84%) rename lib/{paiement => mypayment}/providers/my_structures_provider.dart (70%) rename lib/{paiement => mypayment}/providers/my_wallet_provider.dart (84%) rename lib/{paiement => mypayment}/providers/new_admin_provider.dart (100%) rename lib/{paiement => mypayment}/providers/ongoing_transaction.dart (90%) rename lib/{paiement => mypayment}/providers/pay_amount_provider.dart (100%) rename lib/{paiement => mypayment}/providers/refund_amount_provider.dart (100%) rename lib/{paiement => mypayment}/providers/register_provider.dart (90%) rename lib/{paiement => mypayment}/providers/scan_provider.dart (83%) rename lib/{paiement => mypayment}/providers/selected_interval_provider.dart (90%) rename lib/{paiement => mypayment}/providers/selected_store_history.dart (79%) create mode 100644 lib/mypayment/providers/selected_store_provider.dart rename lib/{paiement => mypayment}/providers/selected_structure_provider.dart (88%) rename lib/{paiement => mypayment}/providers/selected_transactions_provider.dart (89%) rename lib/{paiement => mypayment}/providers/seller_rights_list_providder.dart (100%) rename lib/{paiement => mypayment}/providers/should_display_tos_dialog.dart (91%) rename lib/{paiement => mypayment}/providers/store_provider.dart (85%) rename lib/{paiement => mypayment}/providers/store_sellers_list_provider.dart (92%) rename lib/{paiement => mypayment}/providers/stores_list_provider.dart (84%) rename lib/{paiement => mypayment}/providers/structure_list_provider.dart (92%) rename lib/{paiement => mypayment}/providers/tos_provider.dart (85%) rename lib/{paiement => mypayment}/providers/transaction_provider.dart (88%) rename lib/{paiement => mypayment}/providers/transfer_structure_provider.dart (84%) rename lib/{paiement => mypayment}/repositories/bank_account_holder_repository.dart (94%) create mode 100644 lib/mypayment/repositories/csv_stores_repository.dart rename lib/{paiement => mypayment}/repositories/devices_repository.dart (89%) rename lib/{paiement => mypayment}/repositories/funding_repository.dart (81%) rename lib/{paiement => mypayment}/repositories/invoice_pdf_repository.dart (92%) rename lib/{paiement => mypayment}/repositories/invoices_repository.dart (97%) rename lib/{paiement => mypayment}/repositories/store_sellers_repository.dart (95%) rename lib/{paiement => mypayment}/repositories/stores_repository.dart (88%) rename lib/{paiement => mypayment}/repositories/structures_repository.dart (93%) rename lib/{paiement => mypayment}/repositories/tos_repository.dart (92%) rename lib/{paiement => mypayment}/repositories/transaction_repository.dart (93%) rename lib/{paiement => mypayment}/repositories/users_me_repository.dart (86%) rename lib/{paiement => mypayment}/router.dart (78%) rename lib/{paiement => mypayment}/tools/functions.dart (96%) rename lib/{paiement => mypayment}/tools/key_service.dart (100%) rename lib/{paiement => mypayment}/tools/platform_info.dart (100%) rename lib/{paiement => mypayment}/ui/components/digit_fade_in_animation.dart (100%) rename lib/{paiement => mypayment}/ui/components/keyboard.dart (100%) rename lib/{paiement => mypayment}/ui/components/transaction_card.dart (98%) rename lib/{paiement/ui/paiement.dart => mypayment/ui/mypayment.dart} (94%) rename lib/{paiement => mypayment}/ui/pages/devices_page/add_device_button.dart (100%) create mode 100644 lib/mypayment/ui/pages/devices_page/device_item.dart create mode 100644 lib/mypayment/ui/pages/devices_page/devices_page.dart rename lib/{paiement => mypayment}/ui/pages/fund_page/confirm_button.dart (92%) rename lib/{paiement => mypayment}/ui/pages/fund_page/fund_page.dart (92%) rename lib/{paiement => mypayment}/ui/pages/fund_page/web_view_modal.dart (100%) create mode 100644 lib/mypayment/ui/pages/invoices_admin_page/invoice_card.dart create mode 100644 lib/mypayment/ui/pages/invoices_admin_page/invoices_admin_page.dart create mode 100644 lib/mypayment/ui/pages/invoices_structure_page/invoices_structure_page.dart rename lib/{paiement => mypayment}/ui/pages/main_page/account_card/account_card.dart (89%) rename lib/{paiement => mypayment}/ui/pages/main_page/account_card/day_divider.dart (100%) rename lib/{paiement => mypayment}/ui/pages/main_page/account_card/device_dialog_box.dart (100%) rename lib/{paiement => mypayment}/ui/pages/main_page/account_card/last_transactions.dart (92%) rename lib/{paiement => mypayment}/ui/pages/main_page/flip_card.dart (93%) rename lib/{paiement => mypayment}/ui/pages/main_page/main_card_button.dart (100%) rename lib/{paiement => mypayment}/ui/pages/main_page/main_card_template.dart (97%) rename lib/{paiement => mypayment}/ui/pages/main_page/main_page.dart (86%) rename lib/{paiement => mypayment}/ui/pages/main_page/seller_card/admin_invoice_card.dart (94%) rename lib/{paiement => mypayment}/ui/pages/main_page/seller_card/store_card.dart (88%) rename lib/{paiement => mypayment}/ui/pages/main_page/seller_card/store_divider.dart (100%) rename lib/{paiement => mypayment}/ui/pages/main_page/seller_card/store_list.dart (84%) rename lib/{paiement => mypayment}/ui/pages/main_page/seller_card/store_seller_card.dart (93%) rename lib/{paiement => mypayment}/ui/pages/main_page/seller_card/structure_admin_card.dart (88%) rename lib/{paiement => mypayment}/ui/pages/main_page/tos_dialog.dart (100%) rename lib/{paiement => mypayment}/ui/pages/pay_page/confirm_button.dart (93%) rename lib/{paiement => mypayment}/ui/pages/pay_page/info_card.dart (100%) rename lib/{paiement => mypayment}/ui/pages/pay_page/pay_page.dart (92%) create mode 100644 lib/mypayment/ui/pages/pay_page/qr_code.dart rename lib/{paiement => mypayment}/ui/pages/scan_page/cancel_button.dart (100%) rename lib/{paiement => mypayment}/ui/pages/scan_page/scan_overlay_shape.dart (100%) rename lib/{paiement => mypayment}/ui/pages/scan_page/scan_page.dart (97%) rename lib/{paiement => mypayment}/ui/pages/scan_page/scanner.dart (94%) rename lib/{paiement => mypayment}/ui/pages/stats_page/description_shape.dart (100%) rename lib/{paiement => mypayment}/ui/pages/stats_page/month_bar.dart (93%) rename lib/{paiement => mypayment}/ui/pages/stats_page/month_section_summary.dart (100%) rename lib/{paiement => mypayment}/ui/pages/stats_page/stats_page.dart (85%) rename lib/{paiement => mypayment}/ui/pages/stats_page/sum_up_card.dart (95%) rename lib/{paiement => mypayment}/ui/pages/stats_page/sum_up_chart.dart (95%) rename lib/{paiement => mypayment}/ui/pages/stats_page/transaction_chart.dart (93%) rename lib/{paiement => mypayment}/ui/pages/stats_page/transactions_detail.dart (81%) rename lib/{paiement => mypayment}/ui/pages/store_admin_page/right_check_box.dart (90%) rename lib/{paiement => mypayment}/ui/pages/store_admin_page/search_result.dart (92%) rename lib/{paiement => mypayment}/ui/pages/store_admin_page/seller_right_card.dart (98%) rename lib/{paiement => mypayment}/ui/pages/store_admin_page/seller_right_dialog.dart (100%) rename lib/{paiement => mypayment}/ui/pages/store_admin_page/store_admin_page.dart (94%) rename lib/{paiement => mypayment}/ui/pages/store_pages/add_edit_store.dart (92%) rename lib/{paiement => mypayment}/ui/pages/store_stats_page/refund_page.dart (93%) rename lib/{paiement => mypayment}/ui/pages/store_stats_page/store_stats_page.dart (73%) rename lib/{paiement => mypayment}/ui/pages/store_stats_page/store_transactions_detail.dart (81%) rename lib/{paiement => mypayment}/ui/pages/store_stats_page/summary_card.dart (98%) create mode 100644 lib/mypayment/ui/pages/store_stats_page/tool_bar.dart create mode 100644 lib/mypayment/ui/pages/structure_admin_page/add_store_card.dart create mode 100644 lib/mypayment/ui/pages/structure_admin_page/admin_store_card.dart create mode 100644 lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart rename lib/{paiement => mypayment}/ui/pages/transfer_structure_page/search_result.dart (96%) rename lib/{paiement => mypayment}/ui/pages/transfer_structure_page/transfer_structure_page.dart (95%) create mode 100644 lib/navigation/providers/email_popup_state_provider.dart create mode 100644 lib/others/ui/rollback_page.dart delete mode 100644 lib/paiement/providers/selected_store_provider.dart delete mode 100644 lib/paiement/ui/pages/devices_page/device_item.dart delete mode 100644 lib/paiement/ui/pages/devices_page/devices_page.dart delete mode 100644 lib/paiement/ui/pages/pay_page/qr_code.dart delete mode 100644 lib/paiement/ui/pages/store_stats_page/interval_selector.dart create mode 100644 lib/ph/tools/constants.dart create mode 100644 lib/phonebook/tools/constant.dart create mode 100644 lib/phonebook/ui/pages/add_edit_groupement/add_edit_groupement_page.dart delete mode 100644 lib/super_admin/class/module_visibility.dart delete mode 100644 lib/super_admin/providers/all_my_module_roots_list_provider.dart delete mode 100644 lib/super_admin/providers/module_root_list_provider.dart delete mode 100644 lib/super_admin/providers/module_visibility_list_provider.dart delete mode 100644 lib/super_admin/repositories/module_visibility_repository.dart delete mode 100644 lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart delete mode 100644 lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart create mode 100644 lib/tools/repository/csv_repository.dart create mode 100644 lib/tools/ui/layouts/bottom_modal_template.dart create mode 100644 lib/tools/ui/layouts/button.dart create mode 100644 lib/version/providers/minimal_hyperion_version_provider.dart delete mode 100644 lib/vote/providers/can_vote_provider.dart create mode 100644 lib/vote/tools/constants.dart delete mode 100644 test/booking/is_booking_admin_provider_test.dart create mode 100644 web_dev_config.yaml diff --git a/.env.template b/.env.template index 130341b4bd..2483115899 100644 --- a/.env.template +++ b/.env.template @@ -1,6 +1,9 @@ # A trailing slash is required -DEV_HOST = "<>" -ALPHA_HOST = "<>" -PROD_HOST = "<>" -PLAUSIBLE_HOST = "<>" -PLAUSIBLE_DOMAIN = "<>" \ No newline at end of file +DEV_URL = "http://localhost:3000/" +ALPHA_URL = "https://titan.dev.eclair.ec-lyon.fr/" +PROD_URL = "https://myecl.fr/" +DEV_HOST = "http://localhost:8000/" +ALPHA_HOST = "https://hyperion.dev.eclair.ec-lyon.fr/" +PROD_HOST = "https://hyperion.myecl.fr/" +#PLAUSIBLE_HOST = "" +#PLAUSIBLE_DOMAIN = "" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..ec354d578d --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ +### Subject of the issue + +Describe your issue here. + +### Your environment + +- Version of Hyperion +- Operating system + +### Steps to reproduce + +Tell us how to reproduce this issue. Please provide a working demo. + +### Expected behaviour + +Tell us what should happen + +### Actual behaviour + +Tell us what happens instead diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..239d17d7d7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,73 @@ +# Description + +## Summary + + + +... + + + +## Issues/PR dependencies + + + +### Issues to be resolved + + + + +### Required PRs + + + + +## Changes Made + + + +- [x] ... +- [ ] ... + +## Additional Notes + + + + +
+ + +# Classification + + + +## Type of Change + +- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 🔨 Refactor (non-breaking change that neither fixes a bug nor adds a feature) +- [ ] 🔧 Infra CI/CD (changes to configs of workflows) +- [ ] 💥 BREAKING CHANGE (fix or feature that require a new minimal version of the front-end) +- [ ] 😶‍🌫️ No impact for the end-users + +## Impact & Scope + +- [ ] Core functionality changes +- [ ] Single module changes +- [ ] Multiple modules changes +- [ ] Other: ... + +## Testing + +- [ ] 1. Tested this locally +- [ ] 2. Added/modified tests that pass the CI (or tested in a downstream fork) +- [ ] 3. Tested in a local client using a pre-prod backend +- [ ] 0. Untestable (exceptionally), will be tested in prod directly + +## Documentation + +- [ ] Updated [the docs](docs.myecl.fr) accordingly : +- [ ] `//` Comments +- [ ] No documentation needed + +
diff --git a/.github/PULL_REQUEST_TEMPLATE/Titan_PR_Template.md b/.github/PULL_REQUEST_TEMPLATE/Titan_PR_Template.md deleted file mode 100644 index 4f9c85a464..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE/Titan_PR_Template.md +++ /dev/null @@ -1,16 +0,0 @@ -Goal of this PR : - -Description of the added features : -- [ ] - -Required unmerged PR : -- [ ] - -Images (if the PR changes the front) : - -| Desktop | Mobile | -| ------- | ------ | -| | | - -Other informations : -- diff --git a/.github/dependabot.yaml b/.github/dependabot.yml similarity index 96% rename from .github/dependabot.yaml rename to .github/dependabot.yml index a75470d0d9..6a08eabcc7 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yml @@ -1,16 +1,16 @@ -# Dependabot version updates -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates - -version: 2 -updates: - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - - # Maintain Python dependencies - - package-ecosystem: "pub" - directory: "/" - schedule: - interval: "monthly" +# Dependabot version updates +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates + +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + + # Maintain Python dependencies + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94fb1be737..4ff728e402 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,11 @@ jobs: java-version: "23.x" cache: "gradle" + # Remove ^ to fix flutter version parsing + - name: Fix Flutter Version in Pubspec + run: | + sed -i 's/flutter: ^/flutter: /' pubspec.yaml + - name: Setup Flutter SDK uses: subosito/flutter-action@v2 with: diff --git a/.github/workflows/lintandformat.yml b/.github/workflows/lintandformat.yml index a62f510a16..7e5f4f4112 100644 --- a/.github/workflows/lintandformat.yml +++ b/.github/workflows/lintandformat.yml @@ -14,6 +14,11 @@ jobs: - name: Check out the code uses: actions/checkout@v4 + # Remove ^ to fix flutter version parsing + - name: Fix Flutter Version in Pubspec + run: | + sed -i 's/flutter: ^/flutter: /' pubspec.yaml + - uses: subosito/flutter-action@v2 with: channel: "stable" diff --git a/.github/workflows/release-mobile.yml b/.github/workflows/release-mobile.yml index 7623f778aa..44d53a3b4e 100644 --- a/.github/workflows/release-mobile.yml +++ b/.github/workflows/release-mobile.yml @@ -58,6 +58,11 @@ jobs: java-version: "23.x" cache: "gradle" + # Remove ^ to fix flutter version parsing + - name: Fix Flutter Version in Pubspec + run: | + sed -i 's/flutter: ^/flutter: /' pubspec.yaml + - name: Setup Flutter SDK uses: subosito/flutter-action@v2 with: @@ -77,7 +82,7 @@ jobs: printf "%s" "$CONFIG" > config.json env: CONFIG: ${{ vars.ALPHA_CONFIG }} - + - name: Configure Alpha config.json if: needs.extract-version.outputs.isAlpha == 'false' run: | diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index d3d3fdd774..946bfa5cb8 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -41,6 +41,11 @@ jobs: - name: Checkout 🛎️ uses: actions/checkout@v4 + # Remove ^ to fix flutter version parsing + - name: Fix Flutter Version in Pubspec + run: | + sed -i 's/flutter: ^/flutter: /' pubspec.yaml + - name: Setup Flutter SDK uses: subosito/flutter-action@v2 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c61edf75b3..2b0ce2403a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ name: Test on: pull_request: - types: [opened, edited, ready_for_review, synchronize] + types: [opened, ready_for_review, synchronize] push: branches: - main @@ -17,6 +17,11 @@ jobs: - name: Check out the code uses: actions/checkout@v4 + # Remove ^ to fix flutter version parsing + - name: Fix Flutter Version in Pubspec + run: | + sed -i 's/flutter: ^/flutter: /' pubspec.yaml + - uses: subosito/flutter-action@v2 with: channel: "stable" diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..42bee81154 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dart-code.flutter", "oderwat.indent-rainbow"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index ad92582bd0..660bf06d65 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,16 @@ { - "editor.formatOnSave": true + "editor.tabSize": 2, + "indentRainbow.colors": [ + "rgba(0,0,255,0.15)", + "rgba(0,255,0,0.15)", + "rgba(255,0,0,0.15)", + "rgba(0,255,255,0.15)", + "rgba(255,0,255,0.15)", + "rgba(255,255,0,0.15)" + ], + "[dart]": { + "editor.formatOnSave": true, + "editor.formatOnType": true, + "editor.rulers": [80] + } } diff --git a/Dockerfile b/Dockerfile index e372ba52f0..cbb7592e15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ -FROM nginx:1.28.0-alpine +FROM nginx:1.28.0-alpine3.21-slim COPY nginx.conf /etc/nginx/nginx.conf -COPY ./build/web/ /app/html \ No newline at end of file +COPY ./build/web/ /app/html +RUN find /app/html/ -type f -size +512c -regex '.*\.\(html\|css\|js\|json\|svg\|ttf\|otf\|woff2\|wasm\|mjs\|symbols\|yaml\|env\)' -exec gzip -k9 {} + \ No newline at end of file diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/README.md b/README.md index ba1c3887be..bee8dccf78 100644 --- a/README.md +++ b/README.md @@ -3,28 +3,213 @@ Titan is a cross platform frontend written in Flutter for an open-source project launched by ÉCLAIR, the computer science association of Ecole Centrale de Lyon. This project aims to provide students of business and engineering schools a digital tool to simplify the association process. Our builds of Titan are called MyECL and can be downloaded from the App Store and from the Google Play Store. +Titan is designed to be launched on Web, Android and iOS platforms. -## Setup environment +## 0. Prerequisites -Install Flutter: -https://docs.flutter.dev/get-started/install +- Git +- VS Code -Setup VS Code for Flutter development: -https://docs.flutter.dev/get-started/editor?tab=vscode +
+On Linux... -Titan is designed to be launched on Web, Android and iOS platforms. +Ensure these commmon packages are installed (adapt the command to your distro's package manager): + +```bash +sudo apt-get install -y git curl unzip zip xz-utils libglu1-mesa +``` + +
+ +## 1. Setup your Flutter environment + +Choose one way of installing Flutter: + +
+ + +### Using VS Code (recommended, even if you use another text editor) + + + +- Clone Titan +- Open Titan in VS Code +- **Install the recommended official Flutter extension** (id: `dart-code.flutter`) +- After installation, the extension prompts you to install automatically the Flutter SDK. + - Click **"Download SDK"**. + To feel organized, you may choose the folder with all your Git repositories. + - Then click **"Clone Flutter"**. + Wait a couple minutes. + +-
+ On Windows... + + If you get an error saying roughly: + + ``` + because the execution of scripts is disabled on this system. Please see "get-help about_signing" for more details. + ``` + + Then in a Powershell, run this to allow scripts executions for your user: + + ```ps1 + Set-ExecutionPolicy Unrestricted -Scope CurrentUser + ``` + +
+ +- After downloading (this takes a few minutes), click **"Add SDK to PATH"** + +
+ +
+ + +### Manually (the old way) + + + +Visit [Set up Flutter for your needs](https://docs.flutter.dev/install/custom), with amounts to visiting these two pages: + +- [Install Flutter manually](https://docs.flutter.dev/install/manual) +- [Flutter editor support](https://docs.flutter.dev/tools/editors) + +You're on your own. + +
+ +### Check your installation: + +- Open a terminal (Ctrl + J) +- Run `flutter doctor`: + +```bash +flutter doctor +``` + +> [!NOTE] +> You do not need to have it all green: having **Flutter** correctly installed, a **browser**, and VS Code **with the extensions** is enough, most people don't need more than that! + +> [!TIP] +> Remember, if you ever have any problem with Flutter, don't hesitate to troubleshoot using: +> +> ```bash +> flutter doctor +> ``` + +- Install Flutter: + https://docs.flutter.dev/get-started/install + +- Setup VS Code for Flutter development: + https://docs.flutter.dev/get-started/editor?tab=vscode + +## 2.Install dependencies + +
+If you already had Flutter installed... + +Upgrade Flutter to the latest stable version: + +```bash +flutter upgrade +``` + +Upgrade Pub, which is the package manager of the Dart language, used by the Flutter framework: + +```bash +flutter pub upgrade +``` + +
+ +Install the dependencies you'll need using Pub (referenced in the [pubspec.yaml](pubspec.yaml) file): + +```bash +flutter pub get +``` + +> [!INFO] +> If you need to remove all modules from your virtual environnement, run: +> +> ```bash +> flutter clean +> ``` + +## 3. Complete the dotenv (`.env`) + +> [!IMPORTANT] +> Copy the [`.env.template`](.env.template) file in a new `.env` file. + +```bash +cp .env.template .env +``` + +You may update [`.env`](.env) to match your Hyperion backends. +If you host a Plausible instance, you may set Plausible's URL to get a few analytics. + +> [!TIP] +> NB: a trailing slash is required at the end of every URL. + +## 4. Launch the client + +> [!WARNING] +> Beforehand, check that the Hyperion instance you want to connect to is up and running. + +The Flutter app needs a host device to run. Below we assume, for development purposes, that you are about to run the **web** version. +NB: a device is a platform that can run the Flutter app; thus a browser does count as a device! + +
+ + +### Using VS Code + + + +1. In the activity bar (the leftmost part), click the "Run and Debug" icon (the play button). +2. Click the green play button. +3. In the terminal, choose your device. + +
+
+ + +### Using the command-line interface + + + +```bash +flutter run --flavor alpha +``` + +More generally you can use: + +```bash +flutter run --flavor [ -d ] +``` + +- Where the flavor can be any of `dev`, `alpha`, or `prod` (whose policy is to only accept the prod client). +- Then in the interactive terminal, choose your device. + Alternatively you can add a flag `-d` to indicate non-interactively your favorite device, for instance: + +```bash +flutter run --flavor alpha -d chrome +flutter run --flavor dev -d web-server +``` + +
-## Configure Titan +### Check the app is running -Update [`.env`](.env) to match your Hyperion's backend: +Check that your Titan instance is up and running by waiting one minute until a browser window opens, or in the `web-server` case, by visiting yourself http://localhost:3000. -`PROD_HOST = "<>` +--- -You can also specify an other host for debuging: +
+ -`DEV_HOST = "<>` +# Beyond initial configuration -NB: a trailing slash is required. + ## Development diff --git a/android/app/build.gradle b/android/app/build.gradle index 84554a9f1a..d66d4e23f5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -74,9 +74,9 @@ def generateMainActivityTask = tasks.register("generateMainActivity") { preBuild.dependsOn(generateMainActivityTask) android { - compileSdkVersion flutter.compileSdkVersion - ndkVersion "27.0.12077973" + ndkVersion "29.0.14206865" namespace "${appIdPrefix}.titan" + compileSdk flutter.compileSdkVersion compileOptions { coreLibraryDesugaringEnabled true @@ -95,7 +95,8 @@ android { defaultConfig { applicationId "${appIdPrefix}.titan" minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion + minSdk flutter.minSdkVersion + targetSdk flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } @@ -151,6 +152,6 @@ dependencies { android { defaultConfig { - minSdkVersion flutter.minSdkVersion + minSdk flutter.minSdkVersion } } diff --git a/lib/admin/class/permissions.dart b/lib/admin/class/permissions.dart new file mode 100644 index 0000000000..df8c7d0037 --- /dev/null +++ b/lib/admin/class/permissions.dart @@ -0,0 +1,114 @@ +class GroupPermission { + GroupPermission({required this.permissionName, required this.groupId}); + late final String permissionName; + late final String groupId; + GroupPermission.fromJson(Map json) { + permissionName = json['permission_name']; + groupId = json['group_id']; + } + Map toJson() { + final data = {}; + data['permission_name'] = permissionName; + data['group_id'] = groupId; + return data; + } + + @override + String toString() => + 'GroupPermission(permissionName: $permissionName, groupId: $groupId)'; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is GroupPermission && + permissionName == other.permissionName && + groupId == other.groupId; + + @override + int get hashCode => permissionName.hashCode ^ groupId.hashCode; +} + +class AccountTypePermission { + AccountTypePermission({ + required this.permissionName, + required this.accountType, + }); + + late final String permissionName; + late final String accountType; + + AccountTypePermission.fromJson(Map json) { + permissionName = json['permission_name']; + accountType = json['account_type']; + } + + Map toJson() { + final data = {}; + data['permission_name'] = permissionName; + data['account_type'] = accountType; + return data; + } + + @override + String toString() => + 'AccountTypePermission(permissionName: $permissionName, accountType: $accountType)'; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AccountTypePermission && + permissionName == other.permissionName && + accountType == other.accountType; + + @override + int get hashCode => permissionName.hashCode ^ accountType.hashCode; +} + +class CorePermission { + CorePermission({ + required this.permissionName, + required this.authorizedAccountTypes, + required this.authorizedGroupIds, + }); + + late final String permissionName; + late final List authorizedAccountTypes; + late final List authorizedGroupIds; + + CorePermission.fromJson(Map json) { + permissionName = json['permission_name']; + authorizedAccountTypes = List.from(json['account_types']); + authorizedGroupIds = List.from(json['groups']); + } + + Map toJson() { + final data = {}; + data['permission_name'] = permissionName; + data['account_types'] = authorizedAccountTypes; + data['groups'] = authorizedGroupIds; + return data; + } + + CorePermission copyWith({ + String? permissionName, + List? authorizedAccountTypes, + List? authorizedGroupIds, + }) { + return CorePermission( + permissionName: permissionName ?? this.permissionName, + authorizedAccountTypes: + authorizedAccountTypes ?? this.authorizedAccountTypes, + authorizedGroupIds: authorizedGroupIds ?? this.authorizedGroupIds, + ); + } + + CorePermission.empty() { + permissionName = ''; + authorizedAccountTypes = []; + authorizedGroupIds = []; + } + + @override + String toString() => + 'CorePermission(permissionName: $permissionName, authorizedAccountTypes: $authorizedAccountTypes, authorizedGroupIds: $authorizedGroupIds)'; +} diff --git a/lib/admin/providers/is_admin_provider.dart b/lib/admin/providers/is_admin_provider.dart index a883e1bd04..177732ee55 100644 --- a/lib/admin/providers/is_admin_provider.dart +++ b/lib/admin/providers/is_admin_provider.dart @@ -1,4 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/loan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/user/providers/user_provider.dart'; final isAdminProvider = StateProvider((ref) { @@ -7,3 +9,7 @@ final isAdminProvider = StateProvider((ref) { .map((e) => e.id) .contains("0a25cb76-4b63-4fd3-b939-da6d9feabf28"); }); + +final isLoanAdminProvider = StateProvider((ref) { + return hasUserPermission(ref, LoanPermissionConstants.manageLoaners); +}); diff --git a/lib/admin/providers/permission_name_list_provider.dart b/lib/admin/providers/permission_name_list_provider.dart new file mode 100644 index 0000000000..5a1abe3980 --- /dev/null +++ b/lib/admin/providers/permission_name_list_provider.dart @@ -0,0 +1,32 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/repositories/permission_repository.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class PermissionsNamesListNotifier extends ListNotifier { + PermissionRepository repository = PermissionRepository(); + PermissionsNamesListNotifier({required String token}) + : super(const AsyncValue.loading()) { + repository.setToken(token); + } + + Future>> loadPermissionsNamesList() async { + return await loadList(repository.getPermissionsNamesList); + } +} + +final permissionsNamesListProvider = + StateNotifierProvider< + PermissionsNamesListNotifier, + AsyncValue> + >((ref) { + final token = ref.watch(tokenProvider); + PermissionsNamesListNotifier notifier = PermissionsNamesListNotifier( + token: token, + ); + tokenExpireWrapperAuth(ref, () async { + await notifier.loadPermissionsNamesList(); + }); + return notifier; + }); diff --git a/lib/admin/providers/permissions_list_provider.dart b/lib/admin/providers/permissions_list_provider.dart new file mode 100644 index 0000000000..796dbbc43e --- /dev/null +++ b/lib/admin/providers/permissions_list_provider.dart @@ -0,0 +1,183 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/permissions.dart'; +import 'package:titan/admin/providers/permission_name_list_provider.dart'; +import 'package:titan/admin/repositories/permission_repository.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class PermissionsNotifier extends ListNotifier { + PermissionRepository repository = PermissionRepository(); + PermissionsNotifier({required String token}) + : super(const AsyncValue.loading()) { + repository.setToken(token); + } + + Future>> loadPermissions() async { + return await loadList(repository.getAllPermissions); + } + + Future addGroupPermission(GroupPermission newPermission) async { + final permission = CorePermission( + permissionName: newPermission.permissionName, + authorizedGroupIds: [newPermission.groupId], + authorizedAccountTypes: [], + ); + return await update( + (_) async => repository.addGroupPermission(newPermission), + (permissions, newPermission) { + final permission = permissions.firstWhere( + (p) => p.permissionName == newPermission.permissionName, + orElse: () => CorePermission.empty(), + ); + if (permission.permissionName.isEmpty) { + permission.permissionName = newPermission.permissionName; + permission.authorizedGroupIds.add( + newPermission.authorizedGroupIds.first, + ); + permissions.add(permission); + } else { + permission.authorizedGroupIds.add( + newPermission.authorizedGroupIds.first, + ); + } + return permissions; + }, + permission, + ); + } + + Future deleteGroupPermission(GroupPermission groupPermission) async { + final permissions = CorePermission( + permissionName: groupPermission.permissionName, + authorizedGroupIds: [groupPermission.groupId], + authorizedAccountTypes: [], + ); + return await update( + (moduleVisibility) async => + repository.deleteGroupPermission(groupPermission), + (permissions, groupPermission) { + final permission = permissions.firstWhere( + (p) => p.permissionName == groupPermission.permissionName, + orElse: () => CorePermission.empty(), + ); + if (permission.permissionName.isNotEmpty) { + permission.authorizedGroupIds.removeWhere( + (id) => id == groupPermission.authorizedGroupIds.first, + ); + } + return permissions; + }, + permissions, + ); + } + + Future addAccountTypePermission( + AccountTypePermission newPermission, + ) async { + final permission = CorePermission( + permissionName: newPermission.permissionName, + authorizedGroupIds: [], + authorizedAccountTypes: [newPermission.accountType], + ); + return await update( + (_) async => repository.addAccountTypePermission(newPermission), + (permissions, permission) { + final perm = permissions.firstWhere( + (p) => p.permissionName == permission.permissionName, + orElse: () => CorePermission.empty(), + ); + if (perm.permissionName.isEmpty) { + perm.permissionName = permission.permissionName; + perm.authorizedAccountTypes.add( + permission.authorizedAccountTypes.first, + ); + permissions.add(perm); + } else { + perm.authorizedAccountTypes.add( + permission.authorizedAccountTypes.first, + ); + } + return permissions; + }, + permission, + ); + } + + Future deleteAccountTypePermission( + AccountTypePermission accountTypePermission, + ) async { + final permission = CorePermission( + permissionName: accountTypePermission.permissionName, + authorizedGroupIds: [], + authorizedAccountTypes: [accountTypePermission.accountType], + ); + + return await update( + (moduleVisibility) async => + repository.deleteAccountTypePermission(accountTypePermission), + (permissions, accountTypePermission) { + final perm = permissions.firstWhere( + (p) => p.permissionName == accountTypePermission.permissionName, + orElse: () => CorePermission.empty(), + ); + if (perm.permissionName.isNotEmpty) { + perm.authorizedAccountTypes.removeWhere( + (at) => at == accountTypePermission.authorizedAccountTypes.first, + ); + } + return permissions; + }, + permission, + ); + } +} + +final permissionsProvider = + StateNotifierProvider< + PermissionsNotifier, + AsyncValue> + >((ref) { + final token = ref.watch(tokenProvider); + PermissionsNotifier notifier = PermissionsNotifier(token: token); + tokenExpireWrapperAuth(ref, () async { + await notifier.loadPermissions(); + }); + return notifier; + }); + +final mappedPermissionsProvider = Provider>((ref) { + final permissionsAsync = ref.watch(permissionsProvider); + return permissionsAsync.maybeWhen( + data: (permissions) { + final Map mappedPermissions = {}; + for (var permission in permissions) { + mappedPermissions[permission.permissionName] = permission; + } + return mappedPermissions; + }, + orElse: () => {}, + ); +}); + +final moduleGroupedPermissionsProvider = Provider>>(( + ref, +) { + final permissionsNames = ref.watch(permissionsNamesListProvider); + return permissionsNames.maybeWhen( + data: (names) { + final Map> modulesPermissions = {}; + + for (var permissionName in names) { + final moduleName = permissionName.split('.').first; + if (!modulesPermissions.containsKey(moduleName)) { + modulesPermissions[moduleName] = [permissionName.split('.')[1]]; + } else { + modulesPermissions[moduleName]!.add(permissionName.split('.')[1]); + } + } + return modulesPermissions; + }, + orElse: () => {}, + ); +}); diff --git a/lib/admin/providers/structure_provider.dart b/lib/admin/providers/structure_provider.dart index 1cd9308633..87e798e6c6 100644 --- a/lib/admin/providers/structure_provider.dart +++ b/lib/admin/providers/structure_provider.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/mypayment/class/structure.dart'; class StructureNotifier extends StateNotifier { StructureNotifier() : super(Structure.empty()); diff --git a/lib/admin/repositories/group_repository.dart b/lib/admin/repositories/group_repository.dart index 869937d95d..4720e81839 100644 --- a/lib/admin/repositories/group_repository.dart +++ b/lib/admin/repositories/group_repository.dart @@ -4,9 +4,6 @@ import 'package:titan/admin/class/simple_group.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/repository/repository.dart'; import 'package:titan/user/class/simple_users.dart'; -import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:titan/tools/exception.dart'; class GroupRepository extends Repository { @override @@ -44,18 +41,10 @@ class GroupRepository extends Repository { } Future deleteMember(Group group, SimpleUser user) async { - final response = await http.delete( - Uri.parse("${Repository.host}${ext}membership"), - headers: headers, - body: json.encode({"user_id": user.id, "group_id": group.id}), + return await delete( + "membership", + body: {"user_id": user.id, "group_id": group.id}, ); - if (response.statusCode == 204) { - return true; - } else if (response.statusCode == 403) { - throw AppException(ErrorType.tokenExpire, response.body); - } else { - throw AppException(ErrorType.notFound, "Failed to update item"); - } } } diff --git a/lib/admin/repositories/permission_repository.dart b/lib/admin/repositories/permission_repository.dart new file mode 100644 index 0000000000..71a63262c7 --- /dev/null +++ b/lib/admin/repositories/permission_repository.dart @@ -0,0 +1,40 @@ +import 'package:titan/admin/class/permissions.dart'; +import 'package:titan/tools/repository/repository.dart'; + +class PermissionRepository extends Repository { + @override + // ignore: overridden_fields + final ext = "permissions/"; + + Future> getAllPermissions() async { + return List.from( + (await getList()).map((e) => CorePermission.fromJson(e)), + ); + } + + Future> getPermissionsNamesList() async { + return List.from(await getList(suffix: "list")); + } + + Future addGroupPermission(GroupPermission groupPermission) async { + await create(groupPermission.toJson()); + return true; + } + + Future addAccountTypePermission( + AccountTypePermission accountTypePermission, + ) async { + await create(accountTypePermission.toJson()); + return true; + } + + Future deleteGroupPermission(GroupPermission groupPermission) async { + return await delete("", body: groupPermission.toJson()); + } + + Future deleteAccountTypePermission( + AccountTypePermission accountTypePermission, + ) async { + return await delete("", body: accountTypePermission.toJson()); + } +} diff --git a/lib/admin/router.dart b/lib/admin/router.dart index 95fa2cbafb..36c8074fb5 100644 --- a/lib/admin/router.dart +++ b/lib/admin/router.dart @@ -3,6 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/admin/ui/pages/groups/edit_group_page/edit_group_page.dart' deferred as edit_group_page; +import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' + deferred as structure_page; +import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart' + deferred as add_edit_structure_page; import 'package:titan/admin/ui/pages/main_page/main_page.dart' deferred as main_page; import 'package:titan/admin/ui/pages/groups/groups_page/groups_page.dart' @@ -11,12 +15,8 @@ import 'package:titan/admin/ui/pages/users_management_page/users_management_page deferred as users_managmement_page; import 'package:titan/admin/ui/pages/group_notifification_page/group_notification_page.dart' deferred as group_notification_page; -import 'package:titan/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart' - deferred as add_edit_structure_page; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/class/module.dart'; -import 'package:titan/admin/ui/pages/structure_page/structure_page.dart' - deferred as structure_page; import 'package:titan/admin/ui/pages/membership/association_membership_page/association_membership_page.dart' deferred as association_membership_page; import 'package:titan/admin/ui/pages/membership/association_membership_detail_page/association_membership_detail_page.dart' @@ -40,6 +40,7 @@ class AdminRouter { static const String groupNotification = '/group_notification'; static const String addGroup = '/add_group'; static const String editGroup = '/edit_group'; + static const String permissions = '/permissions'; static const String associationMemberships = '/association_memberships'; static const String detailAssociationMembership = '/detail_association_membership'; diff --git a/lib/admin/tools/functions.dart b/lib/admin/tools/functions.dart index 4c927c6c6d..882a1fdc80 100644 --- a/lib/admin/tools/functions.dart +++ b/lib/admin/tools/functions.dart @@ -36,3 +36,13 @@ bool _isValidEmail(String email) { ); return emailRegex.hasMatch(email.trim()); } + +String capitalizePermissionName(String permissionName) { + return permissionName + .split('_') + .map( + (word) => + word.isNotEmpty ? word[0].toUpperCase() + word.substring(1) : word, + ) + .join(' '); +} diff --git a/lib/admin/ui/pages/permissions/module_expansion_panel.dart b/lib/admin/ui/pages/permissions/module_expansion_panel.dart new file mode 100644 index 0000000000..dd3851d3d6 --- /dev/null +++ b/lib/admin/ui/pages/permissions/module_expansion_panel.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; +import 'package:titan/admin/ui/pages/permissions/permission_expansion_panel.dart'; + +class ModuleExpansionPanel extends HookConsumerWidget { + final List permissionsNames; + final List groups; + final List accountTypes; + + const ModuleExpansionPanel({ + super.key, + required this.permissionsNames, + required this.groups, + required this.accountTypes, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isExpanded = useState>( + List.generate(permissionsNames.length, (index) => false), + ); + final modulesPermissionNames = ref.watch(moduleGroupedPermissionsProvider); + + return ExpansionPanelList( + expansionCallback: (i, isOpen) { + isExpanded.value[i] = isOpen; + isExpanded.value = List.from(isExpanded.value); + }, + children: modulesPermissionNames.keys.map((module) { + final index = modulesPermissionNames.keys.toList().indexOf(module); + return ExpansionPanel( + canTapOnHeader: true, + isExpanded: isExpanded.value[index], + headerBuilder: (context, isOpen) => Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + module, + style: const TextStyle( + color: Color.fromARGB(255, 0, 0, 0), + fontSize: 22, + fontWeight: FontWeight.w800, + ), + textAlign: TextAlign.center, + ), + ), + body: isExpanded.value[index] + ? PermissionsExpansionPanel( + permissionNames: modulesPermissionNames[module]!, + accountTypes: accountTypes, + groups: groups, + ) + : const SizedBox.shrink(), + ); + }).toList(), + ); + } +} diff --git a/lib/admin/ui/pages/permissions/permission_expansion_panel.dart b/lib/admin/ui/pages/permissions/permission_expansion_panel.dart new file mode 100644 index 0000000000..c31c764a95 --- /dev/null +++ b/lib/admin/ui/pages/permissions/permission_expansion_panel.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/class/account_type.dart'; +import 'package:titan/admin/class/permissions.dart'; +import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; +import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/admin/tools/function.dart'; +import 'package:titan/admin/ui/pages/permissions/permission_row.dart'; + +class PermissionsExpansionPanel extends HookConsumerWidget { + const PermissionsExpansionPanel({ + super.key, + required this.permissionNames, + required this.accountTypes, + required this.groups, + }); + final List permissionNames; + final List accountTypes; + final List groups; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final permissionExpanded = useState>( + List.generate(permissionNames.length, (index) => false), + ); + final permissions = ref.watch(mappedPermissionsProvider); + + final permissionsProviderNotifier = ref.read(permissionsProvider.notifier); + + return ExpansionPanelList( + expansionCallback: (i, isOpen) { + permissionExpanded.value[i] = isOpen; + permissionExpanded.value = List.from(permissionExpanded.value); + }, + children: permissionNames.map((permissionName) { + final index = permissionNames.indexOf(permissionName); + return ExpansionPanel( + canTapOnHeader: true, + isExpanded: permissionExpanded.value[index], + headerBuilder: (context, isOpen) => ListTile( + title: Text( + capitalizePermissionName(permissionName), + style: const TextStyle( + color: Color.fromARGB(255, 0, 0, 0), + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + body: permissionExpanded.value[index] + ? Column( + children: [ + const Text( + AdminTextConstants.accountTypes, + style: TextStyle( + color: Color.fromARGB(255, 0, 0, 0), + fontSize: 20, + fontWeight: FontWeight.w800, + ), + ), + ...accountTypes.map((accountType) { + final permission = AccountTypePermission( + permissionName: permissionName, + accountType: accountType.type, + ); + final isAuthorized = permissions[permissionName]! + .authorizedAccountTypes + .contains(accountType.type); + return PermissionRow( + label: accountType.type, + isAuthorized: isAuthorized, + onUnauthorize: () async { + await permissionsProviderNotifier + .deleteAccountTypePermission(permission); + }, + onAuthorize: () async { + await permissionsProviderNotifier + .addAccountTypePermission(permission); + }, + ); + }), + const Divider(), + const Text( + AdminTextConstants.groups, + style: TextStyle( + color: Color.fromARGB(255, 0, 0, 0), + fontSize: 20, + fontWeight: FontWeight.w900, + ), + ), + ...groups.map((group) { + final permission = GroupPermission( + permissionName: permissionName, + groupId: group.id, + ); + final isAuthorized = permissions[permissionName]! + .authorizedGroupIds + .contains(group.id); + return PermissionRow( + label: group.name, + isAuthorized: isAuthorized, + onUnauthorize: () async { + await permissionsProviderNotifier + .deleteGroupPermission(permission); + }, + onAuthorize: () async { + await permissionsProviderNotifier.addGroupPermission( + permission, + ); + }, + ); + }), + ], + ) + : const SizedBox.shrink(), + ); + }).toList(), + ); + } +} diff --git a/lib/admin/ui/pages/permissions/permission_row.dart b/lib/admin/ui/pages/permissions/permission_row.dart new file mode 100644 index 0000000000..6ad32062ce --- /dev/null +++ b/lib/admin/ui/pages/permissions/permission_row.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; + +class PermissionRow extends StatelessWidget { + final String label; + final bool isAuthorized; + final VoidCallback onAuthorize; + final VoidCallback onUnauthorize; + + const PermissionRow({ + super.key, + required this.label, + required this.isAuthorized, + required this.onAuthorize, + required this.onUnauthorize, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + child: Row( + children: [ + Text( + label, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), + ), + const Spacer(), + GestureDetector( + onTap: isAuthorized ? onUnauthorize : onAuthorize, + child: HeroIcon( + isAuthorized ? HeroIcons.check : HeroIcons.xMark, + size: 40, + color: isAuthorized ? Colors.green : Colors.red, + ), + ), + ], + ), + ); + } +} diff --git a/lib/admin/ui/pages/permissions/permissions.dart b/lib/admin/ui/pages/permissions/permissions.dart new file mode 100644 index 0000000000..cf54beee60 --- /dev/null +++ b/lib/admin/ui/pages/permissions/permissions.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/account_types_list_provider.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/admin/providers/permission_name_list_provider.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; +import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/admin/ui/admin.dart'; +import 'package:titan/admin/ui/pages/permissions/module_expansion_panel.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:tuple/tuple.dart'; + +class PermissionsPage extends HookConsumerWidget { + const PermissionsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final permissionsNames = ref.watch(permissionsNamesListProvider); + final permissions = ref.watch(permissionsProvider); + final groups = ref.watch(allGroupListProvider); + final accountTypes = ref.watch(allAccountTypesListProvider); + return AdminTemplate( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 20), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + SizedBox( + child: Column( + children: [ + const Align( + alignment: Alignment.centerLeft, + child: Text( + AdminTextConstants.modifyModuleVisibility, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorConstants.gradient1, + ), + ), + ), + const SizedBox(height: 30), + Async4Children( + values: Tuple4( + permissionsNames, + permissions, + groups, + accountTypes, + ), + builder: + ( + context, + permissionsNames, + permissions, + groups, + accountTypes, + ) => ModuleExpansionPanel( + permissionsNames: permissionsNames, + groups: groups, + accountTypes: accountTypes, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart index 5ec47ef0e1..6096dae25f 100644 --- a/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart +++ b/lib/admin/ui/pages/structure_page/add_edit_structure_page/add_edit_structure_page.dart @@ -7,8 +7,8 @@ import 'package:titan/admin/class/association_membership_simple.dart'; import 'package:titan/admin/providers/association_membership_list_provider.dart'; import 'package:titan/admin/providers/structure_manager_provider.dart'; import 'package:titan/admin/providers/structure_provider.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/providers/structure_list_provider.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/providers/structure_list_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; diff --git a/lib/admin/ui/pages/structure_page/structure_page.dart b/lib/admin/ui/pages/structure_page/structure_page.dart index 3f302e0a6e..1acd05db86 100644 --- a/lib/admin/ui/pages/structure_page/structure_page.dart +++ b/lib/admin/ui/pages/structure_page/structure_page.dart @@ -5,9 +5,9 @@ import 'package:titan/admin/admin.dart'; import 'package:titan/admin/router.dart'; import 'package:titan/admin/providers/structure_manager_provider.dart'; import 'package:titan/admin/providers/structure_provider.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/providers/bank_account_holder_provider.dart'; -import 'package:titan/paiement/providers/structure_list_provider.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/providers/bank_account_holder_provider.dart'; +import 'package:titan/mypayment/providers/structure_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; @@ -17,7 +17,6 @@ import 'package:titan/tools/ui/styleguide/list_item.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/user/class/simple_users.dart'; import 'package:titan/user/providers/user_list_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/advert/tools/constants.dart b/lib/advert/tools/constants.dart index 43a97337d8..69ae97ed97 100644 --- a/lib/advert/tools/constants.dart +++ b/lib/advert/tools/constants.dart @@ -4,3 +4,8 @@ class AdvertColorConstants { static const Color redGradient1 = Color(0xFF9E131F); static const Color redGradient2 = Color(0xFF590512); } + +class AdvertPermissionConstants { + static const String accessAdvert = 'access_advert'; + static const String manageAdvertisers = 'manage_advertisers'; +} diff --git a/lib/amap/class/cash.dart b/lib/amap/class/cash.dart index 6bfdf9f253..b0d9c3b46d 100644 --- a/lib/amap/class/cash.dart +++ b/lib/amap/class/cash.dart @@ -1,29 +1,42 @@ import 'package:titan/user/class/simple_users.dart'; +import 'package:titan/tools/functions.dart'; class Cash { - Cash({required this.balance, required this.user}); - late final double balance; + Cash({ + required this.balance, + required this.user, + required this.lastOrderDate, + }); + late final int balance; late final SimpleUser user; + late final DateTime lastOrderDate; Cash.fromJson(Map json) { balance = json['balance']; user = SimpleUser.fromJson(json['user']); + lastOrderDate = processDateFromAPI(json['last_order_date']); } Map toJson() { final data = {}; data['balance'] = balance; + data['last_order_date'] = processDateToAPI(lastOrderDate); return data; } - Cash copyWith({SimpleUser? user, double? balance}) { - return Cash(user: user ?? this.user, balance: balance ?? this.balance); + Cash copyWith({SimpleUser? user, int? balance, DateTime? lastOrderDate}) { + return Cash( + user: user ?? this.user, + balance: balance ?? this.balance, + lastOrderDate: lastOrderDate ?? this.lastOrderDate, + ); } - static Cash empty() => Cash(user: SimpleUser.empty(), balance: 0); + static Cash empty() => + Cash(user: SimpleUser.empty(), balance: 0, lastOrderDate: DateTime.now()); @override String toString() { - return 'Cash{balance: $balance, user: $user}'; + return 'Cash{balance: $balance, user: $user, lastOrderDate: $lastOrderDate}'; } } diff --git a/lib/amap/class/delivery.dart b/lib/amap/class/delivery.dart index d823f5a514..dde1f6df51 100644 --- a/lib/amap/class/delivery.dart +++ b/lib/amap/class/delivery.dart @@ -6,12 +6,14 @@ enum DeliveryStatus { creation, available, locked, delivered } class Delivery { Delivery({ + required this.name, required this.deliveryDate, required this.products, required this.id, required this.status, this.expanded = false, }); + late final String name; late final bool expanded; late final DeliveryStatus status; late final DateTime deliveryDate; @@ -19,6 +21,7 @@ class Delivery { late final String id; Delivery.fromJson(Map json) { + name = json['name']; deliveryDate = processDateFromAPIWithoutHour(json['delivery_date']); products = List.from( json['products'].map((x) => Product.fromJson(x)), @@ -30,6 +33,7 @@ class Delivery { Map toJson() { final data = {}; + data['name'] = name; data['delivery_date'] = processDateToAPIWithoutHour(deliveryDate); data['products_ids'] = products.map((e) => e.id).toList(); data['status'] = deliveryStatusToString(status); @@ -38,6 +42,7 @@ class Delivery { } Delivery copyWith({ + String? name, DateTime? deliveryDate, List? products, bool? expanded, @@ -45,6 +50,7 @@ class Delivery { DeliveryStatus? status, }) { return Delivery( + name: name ?? this.name, deliveryDate: deliveryDate ?? this.deliveryDate, products: products ?? this.products, expanded: expanded ?? this.expanded, @@ -54,6 +60,7 @@ class Delivery { } static Delivery empty() => Delivery( + name: '', deliveryDate: DateTime.now(), products: [], expanded: false, @@ -63,6 +70,6 @@ class Delivery { @override String toString() { - return 'Delivery{deliveryDate: $deliveryDate, products: $products, id: $id, status: $status, expanded: $expanded}'; + return 'Delivery{name: $name, deliveryDate: $deliveryDate, products: $products, id: $id, status: $status, expanded: $expanded}'; } } diff --git a/lib/amap/class/order.dart b/lib/amap/class/order.dart index 42e7dd9007..8606946249 100644 --- a/lib/amap/class/order.dart +++ b/lib/amap/class/order.dart @@ -8,6 +8,7 @@ enum CollectionSlot { midDay, evening } class Order { Order({ required this.id, + required this.deliveryName, required this.deliveryId, required this.orderingDate, required this.deliveryDate, @@ -23,17 +24,19 @@ class Order { late final SimpleUser user; late final CollectionSlot collectionSlot; late final String id; + late final String deliveryName; late final DateTime orderingDate; late final DateTime deliveryDate; late final String deliveryId; late final List productsDetail; late final bool expanded; late final List products; - late final double amount, lastAmount; + late final int amount, lastAmount; late final List productsQuantity; Order.fromJson(Map json) { id = json['order_id']; + deliveryName = json['delivery_name']; deliveryId = json['delivery_id']; amount = json['amount']; lastAmount = amount; @@ -56,6 +59,7 @@ class Order { Map toJson() { final data = {}; data['order_id'] = id; + data['delivery_name'] = deliveryName; data['delivery_id'] = deliveryId; data['amount'] = amount; data['ordering_date'] = processDateToAPI(orderingDate); @@ -69,18 +73,20 @@ class Order { Order copyWith({ String? id, + String? deliveryName, DateTime? orderingDate, DateTime? deliveryDate, List? products, bool? expanded, String? deliveryId, - double? amount, - double? lastAmount, + int? amount, + int? lastAmount, CollectionSlot? collectionSlot, SimpleUser? user, }) { return Order( id: id ?? this.id, + deliveryName: deliveryName ?? this.deliveryName, orderingDate: orderingDate ?? this.orderingDate, deliveryDate: deliveryDate ?? this.deliveryDate, productsDetail: products != null @@ -102,6 +108,7 @@ class Order { static Order empty() { return Order( id: '', + deliveryName: '', orderingDate: DateTime.now(), deliveryDate: DateTime.now(), productsDetail: [], @@ -118,6 +125,6 @@ class Order { @override String toString() { - return 'Order{id: $id, orderingDate: $orderingDate, deliveryDate: $deliveryDate, productsDetail: $productsDetail, productsQuantity: $productsQuantity, deliveryId: $deliveryId, products: $products, amount: $amount, lastAmount: $lastAmount, collectionSlot: $collectionSlot, user: $user, expanded: $expanded}'; + return 'Order{id: $id, deliveryName: $deliveryName, orderingDate: $orderingDate, deliveryDate: $deliveryDate, productsDetail: $productsDetail, productsQuantity: $productsQuantity, deliveryId: $deliveryId, products: $products, amount: $amount, lastAmount: $lastAmount, collectionSlot: $collectionSlot, user: $user, expanded: $expanded}'; } } diff --git a/lib/amap/class/product.dart b/lib/amap/class/product.dart index c72bdcebea..39f3cf2f51 100644 --- a/lib/amap/class/product.dart +++ b/lib/amap/class/product.dart @@ -8,7 +8,7 @@ class Product { }); late final String id; late final String name; - late final double price; + late final int price; late final int quantity; late final String category; @@ -31,7 +31,7 @@ class Product { Product copyWith({ String? id, String? name, - double? price, + int? price, int? quantity, String? category, }) => Product( diff --git a/lib/amap/providers/cash_list_provider.dart b/lib/amap/providers/cash_list_provider.dart index 821028df80..f36094375f 100644 --- a/lib/amap/providers/cash_list_provider.dart +++ b/lib/amap/providers/cash_list_provider.dart @@ -18,7 +18,7 @@ class CashListProvider extends ListNotifier { return await add(cashRepository.createCash, cash); } - Future updateCash(Cash addedCash, double previousCashAmount) async { + Future updateCash(Cash addedCash, int previousCashAmount) async { return await update( cashRepository.updateCash, (cashList, c) => cashList diff --git a/lib/amap/providers/is_amap_admin_provider.dart b/lib/amap/providers/is_amap_admin_provider.dart index dbfaab9153..199184f937 100644 --- a/lib/amap/providers/is_amap_admin_provider.dart +++ b/lib/amap/providers/is_amap_admin_provider.dart @@ -1,9 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/amap/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; -final isAmapAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("70db65ee-d533-4f6b-9ffa-a4d70a17b7ef"); // admin_amap +final isAmapAdminProvider = Provider((ref) { + return hasUserPermission(ref, AMAPPermissionConstants.manageAMAP); }); diff --git a/lib/amap/providers/user_amount_provider.dart b/lib/amap/providers/user_amount_provider.dart index 1e244dce5b..70d456b5d7 100644 --- a/lib/amap/providers/user_amount_provider.dart +++ b/lib/amap/providers/user_amount_provider.dart @@ -14,7 +14,7 @@ class UserCashNotifier extends SingleNotifier { return await load(() async => amapUserRepository.getCashByUser(userId)); } - Future updateCash(double amount) async { + Future updateCash(int amount) async { state.when( data: (cash) { final newCash = cash.copyWith(balance: cash.balance + amount); diff --git a/lib/amap/ui/components/order_ui.dart b/lib/amap/ui/components/order_ui.dart index ca26eac62d..419e098be0 100644 --- a/lib/amap/ui/components/order_ui.dart +++ b/lib/amap/ui/components/order_ui.dart @@ -39,10 +39,25 @@ class OrderUI extends HookConsumerWidget { displayToast(context, type, msg); } + final style = TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AMAPColorConstants.textDark, + ); + + final tp = TextPainter( + text: TextSpan(text: order.deliveryName, style: style), + textDirection: TextDirection.ltr, + maxLines: isDetail ? 3 : 2, + ); + final maxTextWidth = isDetail ? 180.0 : 150.0; + tp.layout(maxWidth: maxTextWidth); + final lines = tp.computeLineMetrics().length; + return CardLayout( id: order.id, - width: 195, - height: isDetail ? 100 : 150, + width: 214, + height: isDetail ? 104 + 26.0 * lines : 144 + 26.0 * lines, colors: const [ AMAPColorConstants.lightGradient1, AMAPColorConstants.greenGradient1, @@ -55,14 +70,33 @@ class OrderUI extends HookConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '${AppLocalizations.of(context)!.amapThe} ${DateFormat.yMd(locale).format(order.deliveryDate)}', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AMAPColorConstants.textDark, - ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxTextWidth), + child: Text( + order.deliveryName, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AMAPColorConstants.textDark, + ), + maxLines: isDetail ? 3 : 2, + ), + ), + Text( + '${AppLocalizations.of(context)!.amapThe} ${DateFormat.yMd(locale).format(order.deliveryDate)}', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AMAPColorConstants.textDark, + ), + ), + ], ), + if (!isDetail) GestureDetector( onTap: () { @@ -90,7 +124,7 @@ class OrderUI extends HookConsumerWidget { ), const Spacer(), Text( - "${order.amount.toStringAsFixed(2)}€", + "${(order.amount / 100).toStringAsFixed(2)}€", style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, diff --git a/lib/amap/ui/components/product_ui.dart b/lib/amap/ui/components/product_ui.dart index 68d2901568..a80a04b09f 100644 --- a/lib/amap/ui/components/product_ui.dart +++ b/lib/amap/ui/components/product_ui.dart @@ -60,7 +60,7 @@ class ProductCard extends StatelessWidget { ), const SizedBox(height: 4), AutoSizeText( - '${product.price.toStringAsFixed(2)} €', + '${(product.price / 100).toStringAsFixed(2)} €', maxLines: 1, minFontSize: 10, overflow: TextOverflow.ellipsis, diff --git a/lib/amap/ui/pages/admin_page/account_handler.dart b/lib/amap/ui/pages/admin_page/account_handler.dart index 3a4b9dff46..f3a084b4f4 100644 --- a/lib/amap/ui/pages/admin_page/account_handler.dart +++ b/lib/amap/ui/pages/admin_page/account_handler.dart @@ -49,7 +49,7 @@ class AccountHandler extends HookConsumerWidget { editingController: editingController, ), HorizontalListView( - height: 135, + height: 145, children: [ const SizedBox(width: 15), GestureDetector( @@ -69,7 +69,7 @@ class AccountHandler extends HookConsumerWidget { } }, child: CardLayout( - height: 100, + height: 110, width: 100, colors: const [ AMAPColorConstants.green1, diff --git a/lib/amap/ui/pages/admin_page/adding_user_card.dart b/lib/amap/ui/pages/admin_page/adding_user_card.dart index ae703a1a7a..ec5d137568 100644 --- a/lib/amap/ui/pages/admin_page/adding_user_card.dart +++ b/lib/amap/ui/pages/admin_page/adding_user_card.dart @@ -16,7 +16,9 @@ class AddingUserCard extends HookConsumerWidget { final cashNotifier = ref.watch(cashListProvider.notifier); return GestureDetector( onTap: () { - cashNotifier.addCash(Cash(balance: 0, user: user)); + cashNotifier.addCash( + Cash(balance: 0, user: user, lastOrderDate: DateTime.now()), + ); onAdd(); }, child: Container( diff --git a/lib/amap/ui/pages/admin_page/cash_container.dart b/lib/amap/ui/pages/admin_page/cash_container.dart index ddbc8290f8..cd1d2f89b1 100644 --- a/lib/amap/ui/pages/admin_page/cash_container.dart +++ b/lib/amap/ui/pages/admin_page/cash_container.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/amap/providers/cash_list_provider.dart'; @@ -13,8 +14,12 @@ class CashContainer extends HookConsumerWidget { final cash = ref.watch(cashListProvider); return AsyncChild( value: cash, - builder: (context, cash) => - Row(children: cash.map((e) => UserCashUi(cash: e)).toList()), + builder: (context, cash) => Row( + children: cash + .sorted((a, b) => b.lastOrderDate.isAfter(a.lastOrderDate) ? 1 : -1) + .map((e) => UserCashUi(cash: e)) + .toList(), + ), loaderColor: AMAPColorConstants.greenGradient2, ); } diff --git a/lib/amap/ui/pages/admin_page/delivery_handler.dart b/lib/amap/ui/pages/admin_page/delivery_handler.dart index 60be021992..27d7835128 100644 --- a/lib/amap/ui/pages/admin_page/delivery_handler.dart +++ b/lib/amap/ui/pages/admin_page/delivery_handler.dart @@ -32,7 +32,7 @@ class DeliveryHandler extends HookConsumerWidget { ), const SizedBox(height: 10), HorizontalListView( - height: 200, + height: 233, children: [ const SizedBox(width: 15, height: 195), GestureDetector( diff --git a/lib/amap/ui/pages/admin_page/delivery_ui.dart b/lib/amap/ui/pages/admin_page/delivery_ui.dart index 38f0da2991..962728d286 100644 --- a/lib/amap/ui/pages/admin_page/delivery_ui.dart +++ b/lib/amap/ui/pages/admin_page/delivery_ui.dart @@ -48,9 +48,24 @@ class DeliveryUi extends HookConsumerWidget { displayToast(context, type, msg); } + final style = TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AMAPColorConstants.textDark, + ); + + final tp = TextPainter( + text: TextSpan(text: delivery.name, style: style), + textDirection: TextDirection.ltr, + maxLines: 3, + ); + final maxTextWidth = 200.0; + tp.layout(maxWidth: maxTextWidth); + final lines = tp.computeLineMetrics().length; + return CardLayout( id: delivery.id, - height: 160, + height: 155 + 26.0 * (lines), width: 280, shadowColor: AMAPColorConstants.textDark.withValues(alpha: 0.2), padding: const EdgeInsets.all(10), @@ -69,13 +84,31 @@ class DeliveryUi extends HookConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '${AppLocalizations.of(context)!.amapThe} ${DateFormat.yMd(locale).format(delivery.deliveryDate)}', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AMAPColorConstants.textDark, - ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxTextWidth), + child: Text( + delivery.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AMAPColorConstants.textDark, + ), + maxLines: 3, + ), + ), + Text( + '${AppLocalizations.of(context)!.amapThe} ${DateFormat.yMd(locale).format(delivery.deliveryDate)}', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AMAPColorConstants.textDark, + ), + ), + ], ), GestureDetector( onTap: () { diff --git a/lib/amap/ui/pages/admin_page/user_cash_ui.dart b/lib/amap/ui/pages/admin_page/user_cash_ui.dart index 0f7f7a48e6..3aab6e6642 100644 --- a/lib/amap/ui/pages/admin_page/user_cash_ui.dart +++ b/lib/amap/ui/pages/admin_page/user_cash_ui.dart @@ -101,7 +101,7 @@ class UserCashUi extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AutoSizeText( - '${cash.balance.toStringAsFixed(2)} €', + '${(cash.balance / 100).toStringAsFixed(2)} €', maxLines: 1, minFontSize: 10, overflow: TextOverflow.ellipsis, @@ -163,9 +163,16 @@ class UserCashUi extends HookConsumerWidget { .read(cashListProvider.notifier) .updateCash( cash.copyWith( - balance: double.parse( - amount.text.replaceAll(',', '.'), - ), + balance: + (100 * + double.parse( + amount.text + .replaceAll( + ',', + '.', + ), + )) + .round(), ), cash.balance, ) diff --git a/lib/amap/ui/pages/admin_page/user_cash_ui_layout.dart b/lib/amap/ui/pages/admin_page/user_cash_ui_layout.dart index 96cfb08258..7d75e6384e 100644 --- a/lib/amap/ui/pages/admin_page/user_cash_ui_layout.dart +++ b/lib/amap/ui/pages/admin_page/user_cash_ui_layout.dart @@ -10,7 +10,7 @@ class UserCashUiLayout extends StatelessWidget { Widget build(BuildContext context) { return CardLayout( width: 150, - height: 100, + height: 110, colors: const [AMAPColorConstants.green1, AMAPColorConstants.textLight], shadowColor: AMAPColorConstants.textDark.withValues(alpha: 0.2), padding: const EdgeInsets.symmetric(horizontal: 17.0, vertical: 5), diff --git a/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart b/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart index 8c8a08ef01..ddc6068769 100644 --- a/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart +++ b/lib/amap/ui/pages/delivery_pages/add_edit_delivery_cmd_page.dart @@ -19,6 +19,7 @@ import 'package:titan/tools/ui/widgets/align_left_text.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/widgets/date_entry.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/tools/ui/widgets/text_entry.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -34,6 +35,7 @@ class AddEditDeliveryPage extends HookConsumerWidget { final dateController = useTextEditingController( text: isEdit ? DateFormat.yMd(locale).format(delivery.deliveryDate) : '', ); + final nameController = useTextEditingController(); final productList = ref.watch(productListProvider); final sortedProductsList = ref.watch(sortedByCategoryProductsProvider); final selected = ref.watch(selectedListProvider); @@ -62,6 +64,14 @@ class AddEditDeliveryPage extends HookConsumerWidget { AppLocalizations.of(context)!.amapAddDelivery, color: AMAPColorConstants.green2, ), + const SizedBox(height: 20), + TextEntry( + label: AMAPTextConstants.name, + controller: nameController, + color: AMAPColorConstants.greenGradient2, + enabledColor: AMAPColorConstants.enabled, + ), + Container( margin: const EdgeInsets.symmetric(vertical: 30), child: DateEntry( @@ -97,7 +107,12 @@ class AddEditDeliveryPage extends HookConsumerWidget { style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w800, - color: Colors.black, + color: Color.fromARGB( + 0, + 255, + 0, + 0, + ), ), ), ), @@ -135,6 +150,7 @@ class AddEditDeliveryPage extends HookConsumerWidget { if (formKey.currentState!.validate()) { final date = dateController.value.text; final del = Delivery( + name: nameController.text, id: isEdit ? delivery.id : '', products: products .where( @@ -202,7 +218,7 @@ class AddEditDeliveryPage extends HookConsumerWidget { } else { displayToastWithContext( TypeMsg.error, - alreadyExistCommandMsg, + AMAPTextConstants.addingError, ); } } diff --git a/lib/amap/ui/pages/delivery_pages/product_ui_check.dart b/lib/amap/ui/pages/delivery_pages/product_ui_check.dart index 73719a1ab1..036704793b 100644 --- a/lib/amap/ui/pages/delivery_pages/product_ui_check.dart +++ b/lib/amap/ui/pages/delivery_pages/product_ui_check.dart @@ -36,7 +36,7 @@ class ProductUi extends ConsumerWidget { width: 50, alignment: Alignment.centerRight, child: Text( - "${product.price.toStringAsFixed(2)}€", + "${(product.price / 100).toStringAsFixed(2)}€", style: const TextStyle(fontSize: 13), ), ), @@ -44,7 +44,7 @@ class ProductUi extends ConsumerWidget { Checkbox( value: isModification, checkColor: AMAPColorConstants.background, - activeColor: AMAPColorConstants.green2, + activeColor: const Color.fromARGB(223, 121, 164, 0), onChanged: (value) { onclick(); }, diff --git a/lib/amap/ui/pages/detail_delivery_page/detail_page.dart b/lib/amap/ui/pages/detail_delivery_page/detail_page.dart index 6aef738a04..7a6c8b380d 100644 --- a/lib/amap/ui/pages/detail_delivery_page/detail_page.dart +++ b/lib/amap/ui/pages/detail_delivery_page/detail_page.dart @@ -49,6 +49,13 @@ class DetailDeliveryPage extends HookConsumerWidget { padding: const EdgeInsets.all(30), child: Column( children: [ + Text( + delivery.name, + style: const TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + ), + ), Text( "${AppLocalizations.of(context)!.amapDeliveryDate} : ${DateFormat.yMd(locale).format(delivery.deliveryDate)}", style: const TextStyle( diff --git a/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart b/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart index 0c8065e7f5..730a6e4d54 100644 --- a/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart +++ b/lib/amap/ui/pages/detail_delivery_page/order_detail_ui.dart @@ -41,7 +41,7 @@ class DetailOrderUI extends HookConsumerWidget { return CardLayout( width: 250, - height: 145 + (20.0 * order.products.length), + height: 139 + (24.0 * order.products.length), colors: const [ AMAPColorConstants.lightGradient1, AMAPColorConstants.greenGradient1, @@ -83,7 +83,7 @@ class DetailOrderUI extends HookConsumerWidget { SizedBox( width: 90, child: Text( - "${product.quantity} (${(product.quantity * product.price).toStringAsFixed(2)}€)", + "${product.quantity} (${((product.quantity * product.price) / 100).toStringAsFixed(2)}€)", textAlign: TextAlign.right, style: const TextStyle( fontSize: 17, @@ -115,7 +115,7 @@ class DetailOrderUI extends HookConsumerWidget { ), const Spacer(), Text( - "${order.amount.toStringAsFixed(2)}€", + "${(order.amount / 100).toStringAsFixed(2)}€", style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, @@ -128,7 +128,7 @@ class DetailOrderUI extends HookConsumerWidget { Row( children: [ Text( - "${AppLocalizations.of(context)!.amapAmount} : ${userCash.balance.toStringAsFixed(2)}€", + "${AppLocalizations.of(context)!.amapAmount} : ${(userCash.balance / 100).toStringAsFixed(2)}€", style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, diff --git a/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart b/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart index 6376a197b3..1bde478c0b 100644 --- a/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart +++ b/lib/amap/ui/pages/detail_delivery_page/product_detail_ui.dart @@ -18,7 +18,7 @@ class ProductDetailCard extends StatelessWidget { Widget build(BuildContext context) { return CardLayout( width: 130, - height: 100, + height: 104, colors: const [ AMAPColorConstants.lightGradient1, AMAPColorConstants.lightGradient2, @@ -53,7 +53,7 @@ class ProductDetailCard extends StatelessWidget { ), const SizedBox(height: 4), AutoSizeText( - '${(product.price * quantity).toStringAsFixed(2)} €', + '${((product.price * quantity) / 100).toStringAsFixed(2)} €', maxLines: 1, minFontSize: 10, overflow: TextOverflow.ellipsis, diff --git a/lib/amap/ui/pages/detail_page/detail_page.dart b/lib/amap/ui/pages/detail_page/detail_page.dart index 3e204b7226..c8d8fe1947 100644 --- a/lib/amap/ui/pages/detail_page/detail_page.dart +++ b/lib/amap/ui/pages/detail_page/detail_page.dart @@ -38,7 +38,7 @@ class DetailPage extends HookConsumerWidget { ), child: Column( children: [ - const SizedBox(height: 50), + const SizedBox(height: 110), AlignLeftText( padding: const EdgeInsets.symmetric(horizontal: 20), AppLocalizations.of(context)!.amapProducts, diff --git a/lib/amap/ui/pages/list_products_page/product_choice_button.dart b/lib/amap/ui/pages/list_products_page/product_choice_button.dart index 07fd6634da..54c17aeb59 100644 --- a/lib/amap/ui/pages/list_products_page/product_choice_button.dart +++ b/lib/amap/ui/pages/list_products_page/product_choice_button.dart @@ -118,7 +118,7 @@ class ProductChoiceButton extends HookConsumerWidget { } }, child: Text( - "${AppLocalizations.of(context)!.amapConfirm} (${order.amount.toStringAsFixed(2)}€)", + "${AppLocalizations.of(context)!.amapConfirm} (${(order.amount / 100).toStringAsFixed(2)}€)", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, diff --git a/lib/amap/ui/pages/list_products_page/product_ui_list.dart b/lib/amap/ui/pages/list_products_page/product_ui_list.dart index 20da57a609..c3ac4888fc 100644 --- a/lib/amap/ui/pages/list_products_page/product_ui_list.dart +++ b/lib/amap/ui/pages/list_products_page/product_ui_list.dart @@ -40,7 +40,7 @@ class ProductUiInList extends ConsumerWidget { Container( alignment: Alignment.centerRight, child: Text( - "${p.price.toStringAsFixed(2)}€", + "${(p.price / 100).toStringAsFixed(2)}€", style: const TextStyle(fontSize: 15), ), ), diff --git a/lib/amap/ui/pages/main_page/delivery_ui.dart b/lib/amap/ui/pages/main_page/delivery_ui.dart index 903e3eb26a..58e3d6a3f2 100644 --- a/lib/amap/ui/pages/main_page/delivery_ui.dart +++ b/lib/amap/ui/pages/main_page/delivery_ui.dart @@ -27,7 +27,7 @@ class DeliveryUi extends HookConsumerWidget { child: Container( margin: const EdgeInsets.symmetric(vertical: 10.0), padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 8), - height: 50, + height: 70, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), @@ -56,15 +56,38 @@ class DeliveryUi extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(width: 10), - Text( - '${AppLocalizations.of(context)!.amapThe} ${DateFormat.yMd(locale).format(delivery.deliveryDate)}', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: (selected && showSelected) - ? Colors.white - : AMAPColorConstants.textDark, - ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width - 200, + ), + child: Text( + delivery.name, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: (selected && showSelected) + ? Colors.white + : AMAPColorConstants.textDark, + ), + ), + ), + Text( + '${AppLocalizations.of(context)!.amapThe} ${DateFormat.yMd(locale).format(delivery.deliveryDate)}', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: (selected && showSelected) + ? Colors.white + : AMAPColorConstants.textDark, + ), + ), + ], ), const Spacer(), Text( diff --git a/lib/amap/ui/pages/main_page/main_page.dart b/lib/amap/ui/pages/main_page/main_page.dart index 3f38eb0bee..b1130ba195 100644 --- a/lib/amap/ui/pages/main_page/main_page.dart +++ b/lib/amap/ui/pages/main_page/main_page.dart @@ -85,7 +85,7 @@ class AmapMainPage extends HookConsumerWidget { child: AsyncChild( value: balance, builder: (context, s) => Text( - "${AppLocalizations.of(context)!.amapAmount} : ${s.balance.toStringAsFixed(2)}€", + "${AppLocalizations.of(context)!.amapAmount} : ${(s.balance / 100).toStringAsFixed(2)}€", style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -150,7 +150,11 @@ class AmapMainPage extends HookConsumerWidget { animation: animation, child: Container( width: double.infinity, - height: MediaQuery.of(context).size.height - 150, + height: + 350.0 + 90 * availableDeliveriesIds.length > + MediaQuery.of(context).size.height - 150 + ? 350.0 + 90 * availableDeliveriesIds.length + : MediaQuery.of(context).size.height - 150, decoration: BoxDecoration( gradient: const RadialGradient( colors: [ diff --git a/lib/amap/ui/pages/main_page/orders_section.dart b/lib/amap/ui/pages/main_page/orders_section.dart index f59c684f73..2258a57d2d 100644 --- a/lib/amap/ui/pages/main_page/orders_section.dart +++ b/lib/amap/ui/pages/main_page/orders_section.dart @@ -46,7 +46,7 @@ class OrderSection extends HookConsumerWidget { ), const SizedBox(height: 10), HorizontalListView( - height: 195, + height: 196, children: [ const SizedBox(width: 15), GestureDetector( diff --git a/lib/amap/ui/pages/product_pages/add_edit_product.dart b/lib/amap/ui/pages/product_pages/add_edit_product.dart index 5c716ce56c..254f2e999e 100644 --- a/lib/amap/ui/pages/product_pages/add_edit_product.dart +++ b/lib/amap/ui/pages/product_pages/add_edit_product.dart @@ -31,7 +31,9 @@ class AddEditProduct extends HookConsumerWidget { final categories = ref.watch(categoryListProvider); final nameController = useTextEditingController(text: product.name); final priceController = useTextEditingController( - text: isEdit ? product.price.toStringAsFixed(2).replaceAll('.', ',') : "", + text: isEdit + ? (product.price / 100).toStringAsFixed(2).replaceAll('.', ',') + : "", ); final beginState = isEdit ? product.category @@ -180,9 +182,15 @@ class AddEditProduct extends HookConsumerWidget { Product newProduct = Product( id: isEdit ? product.id : "", name: nameController.text, - price: double.parse( - priceController.text.replaceAll(',', '.'), - ), + price: + (100 * + double.parse( + priceController.text.replaceAll( + ',', + '.', + ), + )) + .round(), category: cate, quantity: 0, ); diff --git a/lib/auth/providers/openid_provider.dart b/lib/auth/providers/openid_provider.dart index d0c3bcf3c8..d57b76932e 100644 --- a/lib/auth/providers/openid_provider.dart +++ b/lib/auth/providers/openid_provider.dart @@ -119,7 +119,7 @@ class OpenIdTokenProvider final String tokenKey = "token"; final String refreshTokenKey = "refresh_token"; final String redirectURLScheme = "${getTitanPackageName()}://authorized"; - final String redirectURL = "${getTitanURL()}/static.html"; + final String redirectURL = "${getTitanURL()}static.html"; final AuthorizationServiceConfiguration authorizationServiceConfiguration = AuthorizationServiceConfiguration( authorizationEndpoint: "${Repository.host}auth/authorize", diff --git a/lib/booking/providers/is_admin_provider.dart b/lib/booking/providers/is_admin_provider.dart index 4cd58e778d..03bed8687b 100644 --- a/lib/booking/providers/is_admin_provider.dart +++ b/lib/booking/providers/is_admin_provider.dart @@ -1,9 +1,28 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/booking/providers/user_manager_list_provider.dart'; +import 'package:titan/booking/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; -final isAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("0a25cb76-4b63-4fd3-b939-da6d9feabf28"); +final isManagersAdminProvider = Provider((ref) { + return hasUserPermission(ref, BookingPermissionConstants.manageManagers); +}); + +final isRoomsAdminProvider = Provider((ref) { + return hasUserPermission(ref, BookingPermissionConstants.manageRooms); +}); + +final isBookingAdminProvider = Provider((ref) { + final isManagersAdmin = ref.watch(isManagersAdminProvider); + final isRoomsAdmin = ref.watch(isRoomsAdminProvider); + return isManagersAdmin || isRoomsAdmin; +}); + +final isManagerProvider = StateProvider((ref) { + final managers = ref.watch(userManagerListProvider); + final managersName = managers.when( + data: (managers) => managers.map((e) => e.name).toList(), + loading: () => [], + error: (error, stackTrace) => [], + ); + return managersName.isNotEmpty; }); diff --git a/lib/booking/providers/is_manager_provider.dart b/lib/booking/providers/is_manager_provider.dart deleted file mode 100644 index 58342860e6..0000000000 --- a/lib/booking/providers/is_manager_provider.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/booking/providers/user_manager_list_provider.dart'; - -final isManagerProvider = StateProvider((ref) { - final managers = ref.watch(userManagerListProvider); - final managersName = managers.when( - data: (managers) => managers.map((e) => e.name).toList(), - loading: () => [], - error: (error, stackTrace) => [], - ); - return managersName.isNotEmpty; -}); diff --git a/lib/booking/router.dart b/lib/booking/router.dart index 7821bb328b..76dd9fddc0 100644 --- a/lib/booking/router.dart +++ b/lib/booking/router.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/booking/providers/is_admin_provider.dart'; -import 'package:titan/booking/providers/is_manager_provider.dart'; import 'package:titan/booking/ui/pages/admin_pages/add_edit_manager_page.dart' deferred as add_edit_manager_page; import 'package:titan/booking/ui/pages/detail_pages/detail_booking.dart' @@ -56,7 +55,7 @@ class BookingRouter { path: admin, builder: () => admin_page.AdminPage(), middleware: [ - AdminMiddleware(ref, isAdminProvider), + AdminMiddleware(ref, isBookingAdminProvider), DeferredLoadingMiddleware(admin_page.loadLibrary), ], children: [ @@ -64,7 +63,7 @@ class BookingRouter { path: room, builder: () => add_edit_room_page.AddEditRoomPage(), middleware: [ - AdminMiddleware(ref, isAdminProvider), + AdminMiddleware(ref, isRoomsAdminProvider), DeferredLoadingMiddleware(add_edit_room_page.loadLibrary), ], ), @@ -72,7 +71,7 @@ class BookingRouter { path: manager, builder: () => add_edit_manager_page.AddEditManagerPage(), middleware: [ - AdminMiddleware(ref, isAdminProvider), + AdminMiddleware(ref, isManagersAdminProvider), DeferredLoadingMiddleware(add_edit_manager_page.loadLibrary), ], ), diff --git a/lib/booking/tools/constants.dart b/lib/booking/tools/constants.dart index d34e7df9f4..0607ac0ccb 100644 --- a/lib/booking/tools/constants.dart +++ b/lib/booking/tools/constants.dart @@ -9,3 +9,9 @@ final weekDaysOrdered = [ WeekDays.saturday, WeekDays.sunday, ]; + +class BookingPermissionConstants { + static const String accessBooking = 'access_booking'; + static const String manageManagers = 'manage_managers'; + static const String manageRooms = 'manage_rooms'; +} diff --git a/lib/booking/ui/calendar/appointment_data_source.dart b/lib/booking/ui/calendar/appointment_data_source.dart index db9c918741..d04312f4e6 100644 --- a/lib/booking/ui/calendar/appointment_data_source.dart +++ b/lib/booking/ui/calendar/appointment_data_source.dart @@ -16,7 +16,10 @@ class AppointmentDataSource extends CalendarDataSource { DateTime getEndTime(int index) => appointments![index].end; @override - Color getColor(int index) => generateColor(appointments![index].room.name); + Color getColor(int index) => + generateColor(appointments![index].room.name).withValues( + alpha: appointments![index].decision == Decision.pending ? 0.3 : 1.0, + ); @override String getSubject(int index) { diff --git a/lib/booking/ui/calendar/calendar.dart b/lib/booking/ui/calendar/calendar.dart index ee15f535c5..3d7bc975b2 100644 --- a/lib/booking/ui/calendar/calendar.dart +++ b/lib/booking/ui/calendar/calendar.dart @@ -1,14 +1,24 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/booking/class/booking.dart'; +import 'package:titan/booking/providers/booking_provider.dart'; import 'package:titan/booking/providers/confirmed_booking_list_provider.dart'; +import 'package:titan/booking/providers/manager_booking_list_provider.dart'; import 'package:titan/booking/providers/manager_confirmed_booking_list_provider.dart'; +import 'package:titan/booking/providers/selected_days_provider.dart'; +import 'package:titan/booking/providers/user_booking_list_provider.dart'; +import 'package:titan/booking/router.dart'; +import 'package:titan/booking/tools/constants.dart'; import 'package:titan/booking/ui/calendar/appointment_data_source.dart'; import 'package:titan/booking/ui/calendar/calendar_dialog.dart'; import 'package:titan/navigation/providers/is_web_format_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; class Calendar extends HookConsumerWidget { final bool isManagerPage; @@ -17,20 +27,111 @@ class Calendar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final bookings = isManagerPage - ? ref.watch(managerConfirmedBookingListProvider) + ? ref.watch(managerBookingListProvider) : ref.watch(confirmedBookingListProvider); + final bookingNotifier = ref.watch(bookingProvider.notifier); + final selectedDaysNotifier = ref.watch(selectedDaysProvider.notifier); + final bookingListNotifier = ref.watch(managerBookingListProvider.notifier); + final confirmedBookingListNotifier = ref.watch( + confirmedBookingListProvider.notifier, + ); final isWebFormat = ref.watch(isWebFormatProvider); + final managerConfirmedBookingListNotifier = ref.watch( + managerConfirmedBookingListProvider.notifier, + ); final CalendarController calendarController = CalendarController(); + void handleBooking(Booking booking) { + bookingNotifier.setBooking(booking); + final recurrentDays = SfCalendar.parseRRule( + booking.recurrenceRule, + booking.start, + ).weekDays; + selectedDaysNotifier.setSelectedDays(recurrentDays); + QR.to(BookingRouter.root + BookingRouter.manager + BookingRouter.addEdit); + } + void calendarTapped(CalendarTapDetails details, BuildContext context) { if (details.targetElement == CalendarElement.appointment || details.targetElement == CalendarElement.agenda) { final Booking booking = details.appointments![0]; showDialog( context: context, - builder: (context) => isManagerPage - ? CalendarDialog(booking: booking, isManager: true) - : CalendarDialog(booking: booking, isManager: false), + builder: (context) => CalendarDialog( + booking: booking, + isManager: isManagerPage, + onEdit: () { + handleBooking(booking); + }, + onCopy: () { + handleBooking(booking.copyWith(id: "")); + }, + onConfirm: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: BookingTextConstants.confirm, + descriptions: BookingTextConstants.confirmBooking, + onYes: () async { + await tokenExpireWrapper(ref, () async { + Booking newBooking = booking.copyWith( + decision: Decision.approved, + ); + bookingListNotifier + .toggleConfirmed(newBooking, Decision.approved) + .then((value) { + if (value) { + ref + .read(userBookingListProvider.notifier) + .loadUserBookings(); + confirmedBookingListNotifier.addBooking( + newBooking, + ); + managerConfirmedBookingListNotifier.addBooking( + newBooking, + ); + } + }); + }); + }, + ); + }, + ); + }, + onDecline: () async { + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: BookingTextConstants.decline, + descriptions: BookingTextConstants.declineBooking, + onYes: () async { + await tokenExpireWrapper(ref, () async { + Booking newBooking = booking.copyWith( + decision: Decision.declined, + ); + bookingListNotifier + .toggleConfirmed(newBooking, Decision.declined) + .then((value) { + if (value) { + ref + .read(userBookingListProvider.notifier) + .loadUserBookings(); + confirmedBookingListNotifier.deleteBooking( + newBooking, + ); + managerConfirmedBookingListNotifier + .deleteBooking(newBooking); + } + }); + }); + }, + ); + }, + ); + }, + ), ); } } @@ -43,7 +144,16 @@ class Calendar extends HookConsumerWidget { children: [ SfCalendar( onTap: (details) => calendarTapped(details, context), - dataSource: AppointmentDataSource(res), + dataSource: AppointmentDataSource( + !isManagerPage + ? res + : res + .where( + (booking) => + booking.decision != Decision.declined, + ) + .toList(), + ), controller: calendarController, view: CalendarView.week, selectionDecoration: BoxDecoration( diff --git a/lib/booking/ui/calendar/calendar_dialog.dart b/lib/booking/ui/calendar/calendar_dialog.dart index 819e8576f1..f07d22000e 100644 --- a/lib/booking/ui/calendar/calendar_dialog.dart +++ b/lib/booking/ui/calendar/calendar_dialog.dart @@ -4,20 +4,36 @@ import 'package:titan/booking/class/booking.dart'; import 'package:titan/booking/ui/calendar/calendar_dialog_button.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/ui/layouts/card_button.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; class CalendarDialog extends StatelessWidget { final Booking booking; + final Function()? onEdit, onConfirm, onDecline, onCopy; final bool isManager; const CalendarDialog({ super.key, required this.booking, - required this.isManager, + this.onEdit, + this.onConfirm, + this.onDecline, + this.onCopy, + this.isManager = false, }); @override Widget build(BuildContext context) { final locale = Localizations.localeOf(context); + final isNotEnded = booking.recurrenceRule.isNotEmpty + ? SfCalendar.parseRRule( + booking.recurrenceRule, + booking.start, + ).endDate!.isAfter(DateTime.now()) + : booking.end.isAfter(DateTime.now()); + final showButton = + (isNotEnded && booking.decision == Decision.pending) || isManager; + return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), child: Stack( @@ -116,6 +132,57 @@ class CalendarDialog extends StatelessWidget { ), ], ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (showButton) + GestureDetector( + onTap: onEdit, + child: CardButton( + color: Colors.white, + shadowColor: Colors.grey.withValues(alpha: 0.2), + child: HeroIcon( + HeroIcons.pencil, + color: Colors.black, + ), + ), + ), + if (showButton) const Spacer(), + GestureDetector( + onTap: onCopy, + child: CardButton( + color: Colors.white, + shadowColor: Colors.grey.withValues(alpha: 0.2), + child: HeroIcon( + HeroIcons.documentDuplicate, + color: Colors.black, + ), + ), + ), + const Spacer(), + GestureDetector( + onTap: onConfirm, + child: CardButton( + color: Colors.white, + shadowColor: Colors.grey.withValues(alpha: 0.2), + child: HeroIcon(HeroIcons.check, color: Colors.black), + ), + ), + const Spacer(), + GestureDetector( + onTap: onDecline, + child: CardButton( + color: Colors.black, + shadowColor: Colors.black.withValues(alpha: 0.2), + child: const HeroIcon( + HeroIcons.xMark, + color: Colors.white, + ), + ), + ), + ], + ), ], ], ), diff --git a/lib/booking/ui/pages/admin_pages/admin_page.dart b/lib/booking/ui/pages/admin_pages/admin_page.dart index 3cba6f3e47..f73b05eca0 100644 --- a/lib/booking/ui/pages/admin_pages/admin_page.dart +++ b/lib/booking/ui/pages/admin_pages/admin_page.dart @@ -5,6 +5,7 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/admin/providers/group_id_provider.dart'; import 'package:titan/booking/class/manager.dart'; +import 'package:titan/booking/providers/is_admin_provider.dart'; import 'package:titan/service/class/room.dart'; import 'package:titan/booking/providers/confirmed_booking_list_provider.dart'; import 'package:titan/booking/providers/manager_list_provider.dart'; @@ -28,6 +29,8 @@ class AdminPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { const double minCalendarHeight = 400; const double sumOfHeightOfOthersWidgets = 282; + final isRoomsAdmin = ref.watch(isRoomsAdminProvider); + final isManagersAdmin = ref.watch(isManagersAdminProvider); final roomList = ref.watch(roomListProvider); final roomNotifier = ref.watch(roomProvider.notifier); final managerNotifier = ref.watch(managerProvider.notifier); @@ -53,146 +56,150 @@ class AdminPage extends HookConsumerWidget { children: [ const SizedBox(height: 20), const Expanded(child: Calendar(isManagerPage: false)), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.bookingRoom, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 149, 149, 149), + if (isRoomsAdmin) ...[ + const SizedBox(height: 30), + Padding( + padding: EdgeInsets.symmetric(horizontal: 30.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.bookingRoom, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color.fromARGB(255, 149, 149, 149), + ), ), ), ), - ), - const SizedBox(height: 20), - roomList.when( - data: (List data) => SingleChildScrollView( - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(width: 15), - ItemChip( - onTap: () { - roomNotifier.setRoom(Room.empty()); - managerIdNotifier.setId(""); - QR.to( - BookingRouter.root + - BookingRouter.admin + - BookingRouter.room, - ); - }, - child: const HeroIcon( - HeroIcons.plus, - color: Colors.black, - ), - ), - ...data.map( - (e) => ItemChip( + const SizedBox(height: 20), + roomList.when( + data: (List data) => SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(width: 15), + ItemChip( onTap: () { - roomNotifier.setRoom(e); - managerIdNotifier.setId(e.managerId); + roomNotifier.setRoom(Room.empty()); + managerIdNotifier.setId(""); QR.to( BookingRouter.root + BookingRouter.admin + BookingRouter.room, ); }, - child: Text( - capitalize(e.name), - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, + child: const HeroIcon( + HeroIcons.plus, + color: Colors.black, + ), + ), + ...data.map( + (e) => ItemChip( + onTap: () { + roomNotifier.setRoom(e); + managerIdNotifier.setId(e.managerId); + QR.to( + BookingRouter.root + + BookingRouter.admin + + BookingRouter.room, + ); + }, + child: Text( + capitalize(e.name), + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), ), ), ), - ), - const SizedBox(width: 15), - ], + const SizedBox(width: 15), + ], + ), ), + error: (Object error, StackTrace? stackTrace) { + return Center(child: Text('Error $error')); + }, + loading: () { + return const Center(child: CircularProgressIndicator()); + }, ), - error: (Object error, StackTrace? stackTrace) { - return Center(child: Text('Error $error')); - }, - loading: () { - return const Center(child: CircularProgressIndicator()); - }, - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of(context)!.bookingManager, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 149, 149, 149), + ], + if (isManagersAdmin) ...[ + const SizedBox(height: 20), + Padding( + padding: EdgeInsets.symmetric(horizontal: 30.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.bookingManager, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color.fromARGB(255, 149, 149, 149), + ), ), ), ), - ), - const SizedBox(height: 20), - managerList.when( - data: (List data) => SingleChildScrollView( - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(width: 15), - ItemChip( - onTap: () { - managerNotifier.setManager(Manager.empty()); - groupIdNotifier.setId(""); - QR.to( - BookingRouter.root + - BookingRouter.admin + - BookingRouter.manager, - ); - }, - child: const HeroIcon( - HeroIcons.plus, - color: Colors.black, - ), - ), - ...data.map( - (e) => ItemChip( + const SizedBox(height: 20), + managerList.when( + data: (List data) => SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(width: 15), + ItemChip( onTap: () { - managerNotifier.setManager(e); - groupIdNotifier.setId(e.groupId); + managerNotifier.setManager(Manager.empty()); + groupIdNotifier.setId(""); QR.to( BookingRouter.root + BookingRouter.admin + BookingRouter.manager, ); }, - child: Text( - e.name, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, + child: const HeroIcon( + HeroIcons.plus, + color: Colors.black, + ), + ), + ...data.map( + (e) => ItemChip( + onTap: () { + managerNotifier.setManager(e); + groupIdNotifier.setId(e.groupId); + QR.to( + BookingRouter.root + + BookingRouter.admin + + BookingRouter.manager, + ); + }, + child: Text( + e.name, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), ), ), ), - ), - const SizedBox(width: 15), - ], + const SizedBox(width: 15), + ], + ), ), + error: (Object error, StackTrace? stackTrace) { + return Center(child: Text('Error $error')); + }, + loading: () { + return const Center(child: CircularProgressIndicator()); + }, ), - error: (Object error, StackTrace? stackTrace) { - return Center(child: Text('Error $error')); - }, - loading: () { - return const Center(child: CircularProgressIndicator()); - }, - ), + ], const SizedBox(height: 20), ], ), diff --git a/lib/booking/ui/pages/main_page/main_page.dart b/lib/booking/ui/pages/main_page/main_page.dart index 98ae22f341..c0a11202a6 100644 --- a/lib/booking/ui/pages/main_page/main_page.dart +++ b/lib/booking/ui/pages/main_page/main_page.dart @@ -7,7 +7,6 @@ import 'package:titan/booking/class/booking.dart'; import 'package:titan/booking/providers/booking_provider.dart'; import 'package:titan/booking/providers/confirmed_booking_list_provider.dart'; import 'package:titan/booking/providers/is_admin_provider.dart'; -import 'package:titan/booking/providers/is_manager_provider.dart'; import 'package:titan/booking/providers/manager_booking_list_provider.dart'; import 'package:titan/booking/providers/selected_days_provider.dart'; import 'package:titan/booking/providers/user_booking_list_provider.dart'; @@ -35,7 +34,7 @@ class BookingMainPage extends HookConsumerWidget { const double minCalendarHeight = 400; const double sumOfHeightOfOthersWidgets = 361; final isManager = ref.watch(isManagerProvider); - final isAdmin = ref.watch(isAdminProvider); + final isAdmin = ref.watch(isBookingAdminProvider); final bookingsNotifier = ref.watch(userBookingListProvider.notifier); final confirmedBookingsNotifier = ref.watch( confirmedBookingListProvider.notifier, diff --git a/lib/booking/ui/pages/manager_page/list_booking.dart b/lib/booking/ui/pages/manager_page/list_booking.dart index c5a928de3f..e87a0670e2 100644 --- a/lib/booking/ui/pages/manager_page/list_booking.dart +++ b/lib/booking/ui/pages/manager_page/list_booking.dart @@ -192,12 +192,7 @@ class ListBooking extends HookConsumerWidget { ); }, onCopy: () { - bookingNotifier.setBooking(e.copyWith(id: "")); - QR.to( - BookingRouter.root + - BookingRouter.manager + - BookingRouter.addEdit, - ); + handleBooking(e.copyWith(id: "")); }, onDelete: () async {}, ), diff --git a/lib/centralassociation/class/asso.dart b/lib/centralassociation/class/asso.dart new file mode 100644 index 0000000000..275ae2de8a --- /dev/null +++ b/lib/centralassociation/class/asso.dart @@ -0,0 +1,27 @@ +import 'package:titan/centralassociation/class/link.dart'; + +class Asso { + Asso({required this.name, required this.description, required this.icon}); + late final String name; + late final String description; + late final List linkList; + late final String icon; + + Asso.fromJson(Map json) { + name = json["name"]; + description = json["description"]; + icon = json["icon"]; + linkList = List.from(json["links"].map((e) => Link.fromJson(e))); + } + + Asso.empty() { + name = ''; + description = ''; + linkList = []; + } + + @override + String toString() { + return 'Asso{name: $name, description: $description, link_list: $linkList, icon: $icon}'; + } +} diff --git a/lib/centralassociation/class/link.dart b/lib/centralassociation/class/link.dart new file mode 100644 index 0000000000..6fb41b5a88 --- /dev/null +++ b/lib/centralassociation/class/link.dart @@ -0,0 +1,12 @@ +class Link { + Link({required this.name, required this.url, required this.icon}); + late final String name; + late final String url; + late final String icon; + + Link.fromJson(Map l) { + name = l["name"]; + icon = l["icon"]; + url = l["url"]; + } +} diff --git a/lib/centralassociation/providers/centralassociation_asso_provider.dart b/lib/centralassociation/providers/centralassociation_asso_provider.dart new file mode 100644 index 0000000000..829f560c68 --- /dev/null +++ b/lib/centralassociation/providers/centralassociation_asso_provider.dart @@ -0,0 +1,29 @@ +import 'package:titan/centralassociation/class/asso.dart'; +import 'package:titan/centralassociation/class/link.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/centralassociation/repositories/asso_repository.dart'; +import 'package:titan/tools/providers/list_notifier.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; + +class AssoNotifier extends ListNotifier { + AssoRepository assoRepository = AssoRepository(); + AssoNotifier() : super(const AsyncValue.loading()); + + late List allAssos = []; + late List allLinks = []; + + Future initState() async { + allAssos = await assoRepository.getAssoList(); + allLinks = allAssos.expand((element) => element.linkList).toList(); + state = AsyncValue.data(allAssos); + } +} + +final assoProvider = + StateNotifierProvider>>((ref) { + AssoNotifier notifier = AssoNotifier(); + tokenExpireWrapperAuth(ref, () async { + await notifier.initState(); + }); + return notifier; + }); diff --git a/lib/centralassociation/repositories/asso_repository.dart b/lib/centralassociation/repositories/asso_repository.dart new file mode 100644 index 0000000000..520f4e612b --- /dev/null +++ b/lib/centralassociation/repositories/asso_repository.dart @@ -0,0 +1,39 @@ +import 'package:http/http.dart' as http; +import 'package:titan/tools/logs/logger.dart'; +import 'dart:convert'; +import 'package:titan/centralassociation/class/asso.dart'; + +class AssoRepository { + static const String host = "https://assos.myecl.fr/assos_links.json"; + final Map headers = { + "Content-Type": "application/json; charset=UTF-8", + "Accept": "application/json", + }; + + static final Logger logger = Logger(); + void initLogger() { + logger.init(); + } + + Future> getAssoList() async { + try { + final response = await http.get(Uri.parse(host), headers: headers); + if (response.statusCode == 200) { + try { + final data = jsonDecode(response.body); + final test = data.map((asso) => Asso.fromJson(asso)).toList(); + return test; + } catch (e) { + logger.error("GET $host\nError while decoding response"); + return []; + } + } else { + logger.error("GET $host\n${response.statusCode} ${response.body}"); + return []; + } + } catch (e) { + logger.error("GET $host\nError while fetching response"); + return []; + } + } +} diff --git a/lib/centralassociation/router.dart b/lib/centralassociation/router.dart new file mode 100644 index 0000000000..205869a2d0 --- /dev/null +++ b/lib/centralassociation/router.dart @@ -0,0 +1,32 @@ +import 'package:either_dart/either.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/centralassociation/tools/constants.dart'; +import 'package:titan/centralassociation/ui/pages/main_page.dart' + deferred as main_page; +import 'package:titan/drawer/class/module.dart'; +import 'package:titan/tools/middlewares/authenticated_middleware.dart'; +import 'package:titan/tools/middlewares/deferred_middleware.dart'; +import 'package:qlevar_router/qlevar_router.dart'; + +class CentralassociationRouter { + final Ref ref; + static const String root = '/centralassociation'; + static final Module module = Module( + name: CentralassociationTextConstants.centralassociation, + icon: const Left(HeroIcons.atSymbol), + root: CentralassociationRouter.root, + selected: false, + ); + CentralassociationRouter(this.ref); + + QRoute route() => QRoute( + name: "centralassociation", + path: CentralassociationRouter.root, + builder: () => main_page.CentralassociationMainPage(), + middleware: [ + AuthenticatedMiddleware(ref), + DeferredLoadingMiddleware(main_page.loadLibrary), + ], + ); +} diff --git a/lib/centralassociation/tools/constants.dart b/lib/centralassociation/tools/constants.dart new file mode 100644 index 0000000000..d2d3841d12 --- /dev/null +++ b/lib/centralassociation/tools/constants.dart @@ -0,0 +1,8 @@ +class CentralassociationTextConstants { + static const String centralassociation = "Centralassociation"; + static const String close = 'Fermer'; + static const String error = "Erreur"; + static const String imagePath = "https://assos.myecl.fr/assets/svg_icons/"; + static const String openLink = 'Accéder au site'; + static const String unableToOpen = "Impossible d'ouvrir le lien"; +} diff --git a/lib/centralassociation/tools/functions.dart b/lib/centralassociation/tools/functions.dart new file mode 100644 index 0000000000..8f37ad331d --- /dev/null +++ b/lib/centralassociation/tools/functions.dart @@ -0,0 +1,10 @@ +import 'package:titan/centralassociation/tools/constants.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void openLink(String url) async { + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + } else { + throw '${CentralassociationTextConstants.unableToOpen} $url'; + } +} diff --git a/lib/centralassociation/ui/centralassociation.dart b/lib/centralassociation/ui/centralassociation.dart new file mode 100644 index 0000000000..f26b7f5660 --- /dev/null +++ b/lib/centralassociation/ui/centralassociation.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/ui/widgets/top_bar.dart'; +import 'package:titan/centralassociation/tools/constants.dart'; +import 'package:titan/centralassociation/router.dart'; + +class CentralassociationTemplate extends StatelessWidget { + final Widget child; + const CentralassociationTemplate({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + color: Colors.white, + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const TopBar( + title: CentralassociationTextConstants.centralassociation, + root: CentralassociationRouter.root, + ), + Expanded(child: child), + ], + ), + ), + ), + ); + } +} diff --git a/lib/centralassociation/ui/pages/asso_list.dart b/lib/centralassociation/ui/pages/asso_list.dart new file mode 100644 index 0000000000..b6cdf09172 --- /dev/null +++ b/lib/centralassociation/ui/pages/asso_list.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:titan/centralassociation/class/asso.dart'; +import 'package:titan/centralassociation/ui/pages/link_card.dart'; + +class AssoList extends StatelessWidget { + final Asso asso; + const AssoList({super.key, required this.asso}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + asso.name, + style: const TextStyle(fontSize: 23, fontWeight: FontWeight.w900), + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: asso.linkList + .map((link) => LinkCard(link: link)) + .toList(), + ), + ], + ), + ); + } +} diff --git a/lib/centralassociation/ui/pages/link_card.dart b/lib/centralassociation/ui/pages/link_card.dart new file mode 100644 index 0000000000..8636240a26 --- /dev/null +++ b/lib/centralassociation/ui/pages/link_card.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/centralassociation/class/link.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:titan/centralassociation/tools/constants.dart'; +import 'package:titan/centralassociation/tools/functions.dart'; + +class LinkCard extends HookConsumerWidget { + final Link link; + const LinkCard({super.key, required this.link}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 7), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.3), + blurRadius: 8, + spreadRadius: 2, + offset: const Offset(2, 3), + ), + ], + ), + height: 70, + child: TextButton( + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)), + ), + overlayColor: WidgetStateProperty.all( + const Color.fromARGB(37, 0, 0, 0), + ), + ), + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 3), + width: 45, + height: 45, + child: link.icon.endsWith('.svg') + ? SvgPicture.network( + "${CentralassociationTextConstants.imagePath}${link.icon}", + ) + : Image.network( + "${CentralassociationTextConstants.imagePath}${link.icon}", + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + link.name, + style: const TextStyle(fontSize: 18, color: Colors.black), + ), + ], + ), + ), + ], + ), + onPressed: () { + openLink(link.url); + }, + ), + ); + } +} diff --git a/lib/centralassociation/ui/pages/main_page.dart b/lib/centralassociation/ui/pages/main_page.dart new file mode 100644 index 0000000000..c97c2dc959 --- /dev/null +++ b/lib/centralassociation/ui/pages/main_page.dart @@ -0,0 +1,27 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:flutter/material.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/centralassociation/ui/centralassociation.dart'; +import 'package:titan/centralassociation/providers/centralassociation_asso_provider.dart'; +import 'package:titan/centralassociation/ui/pages/asso_list.dart'; + +class CentralassociationMainPage extends HookConsumerWidget { + const CentralassociationMainPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final assos = ref.watch(assoProvider); + + return CentralassociationTemplate( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: AsyncChild( + value: assos, + builder: (context, assos) => Column( + children: [...assos.map((asso) => AssoList(asso: asso))], + ), + ), + ), + ); + } +} diff --git a/lib/cinema/providers/is_cinema_admin.dart b/lib/cinema/providers/is_cinema_admin.dart index 0e8f1e5afb..7795cb7c86 100644 --- a/lib/cinema/providers/is_cinema_admin.dart +++ b/lib/cinema/providers/is_cinema_admin.dart @@ -1,9 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/cinema/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; -final isCinemaAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("ce5f36e6-5377-489f-9696-de70e2477300"); // admin_cinema +final isCinemaAdminProvider = Provider((ref) { + return hasUserPermission(ref, CinemaPermissionConstants.manageSessions); }); diff --git a/lib/cinema/tools/constants.dart b/lib/cinema/tools/constants.dart index 2b625d283a..1f69f12c0e 100644 --- a/lib/cinema/tools/constants.dart +++ b/lib/cinema/tools/constants.dart @@ -3,3 +3,8 @@ import 'package:flutter/material.dart'; class CinemaColorConstants { static const Color tmdbColor = Color(0xffe2b616); } + +class CinemaPermissionConstants { + static const String accessCinema = 'access_cinema'; + static const String manageSessions = 'manage_sessions'; +} diff --git a/lib/event/providers/is_admin_provider.dart b/lib/event/providers/is_admin_provider.dart index 393cc60a2b..6fce6207c1 100644 --- a/lib/event/providers/is_admin_provider.dart +++ b/lib/event/providers/is_admin_provider.dart @@ -1,9 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/event/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; -final isEventAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("b0357687-2211-410a-9e2a-144519eeaafa"); // admin_calendar +final isEventAdminProvider = Provider((ref) { + return hasUserPermission(ref, EventPermissionConstants.manageEvents); }); diff --git a/lib/event/tools/constants.dart b/lib/event/tools/constants.dart index 9152f84640..06659a6c3b 100644 --- a/lib/event/tools/constants.dart +++ b/lib/event/tools/constants.dart @@ -7,3 +7,8 @@ const eventDayKeys = [ 'eventDaySat', 'eventDaySun', ]; + +class EventPermissionConstants { + static const String accessEvents = 'access_calendar'; + static const String manageEvents = 'manage_events'; +} diff --git a/lib/loan/tools/constants.dart b/lib/loan/tools/constants.dart index b4e6a9c3b9..76a7100276 100644 --- a/lib/loan/tools/constants.dart +++ b/lib/loan/tools/constants.dart @@ -6,3 +6,8 @@ class LoanColorConstants { static const Color redGradient2 = Color.fromARGB(255, 172, 32, 10); static const Color urgentRed = Color.fromARGB(255, 99, 13, 0); } + +class LoanPermissionConstants { + static const String accessLoans = 'access_loan'; + static const String manageLoaners = 'manage_loaners'; +} diff --git a/lib/login/class/account_type.dart b/lib/login/class/account_type.dart deleted file mode 100644 index 06a2f386e7..0000000000 --- a/lib/login/class/account_type.dart +++ /dev/null @@ -1 +0,0 @@ -enum AccountType { student, formerstudent, staff, admin, association } diff --git a/lib/login/router.dart b/lib/login/router.dart index 2ac01f366f..419ecef09a 100644 --- a/lib/login/router.dart +++ b/lib/login/router.dart @@ -10,6 +10,7 @@ import 'package:qlevar_router/qlevar_router.dart'; class LoginRouter { final Ref ref; static const String root = '/login'; + static const String mailReceived = '/mail_received'; LoginRouter(this.ref); QRoute route() => QRoute( diff --git a/lib/login/tools/functions.dart b/lib/login/tools/functions.dart deleted file mode 100644 index 7c34e55d06..0000000000 --- a/lib/login/tools/functions.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:titan/login/class/account_type.dart'; - -String accountTypeToID(AccountType type) { - switch (type) { - case AccountType.student: - return '39691052-2ae5-4e12-99d0-7a9f5f2b0136'; - case AccountType.formerstudent: - return 'ab4c7503-41b3-11ee-8177-089798f1a4a5'; - case AccountType.staff: - return '703056c4-be9d-475c-aa51-b7fc62a96aaa'; - case AccountType.admin: - return '0a25cb76-4b63-4fd3-b939-da6d9feabf28'; - case AccountType.association: - return '29751438-103c-42f2-b09b-33fbb20758a7'; - } -} diff --git a/lib/login/ui/app_sign_in.dart b/lib/login/ui/app_sign_in.dart index b6d8ee126c..8195c8b0a4 100644 --- a/lib/login/ui/app_sign_in.dart +++ b/lib/login/ui/app_sign_in.dart @@ -6,6 +6,8 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/feed/router.dart'; +import 'package:titan/login/providers/animation_provider.dart'; +import 'package:titan/login/tools/constants.dart'; import 'package:titan/login/ui/auth_page.dart'; import 'package:titan/login/ui/components/sign_in_up_bar.dart'; import 'package:titan/tools/constants.dart'; @@ -104,6 +106,7 @@ class AppSignIn extends HookConsumerWidget { .watch(authTokenProvider) .when( data: (token) { + controller?.reverse(); QR.to(pathForwarding.path); }, error: (e, s) { @@ -115,7 +118,9 @@ class AppSignIn extends HookConsumerWidget { )!.loginLoginFailed, ); }, - loading: () {}, + loading: () { + controller?.forward(); + }, ); }, color: ColorConstants.tertiary, diff --git a/lib/login/ui/web/left_panel.dart b/lib/login/ui/web/left_panel.dart index d4f6a93d43..b4c3de6691 100644 --- a/lib/login/ui/web/left_panel.dart +++ b/lib/login/ui/web/left_panel.dart @@ -3,6 +3,8 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/login/providers/animation_provider.dart'; +import 'package:titan/login/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; @@ -81,6 +83,7 @@ class LeftPanel extends HookConsumerWidget { .watch(authTokenProvider) .when( data: (token) { + controller?.reverse(); QR.to(pathForwarding.path); }, error: (e, s) { @@ -90,7 +93,9 @@ class LeftPanel extends HookConsumerWidget { AppLocalizations.of(context)!.loginLoginFailed, ); }, - loading: () {}, + loading: () { + controller?.forward(); + }, ); }, builder: (child) => Container( diff --git a/lib/main.dart b/lib/main.dart index 521d92ddda..3754e4c5ab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,6 +26,8 @@ import 'package:timeago/timeago.dart' as timeago; import 'package:app_links/app_links.dart'; void main() async { + await dotenv.load(); + await setHyperionHost(); QR.setUrlStrategy(); // We set the default page type to QMaterialPage // See https://pub.dev/packages/qlevar_router#page-transition diff --git a/lib/paiement/class/create_device.dart b/lib/mypayment/class/create_device.dart similarity index 100% rename from lib/paiement/class/create_device.dart rename to lib/mypayment/class/create_device.dart diff --git a/lib/paiement/class/funding_url.dart b/lib/mypayment/class/funding_url.dart similarity index 100% rename from lib/paiement/class/funding_url.dart rename to lib/mypayment/class/funding_url.dart diff --git a/lib/paiement/class/history.dart b/lib/mypayment/class/history.dart similarity index 97% rename from lib/paiement/class/history.dart rename to lib/mypayment/class/history.dart index 5c154ab190..0c98f64e42 100644 --- a/lib/paiement/class/history.dart +++ b/lib/mypayment/class/history.dart @@ -1,4 +1,4 @@ -import 'package:titan/paiement/class/history_refund.dart'; +import 'package:titan/mypayment/class/history_refund.dart'; import 'package:titan/tools/functions.dart'; enum HistoryType { transfer, received, given, refundCredited, refundDebited } diff --git a/lib/paiement/class/history_interval.dart b/lib/mypayment/class/history_interval.dart similarity index 100% rename from lib/paiement/class/history_interval.dart rename to lib/mypayment/class/history_interval.dart diff --git a/lib/paiement/class/history_refund.dart b/lib/mypayment/class/history_refund.dart similarity index 100% rename from lib/paiement/class/history_refund.dart rename to lib/mypayment/class/history_refund.dart diff --git a/lib/paiement/class/init_info.dart b/lib/mypayment/class/init_info.dart similarity index 100% rename from lib/paiement/class/init_info.dart rename to lib/mypayment/class/init_info.dart diff --git a/lib/paiement/class/invoice.dart b/lib/mypayment/class/invoice.dart similarity index 96% rename from lib/paiement/class/invoice.dart rename to lib/mypayment/class/invoice.dart index 14806ff274..cbd70d5fce 100644 --- a/lib/paiement/class/invoice.dart +++ b/lib/mypayment/class/invoice.dart @@ -1,5 +1,5 @@ -import 'package:titan/paiement/class/store.dart'; -import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/class/structure.dart'; import 'package:titan/tools/functions.dart'; class InvoiceDetail { diff --git a/lib/paiement/class/qr_code_data.dart b/lib/mypayment/class/qr_code_data.dart similarity index 100% rename from lib/paiement/class/qr_code_data.dart rename to lib/mypayment/class/qr_code_data.dart diff --git a/lib/paiement/class/qr_code_signature_data.dart b/lib/mypayment/class/qr_code_signature_data.dart similarity index 100% rename from lib/paiement/class/qr_code_signature_data.dart rename to lib/mypayment/class/qr_code_signature_data.dart diff --git a/lib/paiement/class/refund.dart b/lib/mypayment/class/refund.dart similarity index 100% rename from lib/paiement/class/refund.dart rename to lib/mypayment/class/refund.dart diff --git a/lib/paiement/class/seller.dart b/lib/mypayment/class/seller.dart similarity index 100% rename from lib/paiement/class/seller.dart rename to lib/mypayment/class/seller.dart diff --git a/lib/paiement/class/store.dart b/lib/mypayment/class/store.dart similarity index 96% rename from lib/paiement/class/store.dart rename to lib/mypayment/class/store.dart index f67e86768c..b28fe46c9b 100644 --- a/lib/paiement/class/store.dart +++ b/lib/mypayment/class/store.dart @@ -1,4 +1,4 @@ -import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/mypayment/class/structure.dart'; class StoreSimple { final String id; diff --git a/lib/paiement/class/structure.dart b/lib/mypayment/class/structure.dart similarity index 100% rename from lib/paiement/class/structure.dart rename to lib/mypayment/class/structure.dart diff --git a/lib/paiement/class/tos.dart b/lib/mypayment/class/tos.dart similarity index 100% rename from lib/paiement/class/tos.dart rename to lib/mypayment/class/tos.dart diff --git a/lib/paiement/class/transaction.dart b/lib/mypayment/class/transaction.dart similarity index 97% rename from lib/paiement/class/transaction.dart rename to lib/mypayment/class/transaction.dart index fbd2865fe1..2e3775ba30 100644 --- a/lib/paiement/class/transaction.dart +++ b/lib/mypayment/class/transaction.dart @@ -1,4 +1,4 @@ -import 'package:titan/paiement/class/history.dart'; +import 'package:titan/mypayment/class/history.dart'; import 'package:titan/tools/functions.dart'; enum TransactionType { direct, request, refund } diff --git a/lib/paiement/class/transfert.dart b/lib/mypayment/class/transfert.dart similarity index 94% rename from lib/paiement/class/transfert.dart rename to lib/mypayment/class/transfert.dart index 00e642e011..b2ff648603 100644 --- a/lib/paiement/class/transfert.dart +++ b/lib/mypayment/class/transfert.dart @@ -1,4 +1,4 @@ -import 'package:titan/paiement/tools/functions.dart'; +import 'package:titan/mypayment/tools/functions.dart'; class Transfer { final int amount; diff --git a/lib/paiement/class/user_store.dart b/lib/mypayment/class/user_store.dart similarity index 95% rename from lib/paiement/class/user_store.dart rename to lib/mypayment/class/user_store.dart index f0d3db3c3c..d6ff673e6d 100644 --- a/lib/paiement/class/user_store.dart +++ b/lib/mypayment/class/user_store.dart @@ -1,5 +1,5 @@ -import 'package:titan/paiement/class/store.dart'; -import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/class/structure.dart'; class UserStore extends Store { final bool canBank; diff --git a/lib/paiement/class/wallet.dart b/lib/mypayment/class/wallet.dart similarity index 96% rename from lib/paiement/class/wallet.dart rename to lib/mypayment/class/wallet.dart index 2a4e5760fc..55a72e1e89 100644 --- a/lib/paiement/class/wallet.dart +++ b/lib/mypayment/class/wallet.dart @@ -1,4 +1,4 @@ -import 'package:titan/paiement/class/store.dart'; +import 'package:titan/mypayment/class/store.dart'; import 'package:titan/user/class/user.dart'; enum WalletType { user, store } diff --git a/lib/paiement/class/wallet_device.dart b/lib/mypayment/class/wallet_device.dart similarity index 100% rename from lib/paiement/class/wallet_device.dart rename to lib/mypayment/class/wallet_device.dart diff --git a/lib/paiement/providers/bank_account_holder_provider.dart b/lib/mypayment/providers/bank_account_holder_provider.dart similarity index 88% rename from lib/paiement/providers/bank_account_holder_provider.dart rename to lib/mypayment/providers/bank_account_holder_provider.dart index 62240d4735..d620c0e6c6 100644 --- a/lib/paiement/providers/bank_account_holder_provider.dart +++ b/lib/mypayment/providers/bank_account_holder_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/repositories/bank_account_holder_repository.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/repositories/bank_account_holder_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class BankAccountHolderNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/barcode_provider.dart b/lib/mypayment/providers/barcode_provider.dart similarity index 88% rename from lib/paiement/providers/barcode_provider.dart rename to lib/mypayment/providers/barcode_provider.dart index 6b7589119f..30d9329e67 100644 --- a/lib/paiement/providers/barcode_provider.dart +++ b/lib/mypayment/providers/barcode_provider.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/qr_code_data.dart'; +import 'package:titan/mypayment/class/qr_code_data.dart'; class BarcodeNotifier extends StateNotifier { BarcodeNotifier() : super(null); diff --git a/lib/paiement/providers/bypass_provider.dart b/lib/mypayment/providers/bypass_provider.dart similarity index 100% rename from lib/paiement/providers/bypass_provider.dart rename to lib/mypayment/providers/bypass_provider.dart diff --git a/lib/paiement/providers/device_list_provider.dart b/lib/mypayment/providers/device_list_provider.dart similarity index 88% rename from lib/paiement/providers/device_list_provider.dart rename to lib/mypayment/providers/device_list_provider.dart index 7b79980aa4..9afa7e4416 100644 --- a/lib/paiement/providers/device_list_provider.dart +++ b/lib/mypayment/providers/device_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/wallet_device.dart'; -import 'package:titan/paiement/repositories/devices_repository.dart'; +import 'package:titan/mypayment/class/wallet_device.dart'; +import 'package:titan/mypayment/repositories/devices_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class DeviceListNotifier extends ListNotifier { diff --git a/lib/paiement/providers/device_provider.dart b/lib/mypayment/providers/device_provider.dart similarity index 84% rename from lib/paiement/providers/device_provider.dart rename to lib/mypayment/providers/device_provider.dart index c18a8bd02c..99322e1d78 100644 --- a/lib/paiement/providers/device_provider.dart +++ b/lib/mypayment/providers/device_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/create_device.dart'; -import 'package:titan/paiement/class/wallet_device.dart'; -import 'package:titan/paiement/repositories/devices_repository.dart'; +import 'package:titan/mypayment/class/create_device.dart'; +import 'package:titan/mypayment/class/wallet_device.dart'; +import 'package:titan/mypayment/repositories/devices_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class DeviceNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/fund_amount_provider.dart b/lib/mypayment/providers/fund_amount_provider.dart similarity index 100% rename from lib/paiement/providers/fund_amount_provider.dart rename to lib/mypayment/providers/fund_amount_provider.dart diff --git a/lib/paiement/providers/funding_url_provider.dart b/lib/mypayment/providers/funding_url_provider.dart similarity index 80% rename from lib/paiement/providers/funding_url_provider.dart rename to lib/mypayment/providers/funding_url_provider.dart index ec8492130a..3421f42f80 100644 --- a/lib/paiement/providers/funding_url_provider.dart +++ b/lib/mypayment/providers/funding_url_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/funding_url.dart'; -import 'package:titan/paiement/class/init_info.dart'; -import 'package:titan/paiement/repositories/funding_repository.dart'; +import 'package:titan/mypayment/class/funding_url.dart'; +import 'package:titan/mypayment/class/init_info.dart'; +import 'package:titan/mypayment/repositories/funding_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class FundingUrlNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/has_accepted_tos_provider.dart b/lib/mypayment/providers/has_accepted_tos_provider.dart similarity index 91% rename from lib/paiement/providers/has_accepted_tos_provider.dart rename to lib/mypayment/providers/has_accepted_tos_provider.dart index ec6033e4e0..00d5437b60 100644 --- a/lib/paiement/providers/has_accepted_tos_provider.dart +++ b/lib/mypayment/providers/has_accepted_tos_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/providers/tos_provider.dart'; +import 'package:titan/mypayment/providers/tos_provider.dart'; class HasAcceptedTosNotifier extends StateNotifier { final bool defaultValue; diff --git a/lib/mypayment/providers/history_export_csv_provider.dart b/lib/mypayment/providers/history_export_csv_provider.dart new file mode 100644 index 0000000000..3a1ccd3506 --- /dev/null +++ b/lib/mypayment/providers/history_export_csv_provider.dart @@ -0,0 +1,27 @@ +import 'dart:typed_data'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/providers/selected_interval_provider.dart'; +import 'package:titan/mypayment/repositories/csv_stores_repository.dart'; + +class HistoryExportCsvNotifier extends FamilyAsyncNotifier { + @override + Future build(Store store) async { + final interval = ref.watch(selectedIntervalProvider); + final CsvStoresRepository csvStoresRepository = ref.watch( + csvStoresRepositoryProvider, + ); + + return await csvStoresRepository.exportStoreHistory( + store, + interval.start, + interval.end, + ); + } +} + +final historyExportCsvProvider = + AsyncNotifierProvider.family( + HistoryExportCsvNotifier.new, + ); diff --git a/lib/paiement/providers/invoice_list_provider.dart b/lib/mypayment/providers/invoice_list_provider.dart similarity index 93% rename from lib/paiement/providers/invoice_list_provider.dart rename to lib/mypayment/providers/invoice_list_provider.dart index 391b2f1a72..9adbda31cf 100644 --- a/lib/paiement/providers/invoice_list_provider.dart +++ b/lib/mypayment/providers/invoice_list_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/invoice.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/repositories/invoices_repository.dart'; +import 'package:titan/mypayment/class/invoice.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/repositories/invoices_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class InvoiceListNotifier extends ListNotifier { diff --git a/lib/paiement/providers/invoice_pdf_provider.dart b/lib/mypayment/providers/invoice_pdf_provider.dart similarity index 87% rename from lib/paiement/providers/invoice_pdf_provider.dart rename to lib/mypayment/providers/invoice_pdf_provider.dart index 09af8adc58..fcee6c6aa1 100644 --- a/lib/paiement/providers/invoice_pdf_provider.dart +++ b/lib/mypayment/providers/invoice_pdf_provider.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/repositories/invoice_pdf_repository.dart'; +import 'package:titan/mypayment/repositories/invoice_pdf_repository.dart'; class InvoicePdfNotifier extends FamilyAsyncNotifier { @override diff --git a/lib/paiement/providers/invoice_provider.dart b/lib/mypayment/providers/invoice_provider.dart similarity index 84% rename from lib/paiement/providers/invoice_provider.dart rename to lib/mypayment/providers/invoice_provider.dart index 33921ba96e..8a3754a727 100644 --- a/lib/paiement/providers/invoice_provider.dart +++ b/lib/mypayment/providers/invoice_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/invoice.dart'; +import 'package:titan/mypayment/class/invoice.dart'; class InvoiceNotifier extends StateNotifier { InvoiceNotifier() : super(Invoice.empty()); diff --git a/lib/paiement/providers/is_payment_admin.dart b/lib/mypayment/providers/is_payment_admin.dart similarity index 79% rename from lib/paiement/providers/is_payment_admin.dart rename to lib/mypayment/providers/is_payment_admin.dart index 32ef97b276..77130fb8be 100644 --- a/lib/paiement/providers/is_payment_admin.dart +++ b/lib/mypayment/providers/is_payment_admin.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/providers/bank_account_holder_provider.dart'; -import 'package:titan/paiement/providers/my_structures_provider.dart'; +import 'package:titan/mypayment/providers/bank_account_holder_provider.dart'; +import 'package:titan/mypayment/providers/my_structures_provider.dart'; final isStructureAdminProvider = StateProvider((ref) { final myStructures = ref.watch(myStructuresProvider); diff --git a/lib/paiement/providers/key_service_provider.dart b/lib/mypayment/providers/key_service_provider.dart similarity index 67% rename from lib/paiement/providers/key_service_provider.dart rename to lib/mypayment/providers/key_service_provider.dart index 657890dd06..558fbafb01 100644 --- a/lib/paiement/providers/key_service_provider.dart +++ b/lib/mypayment/providers/key_service_provider.dart @@ -1,4 +1,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/tools/key_service.dart'; +import 'package:titan/mypayment/tools/key_service.dart'; final keyServiceProvider = Provider((ref) => KeyService()); diff --git a/lib/paiement/providers/last_time_scanned.dart b/lib/mypayment/providers/last_time_scanned.dart similarity index 100% rename from lib/paiement/providers/last_time_scanned.dart rename to lib/mypayment/providers/last_time_scanned.dart diff --git a/lib/paiement/providers/last_used_store_id_provider.dart b/lib/mypayment/providers/last_used_store_id_provider.dart similarity index 100% rename from lib/paiement/providers/last_used_store_id_provider.dart rename to lib/mypayment/providers/last_used_store_id_provider.dart diff --git a/lib/paiement/providers/my_history_provider.dart b/lib/mypayment/providers/my_history_provider.dart similarity index 85% rename from lib/paiement/providers/my_history_provider.dart rename to lib/mypayment/providers/my_history_provider.dart index 95117f0759..7c5fdd1aec 100644 --- a/lib/paiement/providers/my_history_provider.dart +++ b/lib/mypayment/providers/my_history_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/repositories/users_me_repository.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/repositories/users_me_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class MyHistoryNotifier extends ListNotifier { diff --git a/lib/paiement/providers/my_stores_provider.dart b/lib/mypayment/providers/my_stores_provider.dart similarity index 84% rename from lib/paiement/providers/my_stores_provider.dart rename to lib/mypayment/providers/my_stores_provider.dart index 7f59b7058f..6d6c7e16f3 100644 --- a/lib/paiement/providers/my_stores_provider.dart +++ b/lib/mypayment/providers/my_stores_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/user_store.dart'; -import 'package:titan/paiement/repositories/users_me_repository.dart'; +import 'package:titan/mypayment/class/user_store.dart'; +import 'package:titan/mypayment/repositories/users_me_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class MyStoresNotifier extends ListNotifier { diff --git a/lib/paiement/providers/my_structures_provider.dart b/lib/mypayment/providers/my_structures_provider.dart similarity index 70% rename from lib/paiement/providers/my_structures_provider.dart rename to lib/mypayment/providers/my_structures_provider.dart index 4d6a478061..9d59b287f1 100644 --- a/lib/paiement/providers/my_structures_provider.dart +++ b/lib/mypayment/providers/my_structures_provider.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/providers/structure_list_provider.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/providers/structure_list_provider.dart'; import 'package:titan/user/providers/user_provider.dart'; -final myStructuresProvider = StateProvider((ref) { +final myStructuresProvider = StateProvider>((ref) { final user = ref.watch(userProvider); final structures = ref.watch(structureListProvider); return structures.when( diff --git a/lib/paiement/providers/my_wallet_provider.dart b/lib/mypayment/providers/my_wallet_provider.dart similarity index 84% rename from lib/paiement/providers/my_wallet_provider.dart rename to lib/mypayment/providers/my_wallet_provider.dart index 70d2199995..e92d5bdea2 100644 --- a/lib/paiement/providers/my_wallet_provider.dart +++ b/lib/mypayment/providers/my_wallet_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/wallet.dart'; -import 'package:titan/paiement/repositories/users_me_repository.dart'; +import 'package:titan/mypayment/class/wallet.dart'; +import 'package:titan/mypayment/repositories/users_me_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class MyWalletNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/new_admin_provider.dart b/lib/mypayment/providers/new_admin_provider.dart similarity index 100% rename from lib/paiement/providers/new_admin_provider.dart rename to lib/mypayment/providers/new_admin_provider.dart diff --git a/lib/paiement/providers/ongoing_transaction.dart b/lib/mypayment/providers/ongoing_transaction.dart similarity index 90% rename from lib/paiement/providers/ongoing_transaction.dart rename to lib/mypayment/providers/ongoing_transaction.dart index 38ca4178d3..c626f9fc78 100644 --- a/lib/paiement/providers/ongoing_transaction.dart +++ b/lib/mypayment/providers/ongoing_transaction.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/transaction.dart'; +import 'package:titan/mypayment/class/transaction.dart'; class OngoingTransaction extends StateNotifier> { OngoingTransaction() : super(const AsyncValue.loading()); diff --git a/lib/paiement/providers/pay_amount_provider.dart b/lib/mypayment/providers/pay_amount_provider.dart similarity index 100% rename from lib/paiement/providers/pay_amount_provider.dart rename to lib/mypayment/providers/pay_amount_provider.dart diff --git a/lib/paiement/providers/refund_amount_provider.dart b/lib/mypayment/providers/refund_amount_provider.dart similarity index 100% rename from lib/paiement/providers/refund_amount_provider.dart rename to lib/mypayment/providers/refund_amount_provider.dart diff --git a/lib/paiement/providers/register_provider.dart b/lib/mypayment/providers/register_provider.dart similarity index 90% rename from lib/paiement/providers/register_provider.dart rename to lib/mypayment/providers/register_provider.dart index 32f37bf786..8d1730d2b5 100644 --- a/lib/paiement/providers/register_provider.dart +++ b/lib/mypayment/providers/register_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/repositories/users_me_repository.dart'; +import 'package:titan/mypayment/repositories/users_me_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class RegisterNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/scan_provider.dart b/lib/mypayment/providers/scan_provider.dart similarity index 83% rename from lib/paiement/providers/scan_provider.dart rename to lib/mypayment/providers/scan_provider.dart index da897202f8..1c3a79d5e5 100644 --- a/lib/paiement/providers/scan_provider.dart +++ b/lib/mypayment/providers/scan_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/qr_code_data.dart'; -import 'package:titan/paiement/class/transaction.dart'; -import 'package:titan/paiement/repositories/stores_repository.dart'; +import 'package:titan/mypayment/class/qr_code_data.dart'; +import 'package:titan/mypayment/class/transaction.dart'; +import 'package:titan/mypayment/repositories/stores_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class ScanNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/selected_interval_provider.dart b/lib/mypayment/providers/selected_interval_provider.dart similarity index 90% rename from lib/paiement/providers/selected_interval_provider.dart rename to lib/mypayment/providers/selected_interval_provider.dart index 0bebb48dbd..177307ca7b 100644 --- a/lib/paiement/providers/selected_interval_provider.dart +++ b/lib/mypayment/providers/selected_interval_provider.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/history_interval.dart'; +import 'package:titan/mypayment/class/history_interval.dart'; class SelectedIntervalNotifier extends StateNotifier { SelectedIntervalNotifier() : super(HistoryInterval.currentMonth()); diff --git a/lib/paiement/providers/selected_store_history.dart b/lib/mypayment/providers/selected_store_history.dart similarity index 79% rename from lib/paiement/providers/selected_store_history.dart rename to lib/mypayment/providers/selected_store_history.dart index ec1a479a60..b71c0d106b 100644 --- a/lib/paiement/providers/selected_store_history.dart +++ b/lib/mypayment/providers/selected_store_history.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/class/store.dart'; -import 'package:titan/paiement/providers/selected_interval_provider.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/repositories/stores_repository.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/providers/selected_interval_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/repositories/stores_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class SellerHistoryNotifier extends ListNotifier { diff --git a/lib/mypayment/providers/selected_store_provider.dart b/lib/mypayment/providers/selected_store_provider.dart new file mode 100644 index 0000000000..f361304982 --- /dev/null +++ b/lib/mypayment/providers/selected_store_provider.dart @@ -0,0 +1,51 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/mypayment/class/user_store.dart'; +import 'package:titan/mypayment/providers/my_stores_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SelectedStoreNotifier extends StateNotifier { + SelectedStoreNotifier(super.store); + + void updateStore(UserStore store) { + state = store; + SharedPreferences.getInstance().then((pref) { + pref.setString('selectedStoreId', store.id); + }); + } +} + +class LoadSelectedStoreIdProvider extends StateNotifier { + LoadSelectedStoreIdProvider() : super(null); + + void loadSelectedStoreId() { + SharedPreferences.getInstance().then((pref) { + state = pref.getString('selectedStoreId'); + }); + } +} + +final loadSelectedStoreIdProvider = + StateNotifierProvider((ref) { + LoadSelectedStoreIdProvider loadSelectedStoreIdProvider = + LoadSelectedStoreIdProvider(); + loadSelectedStoreIdProvider.loadSelectedStoreId(); + return loadSelectedStoreIdProvider; + }); + +final selectedStoreProvider = + StateNotifierProvider((ref) { + final myStores = ref.watch(myStoresProvider); + final selectedStoreId = ref.watch(loadSelectedStoreIdProvider); + final store = myStores.maybeWhen( + orElse: () => UserStore.empty(), + data: (value) { + if (value.isEmpty) return UserStore.empty(); + if (selectedStoreId == null) return value.first; + return value.firstWhere( + (store) => store.id == selectedStoreId, + orElse: () => value.first, + ); + }, + ); + return SelectedStoreNotifier(store); + }); diff --git a/lib/paiement/providers/selected_structure_provider.dart b/lib/mypayment/providers/selected_structure_provider.dart similarity index 88% rename from lib/paiement/providers/selected_structure_provider.dart rename to lib/mypayment/providers/selected_structure_provider.dart index 40ab07ec42..5d71340b15 100644 --- a/lib/paiement/providers/selected_structure_provider.dart +++ b/lib/mypayment/providers/selected_structure_provider.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/mypayment/class/structure.dart'; class SelectedStructureNotifier extends StateNotifier { SelectedStructureNotifier() : super(Structure.empty()); diff --git a/lib/paiement/providers/selected_transactions_provider.dart b/lib/mypayment/providers/selected_transactions_provider.dart similarity index 89% rename from lib/paiement/providers/selected_transactions_provider.dart rename to lib/mypayment/providers/selected_transactions_provider.dart index 1d08192ca8..da9b72c395 100644 --- a/lib/paiement/providers/selected_transactions_provider.dart +++ b/lib/mypayment/providers/selected_transactions_provider.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; class SelectedTransactionsNotifier extends StateNotifier> { SelectedTransactionsNotifier(super.history); diff --git a/lib/paiement/providers/seller_rights_list_providder.dart b/lib/mypayment/providers/seller_rights_list_providder.dart similarity index 100% rename from lib/paiement/providers/seller_rights_list_providder.dart rename to lib/mypayment/providers/seller_rights_list_providder.dart diff --git a/lib/paiement/providers/should_display_tos_dialog.dart b/lib/mypayment/providers/should_display_tos_dialog.dart similarity index 91% rename from lib/paiement/providers/should_display_tos_dialog.dart rename to lib/mypayment/providers/should_display_tos_dialog.dart index 705db627a4..608754149e 100644 --- a/lib/paiement/providers/should_display_tos_dialog.dart +++ b/lib/mypayment/providers/should_display_tos_dialog.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/providers/tos_provider.dart'; +import 'package:titan/mypayment/providers/tos_provider.dart'; class ShouldDisplayTosDialog extends StateNotifier { final bool defaultValue; diff --git a/lib/paiement/providers/store_provider.dart b/lib/mypayment/providers/store_provider.dart similarity index 85% rename from lib/paiement/providers/store_provider.dart rename to lib/mypayment/providers/store_provider.dart index 7bdf42f00b..3ed06dd969 100644 --- a/lib/paiement/providers/store_provider.dart +++ b/lib/mypayment/providers/store_provider.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/store.dart'; +import 'package:titan/mypayment/class/store.dart'; class StoreProvider extends StateNotifier { StoreProvider() : super(Store.empty()); diff --git a/lib/paiement/providers/store_sellers_list_provider.dart b/lib/mypayment/providers/store_sellers_list_provider.dart similarity index 92% rename from lib/paiement/providers/store_sellers_list_provider.dart rename to lib/mypayment/providers/store_sellers_list_provider.dart index 87d4f9f741..3c51c73484 100644 --- a/lib/paiement/providers/store_sellers_list_provider.dart +++ b/lib/mypayment/providers/store_sellers_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/seller.dart'; -import 'package:titan/paiement/repositories/store_sellers_repository.dart'; +import 'package:titan/mypayment/class/seller.dart'; +import 'package:titan/mypayment/repositories/store_sellers_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class StoreSellerListNotifier extends ListNotifier { diff --git a/lib/paiement/providers/stores_list_provider.dart b/lib/mypayment/providers/stores_list_provider.dart similarity index 84% rename from lib/paiement/providers/stores_list_provider.dart rename to lib/mypayment/providers/stores_list_provider.dart index 6965832847..dcead7bc49 100644 --- a/lib/paiement/providers/stores_list_provider.dart +++ b/lib/mypayment/providers/stores_list_provider.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/store.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/repositories/stores_repository.dart'; -import 'package:titan/paiement/repositories/structures_repository.dart'; -import 'package:titan/paiement/repositories/users_me_repository.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/repositories/stores_repository.dart'; +import 'package:titan/mypayment/repositories/structures_repository.dart'; +import 'package:titan/mypayment/repositories/users_me_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class StoreListNotifier extends ListNotifier { diff --git a/lib/paiement/providers/structure_list_provider.dart b/lib/mypayment/providers/structure_list_provider.dart similarity index 92% rename from lib/paiement/providers/structure_list_provider.dart rename to lib/mypayment/providers/structure_list_provider.dart index d400787fc5..3aa842e03e 100644 --- a/lib/paiement/providers/structure_list_provider.dart +++ b/lib/mypayment/providers/structure_list_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/repositories/structures_repository.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/repositories/structures_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; class StructureListNotifier extends ListNotifier { diff --git a/lib/paiement/providers/tos_provider.dart b/lib/mypayment/providers/tos_provider.dart similarity index 85% rename from lib/paiement/providers/tos_provider.dart rename to lib/mypayment/providers/tos_provider.dart index e7bf5da34e..dd6e659d29 100644 --- a/lib/paiement/providers/tos_provider.dart +++ b/lib/mypayment/providers/tos_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/tos.dart'; -import 'package:titan/paiement/repositories/tos_repository.dart'; +import 'package:titan/mypayment/class/tos.dart'; +import 'package:titan/mypayment/repositories/tos_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class TOSNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/transaction_provider.dart b/lib/mypayment/providers/transaction_provider.dart similarity index 88% rename from lib/paiement/providers/transaction_provider.dart rename to lib/mypayment/providers/transaction_provider.dart index 92d708283d..bf66118a89 100644 --- a/lib/paiement/providers/transaction_provider.dart +++ b/lib/mypayment/providers/transaction_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/refund.dart'; -import 'package:titan/paiement/repositories/transaction_repository.dart'; +import 'package:titan/mypayment/class/refund.dart'; +import 'package:titan/mypayment/repositories/transaction_repository.dart'; import 'package:titan/tools/providers/single_notifier.dart'; class TransactionNotifier extends SingleNotifier { diff --git a/lib/paiement/providers/transfer_structure_provider.dart b/lib/mypayment/providers/transfer_structure_provider.dart similarity index 84% rename from lib/paiement/providers/transfer_structure_provider.dart rename to lib/mypayment/providers/transfer_structure_provider.dart index 4daee97c89..a6f67da1e6 100644 --- a/lib/paiement/providers/transfer_structure_provider.dart +++ b/lib/mypayment/providers/transfer_structure_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/repositories/structures_repository.dart'; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/repositories/structures_repository.dart'; class TransferStructureNotifier extends StateNotifier { final StructuresRepository structuresRepository; diff --git a/lib/paiement/repositories/bank_account_holder_repository.dart b/lib/mypayment/repositories/bank_account_holder_repository.dart similarity index 94% rename from lib/paiement/repositories/bank_account_holder_repository.dart rename to lib/mypayment/repositories/bank_account_holder_repository.dart index 1c69ed3e52..103a6a5b64 100644 --- a/lib/paiement/repositories/bank_account_holder_repository.dart +++ b/lib/mypayment/repositories/bank_account_holder_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/mypayment/class/structure.dart'; import 'package:titan/tools/exception.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/mypayment/repositories/csv_stores_repository.dart b/lib/mypayment/repositories/csv_stores_repository.dart new file mode 100644 index 0000000000..c578380d56 --- /dev/null +++ b/lib/mypayment/repositories/csv_stores_repository.dart @@ -0,0 +1,32 @@ +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/auth/providers/openid_provider.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/repository/csv_repository.dart'; + +class CsvStoresRepository extends CsvRepository { + @override + //ignore: overridden_fields + final ext = "mypayment/stores/"; + + Future exportStoreHistory( + Store store, + DateTime startDate, + DateTime endDate, + ) async { + final queryParams = { + 'start_date': processDateToAPI(startDate), + 'end_date': processDateToAPI(endDate), + }; + + final queryString = Uri(queryParameters: queryParams).query; + + return await getCsv(store.id, suffix: "/history/data-export?$queryString"); + } +} + +final csvStoresRepositoryProvider = Provider((ref) { + final token = ref.watch(tokenProvider); + return CsvStoresRepository()..setToken(token); +}); diff --git a/lib/paiement/repositories/devices_repository.dart b/lib/mypayment/repositories/devices_repository.dart similarity index 89% rename from lib/paiement/repositories/devices_repository.dart rename to lib/mypayment/repositories/devices_repository.dart index 977879a41f..b437f66881 100644 --- a/lib/paiement/repositories/devices_repository.dart +++ b/lib/mypayment/repositories/devices_repository.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/create_device.dart'; -import 'package:titan/paiement/class/wallet_device.dart'; +import 'package:titan/mypayment/class/create_device.dart'; +import 'package:titan/mypayment/class/wallet_device.dart'; import 'package:titan/tools/repository/repository.dart'; class DevicesRepository extends Repository { diff --git a/lib/paiement/repositories/funding_repository.dart b/lib/mypayment/repositories/funding_repository.dart similarity index 81% rename from lib/paiement/repositories/funding_repository.dart rename to lib/mypayment/repositories/funding_repository.dart index 94011224a5..dd6dd32510 100644 --- a/lib/paiement/repositories/funding_repository.dart +++ b/lib/mypayment/repositories/funding_repository.dart @@ -1,8 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/funding_url.dart'; -import 'package:titan/paiement/class/init_info.dart'; -import 'package:titan/paiement/class/transfert.dart'; +import 'package:titan/mypayment/class/funding_url.dart'; +import 'package:titan/mypayment/class/init_info.dart'; +import 'package:titan/mypayment/class/transfert.dart'; import 'package:titan/tools/repository/repository.dart'; class FundingRepository extends Repository { diff --git a/lib/paiement/repositories/invoice_pdf_repository.dart b/lib/mypayment/repositories/invoice_pdf_repository.dart similarity index 92% rename from lib/paiement/repositories/invoice_pdf_repository.dart rename to lib/mypayment/repositories/invoice_pdf_repository.dart index afb36f7e7a..545c9ab331 100644 --- a/lib/paiement/repositories/invoice_pdf_repository.dart +++ b/lib/mypayment/repositories/invoice_pdf_repository.dart @@ -7,7 +7,7 @@ import 'package:titan/tools/repository/pdf_repository.dart'; class InvoicePdfRepository extends PdfRepository { @override // ignore: overridden_fields - final String ext = "mypayment/invoices/"; + final ext = "mypayment/invoices/"; Future getInvoicePdf(String invoiceId) async { return await getPdf(invoiceId); diff --git a/lib/paiement/repositories/invoices_repository.dart b/lib/mypayment/repositories/invoices_repository.dart similarity index 97% rename from lib/paiement/repositories/invoices_repository.dart rename to lib/mypayment/repositories/invoices_repository.dart index 4197cbed2a..dd0309e089 100644 --- a/lib/paiement/repositories/invoices_repository.dart +++ b/lib/mypayment/repositories/invoices_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/invoice.dart'; +import 'package:titan/mypayment/class/invoice.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/paiement/repositories/store_sellers_repository.dart b/lib/mypayment/repositories/store_sellers_repository.dart similarity index 95% rename from lib/paiement/repositories/store_sellers_repository.dart rename to lib/mypayment/repositories/store_sellers_repository.dart index 810cf139e5..7b3f14dd4d 100644 --- a/lib/paiement/repositories/store_sellers_repository.dart +++ b/lib/mypayment/repositories/store_sellers_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/seller.dart'; +import 'package:titan/mypayment/class/seller.dart'; import 'package:titan/tools/repository/repository.dart'; class SellerStoreRepository extends Repository { diff --git a/lib/paiement/repositories/stores_repository.dart b/lib/mypayment/repositories/stores_repository.dart similarity index 88% rename from lib/paiement/repositories/stores_repository.dart rename to lib/mypayment/repositories/stores_repository.dart index 7fa3f50b58..257e975fcc 100644 --- a/lib/paiement/repositories/stores_repository.dart +++ b/lib/mypayment/repositories/stores_repository.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/class/qr_code_data.dart'; -import 'package:titan/paiement/class/store.dart'; -import 'package:titan/paiement/class/transaction.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/class/qr_code_data.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/class/transaction.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/repository/repository.dart'; diff --git a/lib/paiement/repositories/structures_repository.dart b/lib/mypayment/repositories/structures_repository.dart similarity index 93% rename from lib/paiement/repositories/structures_repository.dart rename to lib/mypayment/repositories/structures_repository.dart index 45ff5fc36c..dd5f6734b6 100644 --- a/lib/paiement/repositories/structures_repository.dart +++ b/lib/mypayment/repositories/structures_repository.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/store.dart'; -import 'package:titan/paiement/class/structure.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/class/structure.dart'; import 'package:titan/tools/repository/repository.dart'; class StructuresRepository extends Repository { diff --git a/lib/paiement/repositories/tos_repository.dart b/lib/mypayment/repositories/tos_repository.dart similarity index 92% rename from lib/paiement/repositories/tos_repository.dart rename to lib/mypayment/repositories/tos_repository.dart index 64f3185d07..26776ad4d5 100644 --- a/lib/paiement/repositories/tos_repository.dart +++ b/lib/mypayment/repositories/tos_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/tos.dart'; +import 'package:titan/mypayment/class/tos.dart'; import 'package:titan/tools/repository/repository.dart'; class TosRepository extends Repository { diff --git a/lib/paiement/repositories/transaction_repository.dart b/lib/mypayment/repositories/transaction_repository.dart similarity index 93% rename from lib/paiement/repositories/transaction_repository.dart rename to lib/mypayment/repositories/transaction_repository.dart index f6d96906fd..3b51a888b9 100644 --- a/lib/paiement/repositories/transaction_repository.dart +++ b/lib/mypayment/repositories/transaction_repository.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/refund.dart'; +import 'package:titan/mypayment/class/refund.dart'; import 'package:titan/tools/repository/repository.dart'; class TransactionsRepository extends Repository { diff --git a/lib/paiement/repositories/users_me_repository.dart b/lib/mypayment/repositories/users_me_repository.dart similarity index 86% rename from lib/paiement/repositories/users_me_repository.dart rename to lib/mypayment/repositories/users_me_repository.dart index e33b64b005..86048b05c4 100644 --- a/lib/paiement/repositories/users_me_repository.dart +++ b/lib/mypayment/repositories/users_me_repository.dart @@ -1,8 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/class/user_store.dart'; -import 'package:titan/paiement/class/wallet.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/class/user_store.dart'; +import 'package:titan/mypayment/class/wallet.dart'; import 'package:titan/tools/repository/repository.dart'; class UsersMeRepository extends Repository { diff --git a/lib/paiement/router.dart b/lib/mypayment/router.dart similarity index 78% rename from lib/paiement/router.dart rename to lib/mypayment/router.dart index 4895966a5a..492a81dd9e 100644 --- a/lib/paiement/router.dart +++ b/lib/mypayment/router.dart @@ -1,30 +1,30 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/navigation/class/module.dart'; -import 'package:titan/paiement/providers/is_payment_admin.dart'; -import 'package:titan/paiement/ui/pages/structure_admin_page/structure_admin_page.dart' - deferred as structure_stores_page; -import 'package:titan/paiement/ui/pages/fund_page/web_view_modal.dart' +import 'package:titan/mypayment/providers/is_payment_admin.dart'; +import 'package:titan/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart' + deferred as structure_admin_page; +import 'package:titan/mypayment/ui/pages/fund_page/web_view_modal.dart' deferred as fund_page; -import 'package:titan/paiement/ui/pages/invoices_admin_page/invoices_admin_page.dart' +import 'package:titan/mypayment/ui/pages/invoices_admin_page/invoices_admin_page.dart' deferred as invoices_admin_page; -import 'package:titan/paiement/ui/pages/invoices_structure_page/invoices_structure_page.dart' +import 'package:titan/mypayment/ui/pages/invoices_structure_page/invoices_structure_page.dart' deferred as structure_invoices_page; -import 'package:titan/paiement/ui/pages/store_pages/add_edit_store.dart' +import 'package:titan/mypayment/ui/pages/store_pages/add_edit_store.dart' deferred as add_edit_page; -import 'package:titan/paiement/ui/pages/store_admin_page/store_admin_page.dart' +import 'package:titan/mypayment/ui/pages/store_admin_page/store_admin_page.dart' deferred as store_admin_page; -import 'package:titan/paiement/ui/pages/devices_page/devices_page.dart' +import 'package:titan/mypayment/ui/pages/devices_page/devices_page.dart' deferred as devices_page; -import 'package:titan/paiement/ui/pages/main_page/main_page.dart' +import 'package:titan/mypayment/ui/pages/main_page/main_page.dart' deferred as main_page; -import 'package:titan/paiement/ui/pages/stats_page/stats_page.dart' +import 'package:titan/mypayment/ui/pages/stats_page/stats_page.dart' deferred as stats_page; -import 'package:titan/paiement/ui/pages/store_stats_page/store_stats_page.dart' +import 'package:titan/mypayment/ui/pages/store_stats_page/store_stats_page.dart' deferred as store_stats_page; -import 'package:titan/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart' +import 'package:titan/mypayment/ui/pages/transfer_structure_page/transfer_structure_page.dart' deferred as transfer_structure_page; +import 'package:titan/navigation/class/module.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/middlewares/admin_middleware.dart'; import 'package:titan/tools/middlewares/authenticated_middleware.dart'; @@ -33,7 +33,7 @@ import 'package:qlevar_router/qlevar_router.dart'; class PaymentRouter { final Ref ref; - static const String root = '/payment'; + static const String root = '/mypayment'; static const String stats = '/stats'; static const String devices = '/devices'; static const String structureStores = '/structureStores'; @@ -53,7 +53,7 @@ class PaymentRouter { PaymentRouter(this.ref); QRoute route() => QRoute( - name: "paiement", + name: "mypayment", path: PaymentRouter.root, builder: () => main_page.PaymentMainPage(), middleware: [ @@ -98,9 +98,9 @@ class PaymentRouter { ), QRoute( path: PaymentRouter.structureStores, - builder: () => structure_stores_page.StructureStoresPage(), + builder: () => structure_admin_page.StructureStoresPage(), middleware: [ - DeferredLoadingMiddleware(structure_stores_page.loadLibrary), + DeferredLoadingMiddleware(structure_admin_page.loadLibrary), AdminMiddleware(ref, isStructureAdminProvider), ], children: [ diff --git a/lib/paiement/tools/functions.dart b/lib/mypayment/tools/functions.dart similarity index 96% rename from lib/paiement/tools/functions.dart rename to lib/mypayment/tools/functions.dart index d5dc5925bf..85affa0bbc 100644 --- a/lib/paiement/tools/functions.dart +++ b/lib/mypayment/tools/functions.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'dart:math' as math; import 'package:flutter/material.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/class/qr_code_data.dart'; -import 'package:titan/paiement/class/qr_code_signature_data.dart'; -import 'package:titan/paiement/class/wallet_device.dart'; -import 'package:titan/paiement/tools/key_service.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/class/qr_code_data.dart'; +import 'package:titan/mypayment/class/qr_code_signature_data.dart'; +import 'package:titan/mypayment/class/wallet_device.dart'; +import 'package:titan/mypayment/tools/key_service.dart'; enum TransferType { helloAsso, check, cash, bankTransfer } diff --git a/lib/paiement/tools/key_service.dart b/lib/mypayment/tools/key_service.dart similarity index 100% rename from lib/paiement/tools/key_service.dart rename to lib/mypayment/tools/key_service.dart diff --git a/lib/paiement/tools/platform_info.dart b/lib/mypayment/tools/platform_info.dart similarity index 100% rename from lib/paiement/tools/platform_info.dart rename to lib/mypayment/tools/platform_info.dart diff --git a/lib/paiement/ui/components/digit_fade_in_animation.dart b/lib/mypayment/ui/components/digit_fade_in_animation.dart similarity index 100% rename from lib/paiement/ui/components/digit_fade_in_animation.dart rename to lib/mypayment/ui/components/digit_fade_in_animation.dart diff --git a/lib/paiement/ui/components/keyboard.dart b/lib/mypayment/ui/components/keyboard.dart similarity index 100% rename from lib/paiement/ui/components/keyboard.dart rename to lib/mypayment/ui/components/keyboard.dart diff --git a/lib/paiement/ui/components/transaction_card.dart b/lib/mypayment/ui/components/transaction_card.dart similarity index 98% rename from lib/paiement/ui/components/transaction_card.dart rename to lib/mypayment/ui/components/transaction_card.dart index 5c033d2cbc..a086e42b20 100644 --- a/lib/paiement/ui/components/transaction_card.dart +++ b/lib/mypayment/ui/components/transaction_card.dart @@ -4,9 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/tools/functions.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/tools/functions.dart'; class TransactionCard extends ConsumerWidget { final History transaction; diff --git a/lib/paiement/ui/paiement.dart b/lib/mypayment/ui/mypayment.dart similarity index 94% rename from lib/paiement/ui/paiement.dart rename to lib/mypayment/ui/mypayment.dart index c1f2773542..3e25e72956 100644 --- a/lib/paiement/ui/paiement.dart +++ b/lib/mypayment/ui/mypayment.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/router.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/mypayment/router.dart'; class PaymentTemplate extends HookConsumerWidget { final Widget child; diff --git a/lib/paiement/ui/pages/devices_page/add_device_button.dart b/lib/mypayment/ui/pages/devices_page/add_device_button.dart similarity index 100% rename from lib/paiement/ui/pages/devices_page/add_device_button.dart rename to lib/mypayment/ui/pages/devices_page/add_device_button.dart diff --git a/lib/mypayment/ui/pages/devices_page/device_item.dart b/lib/mypayment/ui/pages/devices_page/device_item.dart new file mode 100644 index 0000000000..2d659f5f51 --- /dev/null +++ b/lib/mypayment/ui/pages/devices_page/device_item.dart @@ -0,0 +1,93 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:titan/mypayment/class/wallet_device.dart'; +import 'package:titan/mypayment/tools/functions.dart'; + +class DeviceItem extends ConsumerWidget { + final WalletDevice device; + final bool isActual; + final Future Function() onRevoke; + const DeviceItem({ + super.key, + required this.device, + required this.isActual, + required this.onRevoke, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 30), + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(15)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + spreadRadius: 1, + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(15)), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Container( + color: Colors.grey.shade200.withValues(alpha: 0.5), + child: SizedBox( + height: isActual ? 80 : 70, + child: Row( + children: [ + const SizedBox(width: 20), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + device.name, + style: const TextStyle( + color: Color(0xff204550), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + if (isActual) + const Text( + '(cet appareil)', + style: TextStyle( + color: Color(0xff204550), + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(width: 10), + Spacer(), + getStatusTag(device.status), + if (device.status != WalletDeviceStatus.revoked) ...[ + const SizedBox(width: 20), + GestureDetector( + onTap: onRevoke, + child: const HeroIcon( + HeroIcons.trash, + size: 25, + color: Color(0xff204550), + ), + ), + ], + const SizedBox(width: 20), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/mypayment/ui/pages/devices_page/devices_page.dart b/lib/mypayment/ui/pages/devices_page/devices_page.dart new file mode 100644 index 0000000000..664ab74b93 --- /dev/null +++ b/lib/mypayment/ui/pages/devices_page/devices_page.dart @@ -0,0 +1,328 @@ +import 'dart:convert'; + +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/mypayment/class/create_device.dart'; +import 'package:titan/mypayment/class/wallet_device.dart'; +import 'package:titan/mypayment/providers/device_list_provider.dart'; +import 'package:titan/mypayment/providers/device_provider.dart'; +import 'package:titan/mypayment/providers/has_accepted_tos_provider.dart'; +import 'package:titan/mypayment/providers/key_service_provider.dart'; +import 'package:titan/mypayment/tools/functions.dart'; +import 'package:titan/mypayment/ui/pages/devices_page/add_device_button.dart'; +import 'package:titan/mypayment/ui/pages/devices_page/device_item.dart'; +import 'package:titan/mypayment/ui/pages/main_page/account_card/device_dialog_box.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; + +class DevicesPage extends HookConsumerWidget { + const DevicesPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final devices = ref.watch(deviceListProvider); + final devicesNotifier = ref.read(deviceListProvider.notifier); + final deviceNotifier = ref.read(deviceProvider.notifier); + final keyService = ref.watch(keyServiceProvider); + final deviceKey = keyService.getKeyId(); + final displayAddDevice = useState(true); + final hasAcceptedToS = ref.watch(hasAcceptedTosProvider); + final showRovokedDevices = useState(false); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + Future getDeviceName() async { + final deviceInfo = DeviceInfoPlugin(); + if (Theme.of(context).platform == TargetPlatform.android) { + return deviceInfo.androidInfo.then((info) => info.model); + } else if (Theme.of(context).platform == TargetPlatform.iOS) { + return deviceInfo.iosInfo.then((info) => info.utsname.machine); + } else { + return Future.value("Unknown Device"); + } + } + + return PaymentTemplate( + child: Refresher( + onRefresh: () async { + await devicesNotifier.getDeviceList(); + }, + child: FutureBuilder( + future: deviceKey, + builder: (context, snapshot) { + return AsyncChild( + value: devices, + builder: (context, devices) { + final activeDevices = devices + .where( + (device) => device.status != WalletDeviceStatus.revoked, + ) + .toList(); + + final inactiveDevices = devices + .where( + (device) => device.status == WalletDeviceStatus.revoked, + ) + .toList(); + + activeDevices.sort((a, b) { + if (a.id == snapshot.data) return -1; + if (b.id == snapshot.data) return 1; + return statusOrder(a.status).compareTo(statusOrder(b.status)); + }); + + inactiveDevices.sort((a, b) { + if (a.id == snapshot.data) return -1; + if (b.id == snapshot.data) return 1; + return statusOrder(a.status).compareTo(statusOrder(b.status)); + }); + + final firstDevice = devices + .where((element) => element.id == snapshot.data) + .firstOrNull; + + final shouldDisplayAddDevice = + (snapshot.data == null || + firstDevice == null || + firstDevice.status == WalletDeviceStatus.revoked) && + displayAddDevice.value; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (shouldDisplayAddDevice && !kIsWeb) + AddDeviceButton( + onTap: () async { + if (!hasAcceptedToS) { + displayToastWithContext( + TypeMsg.error, + "Veuillez accepter les Conditions Générales d'Utilisation.", + ); + return; + } + final name = await getDeviceName(); + final keyPair = await keyService.generateKeyPair(); + final publicKey = + (await keyPair.extractPublicKey()).bytes; + final base64PublicKey = base64Encode(publicKey); + final body = CreateDevice( + name: name, + ed25519PublicKey: base64PublicKey, + ); + final value = await deviceNotifier.registerDevice( + body, + ); + if (value != null) { + await keyService.saveKeyPair(keyPair); + await keyService.saveKeyId(value); + await devicesNotifier.getDeviceList(); + displayAddDevice.value = false; + if (context.mounted) { + await showDialog( + context: context, + builder: (context) { + return DeviceDialogBox( + title: + 'Demande d\'activation de l\'appareil', + descriptions: + "La demande d'activation est prise en compte, veuillez consulter votre boite mail pour finaliser la démarche", + buttonText: "Ok", + onClick: () { + Navigator.of(context).pop(); + }, + ); + }, + ); + } + } + }, + ), + // Afficher les appareils actifs + ...activeDevices.map((device) { + return DeviceItem( + device: device, + isActual: device.id == snapshot.data, + onRevoke: () async { + if (!hasAcceptedToS) { + displayToastWithContext( + TypeMsg.error, + "Veuillez accepter les Conditions Générales d'Utilisation.", + ); + return; + } + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: "Révoquer l'appareil ?", + descriptions: + "Vous ne pourrez plus utiliser cet appareil pour les paiements", + onYes: () async { + tokenExpireWrapper(ref, () async { + final value = await devicesNotifier + .revokeDevice( + device.copyWith( + status: WalletDeviceStatus.revoked, + ), + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Appareil révoqué", + ); + final savedId = await keyService + .getKeyId(); + if (savedId == device.id) { + await keyService.clear(); + } + } else { + displayToastWithContext( + TypeMsg.error, + "Erreur lors de la révocation de l'appareil", + ); + } + }); + }, + ); + }, + ); + }, + ); + }), + + if (inactiveDevices.isNotEmpty) ...[ + GestureDetector( + onTap: () { + showRovokedDevices.value = !showRovokedDevices.value; + }, + child: Container( + margin: const EdgeInsets.only( + bottom: 10, + top: 20, + left: 30, + right: 30, + ), + width: double.infinity, + height: 70, + decoration: BoxDecoration( + gradient: RadialGradient( + colors: const [ + Color.fromARGB(255, 9, 103, 103), + Color(0xff017f80), + Color.fromARGB(255, 4, 84, 84), + ], + center: Alignment.topLeft, + radius: 1.5, + ), + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Color(0xff017f80).withValues(alpha: 0.2), + spreadRadius: 1, + blurRadius: 3, + offset: const Offset( + 0, + 1, + ), // changes position of shadow + ), + ], + ), + child: Center( + child: Text( + showRovokedDevices.value + ? 'Masquer les appareils revoqués' + : 'Afficher les appareils revoqués', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + if (showRovokedDevices.value) ...[ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Appareils révoqués', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.grey, + ), + ), + ), + ...inactiveDevices.map((device) { + return DeviceItem( + device: device, + isActual: device.id == snapshot.data, + onRevoke: () async { + if (!hasAcceptedToS) { + displayToastWithContext( + TypeMsg.error, + "Veuillez accepter les Conditions Générales d'Utilisation.", + ); + return; + } + await showDialog( + context: context, + builder: (context) { + return CustomDialogBox( + title: "Révoquer l'appareil ?", + descriptions: + "Vous ne pourrez plus utiliser cet appareil pour les paiements", + onYes: () async { + tokenExpireWrapper(ref, () async { + final value = await devicesNotifier + .revokeDevice( + device.copyWith( + status: + WalletDeviceStatus.revoked, + ), + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Appareil révoqué", + ); + final savedId = await keyService + .getKeyId(); + if (savedId == device.id) { + await keyService.clear(); + } + } else { + displayToastWithContext( + TypeMsg.error, + "Erreur lors de la révocation de l'appareil", + ); + } + }); + }, + ); + }, + ); + }, + ); + }), + ], + ], + ], + ); + }, + ); + }, + ), + ), + ); + } +} diff --git a/lib/paiement/ui/pages/fund_page/confirm_button.dart b/lib/mypayment/ui/pages/fund_page/confirm_button.dart similarity index 92% rename from lib/paiement/ui/pages/fund_page/confirm_button.dart rename to lib/mypayment/ui/pages/fund_page/confirm_button.dart index 06e19bcd4b..8f1ea99086 100644 --- a/lib/paiement/ui/pages/fund_page/confirm_button.dart +++ b/lib/mypayment/ui/pages/fund_page/confirm_button.dart @@ -5,12 +5,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/init_info.dart'; -import 'package:titan/paiement/providers/fund_amount_provider.dart'; -import 'package:titan/paiement/providers/funding_url_provider.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; -import 'package:titan/paiement/providers/my_wallet_provider.dart'; -import 'package:titan/paiement/providers/tos_provider.dart'; +import 'package:titan/mypayment/class/init_info.dart'; +import 'package:titan/mypayment/providers/fund_amount_provider.dart'; +import 'package:titan/mypayment/providers/funding_url_provider.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; +import 'package:titan/mypayment/providers/my_wallet_provider.dart'; +import 'package:titan/mypayment/providers/tos_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:universal_html/html.dart' as html; @@ -38,8 +38,8 @@ class ConfirmFundButton extends ConsumerWidget { ); final redirectUrl = kIsWeb - ? "${getTitanURL()}/payment" - : "${getTitanURLScheme()}://payment"; + ? "${getTitanURL()}mypayment" // ? + : "${getTitanURLScheme()}://mypayment"; final amountToAdd = double.tryParse(fundAmount.replaceAll(",", ".")) ?? 0; final minValidFundAmount = diff --git a/lib/paiement/ui/pages/fund_page/fund_page.dart b/lib/mypayment/ui/pages/fund_page/fund_page.dart similarity index 92% rename from lib/paiement/ui/pages/fund_page/fund_page.dart rename to lib/mypayment/ui/pages/fund_page/fund_page.dart index 47c4836662..46797d17da 100644 --- a/lib/paiement/ui/pages/fund_page/fund_page.dart +++ b/lib/mypayment/ui/pages/fund_page/fund_page.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/fund_amount_provider.dart'; -import 'package:titan/paiement/providers/my_wallet_provider.dart'; -import 'package:titan/paiement/providers/tos_provider.dart'; -import 'package:titan/paiement/ui/components/digit_fade_in_animation.dart'; -import 'package:titan/paiement/ui/components/keyboard.dart'; -import 'package:titan/paiement/ui/pages/fund_page/confirm_button.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; +import 'package:titan/mypayment/providers/fund_amount_provider.dart'; +import 'package:titan/mypayment/providers/my_wallet_provider.dart'; +import 'package:titan/mypayment/providers/tos_provider.dart'; +import 'package:titan/mypayment/ui/components/digit_fade_in_animation.dart'; +import 'package:titan/mypayment/ui/components/keyboard.dart'; +import 'package:titan/mypayment/ui/pages/fund_page/confirm_button.dart'; class FundPage extends ConsumerWidget { const FundPage({super.key}); diff --git a/lib/paiement/ui/pages/fund_page/web_view_modal.dart b/lib/mypayment/ui/pages/fund_page/web_view_modal.dart similarity index 100% rename from lib/paiement/ui/pages/fund_page/web_view_modal.dart rename to lib/mypayment/ui/pages/fund_page/web_view_modal.dart diff --git a/lib/mypayment/ui/pages/invoices_admin_page/invoice_card.dart b/lib/mypayment/ui/pages/invoices_admin_page/invoice_card.dart new file mode 100644 index 0000000000..b08b647a91 --- /dev/null +++ b/lib/mypayment/ui/pages/invoices_admin_page/invoice_card.dart @@ -0,0 +1,240 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/mypayment/class/invoice.dart'; +import 'package:titan/mypayment/providers/invoice_list_provider.dart'; +import 'package:titan/mypayment/providers/invoice_pdf_provider.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/ui/layouts/bottom_modal_template.dart'; +import 'package:titan/tools/ui/layouts/button.dart'; +import 'package:titan/tools/ui/layouts/card_layout.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; + +class InvoiceCard extends HookConsumerWidget { + final Invoice invoice; + final bool isAdmin; + + const InvoiceCard({super.key, required this.invoice, required this.isAdmin}) + : super(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final invoicesNotifier = ref.read(invoiceListProvider.notifier); + final invoicePdf = ref.watch(invoicePdfProvider(invoice.id).future); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: GestureDetector( + onTap: () => showCustomBottomModal( + context: context, + modal: BottomModalTemplate( + title: invoice.reference, + child: Column( + children: [ + Button( + onPressed: () async { + late final Uint8List pdfBytes; + try { + pdfBytes = await invoicePdf; + } catch (e) { + displayToastWithContext(TypeMsg.error, e.toString()); + return; + } + final path = kIsWeb + ? await FileSaver.instance.saveFile( + name: invoice.reference, + bytes: pdfBytes, + ext: "pdf", + mimeType: MimeType.pdf, + ) + : await FileSaver.instance.saveAs( + name: invoice.reference, + bytes: pdfBytes, + ext: "pdf", + mimeType: MimeType.pdf, + ); + if (path != null) { + displayToastWithContext( + TypeMsg.msg, + "Téléchargement réussi", + ); + } + }, + text: "Télécharger la facture (PDF)", + ), + if (!invoice.received && isAdmin) ...[ + const SizedBox(height: 10), + Button( + onPressed: () async { + final value = await invoicesNotifier + .updateInvoicePaidStatus(invoice, !invoice.paid); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Statut mis à jour avec succès", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Erreur lors de la mise à jour du statut", + ); + } + }, + text: invoice.paid + ? "Marquer comme impayée" + : "Marquer comme payée", + ), + ], + if (!isAdmin && invoice.paid && !invoice.received) ...[ + const SizedBox(height: 10), + Button( + onPressed: () async { + Navigator.of(context).pop(); + showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: "Marquer comme reçue", + descriptions: + "Confirmez-vous avoir bien reçu le paiement ?", + onYes: () async { + final value = await invoicesNotifier + .updateInvoiceReceivedStatus(invoice, true); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Statut mis à jour avec succès", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Erreur lors de la mise à jour du statut", + ); + } + }, + ), + ); + }, + text: "Marquer comme reçue", + ), + ], + if (!invoice.paid && isAdmin) ...[ + const SizedBox(height: 10), + Button( + onPressed: () async { + Navigator.of(context).pop(); + showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: "Supprimer la facture", + descriptions: "Cette action est irréversible", + onYes: () async { + final value = await invoicesNotifier.deleteInvoice( + invoice, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Facture supprimée avec succès", + ); + } else { + displayToastWithContext( + TypeMsg.error, + "Erreur lors de la suppression de la facture", + ); + } + }, + ), + ); + }, + text: "Supprimer la facture", + ), + ], + ], + ), + ), + ), + child: CardLayout( + margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 20), + shadowColor: Colors.grey.withValues(alpha: 0.2), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + invoice.reference, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 5), + Text( + invoice.structure.name, + style: TextStyle(fontSize: 14), + ), + ], + ), + ), + Expanded( + flex: kIsWeb ? 2 : 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (kIsWeb) + Column( + children: [ + Text( + '${(invoice.total / 100).toStringAsFixed(2)} €', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: ColorConstants.background2, + ), + ), + AutoSizeText( + "Du ${processDate(invoice.startDate)} au ${processDate(invoice.endDate)}", + maxLines: 2, + style: TextStyle(fontSize: 12), + ), + ], + ), + Text( + invoice.received + ? "Reçue" + : invoice.paid + ? "Payée" + : "En attente", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: invoice.received + ? Colors.green + : invoice.paid + ? Colors.blue + : Colors.orange, + ), + ), + const HeroIcon( + HeroIcons.chevronRight, + color: ColorConstants.background2, + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/mypayment/ui/pages/invoices_admin_page/invoices_admin_page.dart b/lib/mypayment/ui/pages/invoices_admin_page/invoices_admin_page.dart new file mode 100644 index 0000000000..327fe45668 --- /dev/null +++ b/lib/mypayment/ui/pages/invoices_admin_page/invoices_admin_page.dart @@ -0,0 +1,193 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/structure_provider.dart'; +import 'package:titan/mypayment/providers/invoice_list_provider.dart'; +import 'package:titan/mypayment/providers/structure_list_provider.dart'; +import 'package:titan/mypayment/ui/pages/invoices_admin_page/invoice_card.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/bottom_modal_template.dart'; +import 'package:titan/tools/ui/layouts/button.dart'; +import 'package:titan/tools/ui/layouts/item_chip.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:tuple/tuple.dart'; + +class InvoicesAdminPage extends HookConsumerWidget { + const InvoicesAdminPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final page = useState(1); + final pageSize = useState(20); + final invoices = ref.watch(invoiceListProvider); + final structures = ref.watch(structureListProvider); + final structureNotifier = ref.watch(structureProvider.notifier); + final invoicesNotifier = ref.read(invoiceListProvider.notifier); + + void refreshInvoices() { + tokenExpireWrapper( + ref, + () => invoicesNotifier.getInvoices( + page: page.value, + pageLimit: pageSize.value, + ), + ); + } + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return PaymentTemplate( + child: Refresher( + onRefresh: () async { + refreshInvoices(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Async2Children( + values: Tuple2(invoices, structures), + builder: (context, invoices, structures) { + return Column( + children: [ + Row( + children: [ + IconButton( + icon: HeroIcon(HeroIcons.arrowLeft), + onPressed: page.value <= 1 + ? null + : () { + page.value--; + refreshInvoices(); + }, + color: Colors.white, + disabledColor: ColorConstants.deactivated1, + ), + DropdownButton( + items: [10, 20, 50, 100] + .map( + (size) => DropdownMenuItem( + value: size, + child: Text("$size par page"), + ), + ) + .toList(), + onChanged: (value) { + if (value != null) { + pageSize.value = value; + refreshInvoices(); + } + }, + value: pageSize.value, + ), + IconButton( + icon: HeroIcon(HeroIcons.arrowRight), + onPressed: invoices.length < pageSize.value + ? null + : () { + page.value++; + refreshInvoices(); + }, + color: Colors.white, + disabledColor: ColorConstants.deactivated1, + ), + ], + ), + const SizedBox(height: 10), + Button.secondary( + onPressed: () => showCustomBottomModal( + context: context, + modal: Consumer( + builder: (context, ref, _) { + final structure = ref.watch(structureProvider); + return BottomModalTemplate( + title: "Créer une nouvelle facture", + child: Column( + children: [ + Text( + "Sélectionnez une structure", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox( + height: min( + structures.length * 50, + MediaQuery.of(context).size.height * 0.8, + ), + child: SingleChildScrollView( + child: Column( + children: structures + .map( + (e) => ItemChip( + vertical: true, + onTap: () => structureNotifier + .setStructure(e), + selected: structure.id == e.id, + child: Text( + e.name, + style: TextStyle( + fontSize: 16, + color: structure.id == e.id + ? Colors.white + : Colors.black, + ), + ), + ), + ) + .toList(), + ), + ), + ), + Button( + onPressed: () async { + if (structure.id == "") return; + Navigator.pop(context); + await tokenExpireWrapper(ref, () async { + final value = await invoicesNotifier + .createInvoice(structure); + if (value) { + displayToastWithContext( + TypeMsg.msg, + "Facture créée avec succès", + ); + refreshInvoices(); + } else { + displayToastWithContext( + TypeMsg.error, + "Aucune facture à générer pour cette structure", + ); + } + }); + }, + text: "Créer la facture", + ), + ], + ), + ); + }, + ), + ), + text: "Créer une nouvelle facture", + ), + const SizedBox(height: 10), + ...invoices.map( + (invoice) => InvoiceCard(invoice: invoice, isAdmin: true), + ), + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/mypayment/ui/pages/invoices_structure_page/invoices_structure_page.dart b/lib/mypayment/ui/pages/invoices_structure_page/invoices_structure_page.dart new file mode 100644 index 0000000000..ab6b11a7fe --- /dev/null +++ b/lib/mypayment/ui/pages/invoices_structure_page/invoices_structure_page.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/mypayment/providers/invoice_list_provider.dart'; +import 'package:titan/mypayment/providers/selected_structure_provider.dart'; +import 'package:titan/mypayment/ui/pages/invoices_admin_page/invoice_card.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; + +class StructureInvoicesPage extends HookConsumerWidget { + const StructureInvoicesPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final page = useState(1); + final pageSize = useState(20); + + final selectedStructure = ref.watch(selectedStructureProvider); + final invoices = ref.watch(invoiceListProvider); + final invoicesNotifier = ref.watch(invoiceListProvider.notifier); + + void refreshInvoices() { + tokenExpireWrapper( + ref, + () => invoicesNotifier.getStructureInvoices( + selectedStructure.id, + page: page.value, + pageLimit: pageSize.value, + ), + ); + } + + return PaymentTemplate( + child: Refresher( + onRefresh: () async { + refreshInvoices(); + }, + child: AsyncChild( + value: invoices, + builder: (context, invoices) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + Row( + children: [ + IconButton( + icon: HeroIcon(HeroIcons.arrowLeft), + onPressed: page.value <= 1 + ? null + : () { + page.value--; + refreshInvoices(); + }, + color: Colors.white, + disabledColor: ColorConstants.deactivated1, + ), + DropdownButton( + items: [10, 20, 50, 100] + .map( + (size) => DropdownMenuItem( + value: size, + child: Text(size.toString()), + ), + ) + .toList(), + onChanged: (value) { + if (value != null) { + pageSize.value = value; + refreshInvoices(); + } + }, + value: pageSize.value, + ), + IconButton( + icon: HeroIcon(HeroIcons.arrowRight), + onPressed: invoices.length < pageSize.value + ? null + : () { + page.value++; + refreshInvoices(); + }, + color: Colors.white, + disabledColor: ColorConstants.deactivated1, + ), + ], + ), + const SizedBox(height: 10), + ...invoices.map( + (invoice) => InvoiceCard(invoice: invoice, isAdmin: false), + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/paiement/ui/pages/main_page/account_card/account_card.dart b/lib/mypayment/ui/pages/main_page/account_card/account_card.dart similarity index 89% rename from lib/paiement/ui/pages/main_page/account_card/account_card.dart rename to lib/mypayment/ui/pages/main_page/account_card/account_card.dart index 318baaf465..a085f385cf 100644 --- a/lib/paiement/ui/pages/main_page/account_card/account_card.dart +++ b/lib/mypayment/ui/pages/main_page/account_card/account_card.dart @@ -4,20 +4,20 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/wallet_device.dart'; -import 'package:titan/paiement/providers/device_list_provider.dart'; -import 'package:titan/paiement/providers/device_provider.dart'; -import 'package:titan/paiement/providers/fund_amount_provider.dart'; -import 'package:titan/paiement/providers/has_accepted_tos_provider.dart'; -import 'package:titan/paiement/providers/key_service_provider.dart'; -import 'package:titan/paiement/providers/my_wallet_provider.dart'; -import 'package:titan/paiement/providers/pay_amount_provider.dart'; -import 'package:titan/paiement/router.dart'; -import 'package:titan/paiement/ui/pages/fund_page/fund_page.dart'; -import 'package:titan/paiement/ui/pages/main_page/account_card/device_dialog_box.dart'; -import 'package:titan/paiement/ui/pages/main_page/main_card_button.dart'; -import 'package:titan/paiement/ui/pages/main_page/main_card_template.dart'; -import 'package:titan/paiement/ui/pages/pay_page/pay_page.dart'; +import 'package:titan/mypayment/class/wallet_device.dart'; +import 'package:titan/mypayment/providers/device_list_provider.dart'; +import 'package:titan/mypayment/providers/device_provider.dart'; +import 'package:titan/mypayment/providers/fund_amount_provider.dart'; +import 'package:titan/mypayment/providers/has_accepted_tos_provider.dart'; +import 'package:titan/mypayment/providers/key_service_provider.dart'; +import 'package:titan/mypayment/providers/my_wallet_provider.dart'; +import 'package:titan/mypayment/providers/pay_amount_provider.dart'; +import 'package:titan/mypayment/router.dart'; +import 'package:titan/mypayment/ui/pages/fund_page/fund_page.dart'; +import 'package:titan/mypayment/ui/pages/main_page/account_card/device_dialog_box.dart'; +import 'package:titan/mypayment/ui/pages/main_page/main_card_button.dart'; +import 'package:titan/mypayment/ui/pages/main_page/main_card_template.dart'; +import 'package:titan/mypayment/ui/pages/pay_page/pay_page.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; diff --git a/lib/paiement/ui/pages/main_page/account_card/day_divider.dart b/lib/mypayment/ui/pages/main_page/account_card/day_divider.dart similarity index 100% rename from lib/paiement/ui/pages/main_page/account_card/day_divider.dart rename to lib/mypayment/ui/pages/main_page/account_card/day_divider.dart diff --git a/lib/paiement/ui/pages/main_page/account_card/device_dialog_box.dart b/lib/mypayment/ui/pages/main_page/account_card/device_dialog_box.dart similarity index 100% rename from lib/paiement/ui/pages/main_page/account_card/device_dialog_box.dart rename to lib/mypayment/ui/pages/main_page/account_card/device_dialog_box.dart diff --git a/lib/paiement/ui/pages/main_page/account_card/last_transactions.dart b/lib/mypayment/ui/pages/main_page/account_card/last_transactions.dart similarity index 92% rename from lib/paiement/ui/pages/main_page/account_card/last_transactions.dart rename to lib/mypayment/ui/pages/main_page/account_card/last_transactions.dart index c361dd963b..f0ffda9776 100644 --- a/lib/paiement/ui/pages/main_page/account_card/last_transactions.dart +++ b/lib/mypayment/ui/pages/main_page/account_card/last_transactions.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; -import 'package:titan/paiement/ui/pages/main_page/account_card/day_divider.dart'; -import 'package:titan/paiement/ui/components/transaction_card.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; +import 'package:titan/mypayment/ui/pages/main_page/account_card/day_divider.dart'; +import 'package:titan/mypayment/ui/components/transaction_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:timeago/timeago.dart' as timeago; diff --git a/lib/paiement/ui/pages/main_page/flip_card.dart b/lib/mypayment/ui/pages/main_page/flip_card.dart similarity index 93% rename from lib/paiement/ui/pages/main_page/flip_card.dart rename to lib/mypayment/ui/pages/main_page/flip_card.dart index 451c29f7a8..5d8c405ee7 100644 --- a/lib/paiement/ui/pages/main_page/flip_card.dart +++ b/lib/mypayment/ui/pages/main_page/flip_card.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; -import 'package:titan/paiement/providers/my_wallet_provider.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; +import 'package:titan/mypayment/providers/my_wallet_provider.dart'; class FlipCard extends HookConsumerWidget { final Widget front; diff --git a/lib/paiement/ui/pages/main_page/main_card_button.dart b/lib/mypayment/ui/pages/main_page/main_card_button.dart similarity index 100% rename from lib/paiement/ui/pages/main_page/main_card_button.dart rename to lib/mypayment/ui/pages/main_page/main_card_button.dart diff --git a/lib/paiement/ui/pages/main_page/main_card_template.dart b/lib/mypayment/ui/pages/main_page/main_card_template.dart similarity index 97% rename from lib/paiement/ui/pages/main_page/main_card_template.dart rename to lib/mypayment/ui/pages/main_page/main_card_template.dart index 21f435083f..d93af67602 100644 --- a/lib/paiement/ui/pages/main_page/main_card_template.dart +++ b/lib/mypayment/ui/pages/main_page/main_card_template.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/paiement/ui/pages/main_page/main_card_button.dart'; +import 'package:titan/mypayment/ui/pages/main_page/main_card_button.dart'; class MainCardTemplate extends StatelessWidget { final List actionButtons; diff --git a/lib/paiement/ui/pages/main_page/main_page.dart b/lib/mypayment/ui/pages/main_page/main_page.dart similarity index 86% rename from lib/paiement/ui/pages/main_page/main_page.dart rename to lib/mypayment/ui/pages/main_page/main_page.dart index 2738ca2131..753c86652d 100644 --- a/lib/paiement/ui/pages/main_page/main_page.dart +++ b/lib/mypayment/ui/pages/main_page/main_page.dart @@ -3,21 +3,21 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/navigation/ui/scroll_to_hide_navbar.dart'; -import 'package:titan/paiement/providers/has_accepted_tos_provider.dart'; -import 'package:titan/paiement/providers/my_wallet_provider.dart'; -import 'package:titan/paiement/providers/tos_provider.dart'; -import 'package:titan/paiement/providers/is_payment_admin.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; -import 'package:titan/paiement/providers/my_stores_provider.dart'; -import 'package:titan/paiement/providers/register_provider.dart'; -import 'package:titan/paiement/providers/should_display_tos_dialog.dart'; -import 'package:titan/paiement/ui/pages/main_page/account_card/account_card.dart'; -import 'package:titan/paiement/ui/pages/main_page/tos_dialog.dart'; -import 'package:titan/paiement/ui/pages/main_page/account_card/last_transactions.dart'; -import 'package:titan/paiement/ui/pages/main_page/flip_card.dart'; -import 'package:titan/paiement/ui/pages/main_page/seller_card/store_card.dart'; -import 'package:titan/paiement/ui/pages/main_page/seller_card/store_list.dart'; -import 'package:titan/paiement/ui/paiement.dart'; +import 'package:titan/mypayment/providers/is_payment_admin.dart'; +import 'package:titan/mypayment/providers/has_accepted_tos_provider.dart'; +import 'package:titan/mypayment/providers/my_wallet_provider.dart'; +import 'package:titan/mypayment/providers/tos_provider.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; +import 'package:titan/mypayment/providers/my_stores_provider.dart'; +import 'package:titan/mypayment/providers/register_provider.dart'; +import 'package:titan/mypayment/providers/should_display_tos_dialog.dart'; +import 'package:titan/mypayment/ui/pages/main_page/account_card/account_card.dart'; +import 'package:titan/mypayment/ui/pages/main_page/tos_dialog.dart'; +import 'package:titan/mypayment/ui/pages/main_page/account_card/last_transactions.dart'; +import 'package:titan/mypayment/ui/pages/main_page/flip_card.dart'; +import 'package:titan/mypayment/ui/pages/main_page/seller_card/store_card.dart'; +import 'package:titan/mypayment/ui/pages/main_page/seller_card/store_list.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; @@ -46,7 +46,7 @@ class PaymentMainPage extends HookConsumerWidget { final mySellersNotifier = ref.read(myStoresProvider.notifier); final myHistoryNotifier = ref.read(myHistoryProvider.notifier); final myWalletNotifier = ref.read(myWalletProvider.notifier); - final isAdmin = ref.watch(isStructureAdminProvider); + final isStructureAdmin = ref.watch(isStructureAdminProvider); final flipped = useState(true); ref.listen(pathForwardingProvider, (previous, next) async { @@ -152,7 +152,7 @@ class PaymentMainPage extends HookConsumerWidget { AsyncChild( value: mySellers, builder: (context, mySellers) { - if (mySellers.isEmpty && !isAdmin) { + if (mySellers.isEmpty && !isStructureAdmin) { return SizedBox( height: 250, width: MediaQuery.of(context).size.width, diff --git a/lib/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart b/lib/mypayment/ui/pages/main_page/seller_card/admin_invoice_card.dart similarity index 94% rename from lib/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart rename to lib/mypayment/ui/pages/main_page/seller_card/admin_invoice_card.dart index a6bde2e160..a892ef4cdf 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart +++ b/lib/mypayment/ui/pages/main_page/seller_card/admin_invoice_card.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/invoice_list_provider.dart'; -import 'package:titan/paiement/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/mypayment/providers/invoice_list_provider.dart'; +import 'package:titan/mypayment/router.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; class InvoiceAdminCard extends ConsumerWidget { diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart b/lib/mypayment/ui/pages/main_page/seller_card/store_card.dart similarity index 88% rename from lib/paiement/ui/pages/main_page/seller_card/store_card.dart rename to lib/mypayment/ui/pages/main_page/seller_card/store_card.dart index ee7c0faf09..2daec3db34 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_card.dart +++ b/lib/mypayment/ui/pages/main_page/seller_card/store_card.dart @@ -3,14 +3,14 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/barcode_provider.dart'; -import 'package:titan/paiement/providers/ongoing_transaction.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/router.dart'; -import 'package:titan/paiement/ui/pages/main_page/main_card_button.dart'; -import 'package:titan/paiement/ui/pages/main_page/main_card_template.dart'; -import 'package:titan/paiement/ui/pages/scan_page/scan_page.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; +import 'package:titan/mypayment/providers/barcode_provider.dart'; +import 'package:titan/mypayment/providers/ongoing_transaction.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/router.dart'; +import 'package:titan/mypayment/ui/pages/main_page/main_card_button.dart'; +import 'package:titan/mypayment/ui/pages/main_page/main_card_template.dart'; +import 'package:titan/mypayment/ui/pages/scan_page/scan_page.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_divider.dart b/lib/mypayment/ui/pages/main_page/seller_card/store_divider.dart similarity index 100% rename from lib/paiement/ui/pages/main_page/seller_card/store_divider.dart rename to lib/mypayment/ui/pages/main_page/seller_card/store_divider.dart diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart b/lib/mypayment/ui/pages/main_page/seller_card/store_list.dart similarity index 84% rename from lib/paiement/ui/pages/main_page/seller_card/store_list.dart rename to lib/mypayment/ui/pages/main_page/seller_card/store_list.dart index 7208083b63..442366e68f 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_list.dart +++ b/lib/mypayment/ui/pages/main_page/seller_card/store_list.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/user_store.dart'; -import 'package:titan/paiement/providers/is_payment_admin.dart'; -import 'package:titan/paiement/providers/my_stores_provider.dart'; -import 'package:titan/paiement/ui/pages/main_page/seller_card/admin_invoice_card.dart'; -import 'package:titan/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart'; -import 'package:titan/paiement/ui/pages/main_page/seller_card/store_divider.dart'; -import 'package:titan/paiement/ui/pages/main_page/seller_card/store_seller_card.dart'; +import 'package:titan/mypayment/class/user_store.dart'; +import 'package:titan/mypayment/providers/is_payment_admin.dart'; +import 'package:titan/mypayment/providers/my_stores_provider.dart'; +import 'package:titan/mypayment/ui/pages/main_page/seller_card/admin_invoice_card.dart'; +import 'package:titan/mypayment/ui/pages/main_page/seller_card/store_divider.dart'; +import 'package:titan/mypayment/ui/pages/main_page/seller_card/store_seller_card.dart'; +import 'package:titan/mypayment/ui/pages/main_page/seller_card/structure_admin_card.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; class StoreList extends ConsumerWidget { diff --git a/lib/paiement/ui/pages/main_page/seller_card/store_seller_card.dart b/lib/mypayment/ui/pages/main_page/seller_card/store_seller_card.dart similarity index 93% rename from lib/paiement/ui/pages/main_page/seller_card/store_seller_card.dart rename to lib/mypayment/ui/pages/main_page/seller_card/store_seller_card.dart index 93d35d6768..177c0145b7 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/store_seller_card.dart +++ b/lib/mypayment/ui/pages/main_page/seller_card/store_seller_card.dart @@ -2,8 +2,8 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; -import 'package:titan/paiement/class/user_store.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/class/user_store.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; class StoreSellerCard extends ConsumerWidget { final UserStore store; diff --git a/lib/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart b/lib/mypayment/ui/pages/main_page/seller_card/structure_admin_card.dart similarity index 88% rename from lib/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart rename to lib/mypayment/ui/pages/main_page/seller_card/structure_admin_card.dart index c89fcb8964..ad58f1d250 100644 --- a/lib/paiement/ui/pages/main_page/seller_card/structure_admin_card.dart +++ b/lib/mypayment/ui/pages/main_page/seller_card/structure_admin_card.dart @@ -3,14 +3,18 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/invoice_list_provider.dart'; -import 'package:titan/paiement/providers/my_structures_provider.dart'; -import 'package:titan/paiement/providers/selected_structure_provider.dart'; -import 'package:titan/paiement/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/styleguide/bottom_modal_template.dart'; import 'package:titan/tools/ui/styleguide/button.dart'; +import 'package:titan/mypayment/providers/invoice_list_provider.dart'; +import 'package:titan/mypayment/providers/my_structures_provider.dart'; +import 'package:titan/mypayment/providers/selected_structure_provider.dart'; +import 'package:titan/mypayment/router.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/layouts/bottom_modal_template.dart'; +import 'package:titan/tools/ui/layouts/button.dart'; class StructureAdminCard extends ConsumerWidget { const StructureAdminCard({super.key}); diff --git a/lib/paiement/ui/pages/main_page/tos_dialog.dart b/lib/mypayment/ui/pages/main_page/tos_dialog.dart similarity index 100% rename from lib/paiement/ui/pages/main_page/tos_dialog.dart rename to lib/mypayment/ui/pages/main_page/tos_dialog.dart diff --git a/lib/paiement/ui/pages/pay_page/confirm_button.dart b/lib/mypayment/ui/pages/pay_page/confirm_button.dart similarity index 93% rename from lib/paiement/ui/pages/pay_page/confirm_button.dart rename to lib/mypayment/ui/pages/pay_page/confirm_button.dart index b2c36140cb..63b096991e 100644 --- a/lib/paiement/ui/pages/pay_page/confirm_button.dart +++ b/lib/mypayment/ui/pages/pay_page/confirm_button.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -7,12 +8,12 @@ import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_darwin/local_auth_darwin.dart'; import 'package:titan/event/tools/functions.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/key_service_provider.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; -import 'package:titan/paiement/providers/my_wallet_provider.dart'; -import 'package:titan/paiement/providers/pay_amount_provider.dart'; -import 'package:titan/paiement/ui/pages/pay_page/info_card.dart'; -import 'package:titan/paiement/ui/pages/pay_page/qr_code.dart'; +import 'package:titan/mypayment/providers/key_service_provider.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; +import 'package:titan/mypayment/providers/my_wallet_provider.dart'; +import 'package:titan/mypayment/providers/pay_amount_provider.dart'; +import 'package:titan/mypayment/ui/pages/pay_page/info_card.dart'; +import 'package:titan/mypayment/ui/pages/pay_page/qr_code.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; diff --git a/lib/paiement/ui/pages/pay_page/info_card.dart b/lib/mypayment/ui/pages/pay_page/info_card.dart similarity index 100% rename from lib/paiement/ui/pages/pay_page/info_card.dart rename to lib/mypayment/ui/pages/pay_page/info_card.dart diff --git a/lib/paiement/ui/pages/pay_page/pay_page.dart b/lib/mypayment/ui/pages/pay_page/pay_page.dart similarity index 92% rename from lib/paiement/ui/pages/pay_page/pay_page.dart rename to lib/mypayment/ui/pages/pay_page/pay_page.dart index 208e802698..29ef5e135d 100644 --- a/lib/paiement/ui/pages/pay_page/pay_page.dart +++ b/lib/mypayment/ui/pages/pay_page/pay_page.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/my_wallet_provider.dart'; -import 'package:titan/paiement/providers/pay_amount_provider.dart'; -import 'package:titan/paiement/ui/pages/pay_page/confirm_button.dart'; -import 'package:titan/paiement/ui/components/digit_fade_in_animation.dart'; -import 'package:titan/paiement/ui/components/keyboard.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; +import 'package:titan/mypayment/providers/my_wallet_provider.dart'; +import 'package:titan/mypayment/providers/pay_amount_provider.dart'; +import 'package:titan/mypayment/ui/pages/pay_page/confirm_button.dart'; +import 'package:titan/mypayment/ui/components/digit_fade_in_animation.dart'; +import 'package:titan/mypayment/ui/components/keyboard.dart'; class PayPage extends ConsumerWidget { const PayPage({super.key}); diff --git a/lib/mypayment/ui/pages/pay_page/qr_code.dart b/lib/mypayment/ui/pages/pay_page/qr_code.dart new file mode 100644 index 0000000000..dee8cdeb55 --- /dev/null +++ b/lib/mypayment/ui/pages/pay_page/qr_code.dart @@ -0,0 +1,59 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/mypayment/providers/key_service_provider.dart'; +import 'package:titan/mypayment/providers/pay_amount_provider.dart'; +import 'package:titan/mypayment/tools/functions.dart'; +import 'package:titan/tools/ui/widgets/loader.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:uuid/uuid.dart'; + +class QrCode extends ConsumerWidget { + const QrCode({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final payAmount = ref.watch(payAmountProvider); + final id = const Uuid().v4(); + final keyService = ref.watch(keyServiceProvider); + return FutureBuilder( + future: getQRCodeContent(id, payAmount, keyService, true), + builder: (context, snapshot) { + switch (snapshot) { + case AsyncSnapshot(:final error?): + if (kDebugMode) { + debugPrint('Could not load QR code: $error'); + } + return Center(child: Text('Erreur lors du chargement du QR code')); + case AsyncSnapshot(:final data?): + return Center( + child: QrImageView( + data: data, + version: QrVersions.auto, + size: min( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.8, + ), + eyeStyle: const QrEyeStyle( + color: Colors.black, + eyeShape: QrEyeShape.square, + ), + dataModuleStyle: const QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.square, + color: Colors.black, + ), + ), + ); + case AsyncSnapshot(): + return SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.width * 0.8, + child: const Loader(), + ); + } + }, + ); + } +} diff --git a/lib/paiement/ui/pages/scan_page/cancel_button.dart b/lib/mypayment/ui/pages/scan_page/cancel_button.dart similarity index 100% rename from lib/paiement/ui/pages/scan_page/cancel_button.dart rename to lib/mypayment/ui/pages/scan_page/cancel_button.dart diff --git a/lib/paiement/ui/pages/scan_page/scan_overlay_shape.dart b/lib/mypayment/ui/pages/scan_page/scan_overlay_shape.dart similarity index 100% rename from lib/paiement/ui/pages/scan_page/scan_overlay_shape.dart rename to lib/mypayment/ui/pages/scan_page/scan_overlay_shape.dart diff --git a/lib/paiement/ui/pages/scan_page/scan_page.dart b/lib/mypayment/ui/pages/scan_page/scan_page.dart similarity index 97% rename from lib/paiement/ui/pages/scan_page/scan_page.dart rename to lib/mypayment/ui/pages/scan_page/scan_page.dart index 39a38ac68f..6ae87ffb16 100644 --- a/lib/paiement/ui/pages/scan_page/scan_page.dart +++ b/lib/mypayment/ui/pages/scan_page/scan_page.dart @@ -4,13 +4,13 @@ import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/barcode_provider.dart'; -import 'package:titan/paiement/providers/bypass_provider.dart'; -import 'package:titan/paiement/providers/ongoing_transaction.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/providers/transaction_provider.dart'; -import 'package:titan/paiement/ui/pages/scan_page/cancel_button.dart'; -import 'package:titan/paiement/ui/pages/scan_page/scanner.dart'; +import 'package:titan/mypayment/providers/barcode_provider.dart'; +import 'package:titan/mypayment/providers/bypass_provider.dart'; +import 'package:titan/mypayment/providers/ongoing_transaction.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/providers/transaction_provider.dart'; +import 'package:titan/mypayment/ui/pages/scan_page/cancel_button.dart'; +import 'package:titan/mypayment/ui/pages/scan_page/scanner.dart'; import 'package:titan/tools/exception.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; diff --git a/lib/paiement/ui/pages/scan_page/scanner.dart b/lib/mypayment/ui/pages/scan_page/scanner.dart similarity index 94% rename from lib/paiement/ui/pages/scan_page/scanner.dart rename to lib/mypayment/ui/pages/scan_page/scanner.dart index 69223197e7..f82dbd38d0 100644 --- a/lib/paiement/ui/pages/scan_page/scanner.dart +++ b/lib/mypayment/ui/pages/scan_page/scanner.dart @@ -6,13 +6,13 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/barcode_provider.dart'; -import 'package:titan/paiement/providers/bypass_provider.dart'; -import 'package:titan/paiement/providers/last_time_scanned.dart'; -import 'package:titan/paiement/providers/ongoing_transaction.dart'; -import 'package:titan/paiement/providers/scan_provider.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/ui/pages/scan_page/scan_overlay_shape.dart'; +import 'package:titan/mypayment/providers/barcode_provider.dart'; +import 'package:titan/mypayment/providers/bypass_provider.dart'; +import 'package:titan/mypayment/providers/last_time_scanned.dart'; +import 'package:titan/mypayment/providers/ongoing_transaction.dart'; +import 'package:titan/mypayment/providers/scan_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/ui/pages/scan_page/scan_overlay_shape.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; diff --git a/lib/paiement/ui/pages/stats_page/description_shape.dart b/lib/mypayment/ui/pages/stats_page/description_shape.dart similarity index 100% rename from lib/paiement/ui/pages/stats_page/description_shape.dart rename to lib/mypayment/ui/pages/stats_page/description_shape.dart diff --git a/lib/paiement/ui/pages/stats_page/month_bar.dart b/lib/mypayment/ui/pages/stats_page/month_bar.dart similarity index 93% rename from lib/paiement/ui/pages/stats_page/month_bar.dart rename to lib/mypayment/ui/pages/stats_page/month_bar.dart index 6c1d0e40c5..77c84347a3 100644 --- a/lib/paiement/ui/pages/stats_page/month_bar.dart +++ b/lib/mypayment/ui/pages/stats_page/month_bar.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; class MonthBar extends HookConsumerWidget { final DateTime currentMonth; diff --git a/lib/paiement/ui/pages/stats_page/month_section_summary.dart b/lib/mypayment/ui/pages/stats_page/month_section_summary.dart similarity index 100% rename from lib/paiement/ui/pages/stats_page/month_section_summary.dart rename to lib/mypayment/ui/pages/stats_page/month_section_summary.dart diff --git a/lib/paiement/ui/pages/stats_page/stats_page.dart b/lib/mypayment/ui/pages/stats_page/stats_page.dart similarity index 85% rename from lib/paiement/ui/pages/stats_page/stats_page.dart rename to lib/mypayment/ui/pages/stats_page/stats_page.dart index 6bf7cb9db7..fa1cbdb27f 100644 --- a/lib/paiement/ui/pages/stats_page/stats_page.dart +++ b/lib/mypayment/ui/pages/stats_page/stats_page.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; -import 'package:titan/paiement/ui/pages/stats_page/month_bar.dart'; -import 'package:titan/paiement/ui/pages/stats_page/sum_up_chart.dart'; -import 'package:titan/paiement/ui/pages/stats_page/transactions_detail.dart'; -import 'package:titan/paiement/ui/paiement.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; +import 'package:titan/mypayment/ui/pages/stats_page/month_bar.dart'; +import 'package:titan/mypayment/ui/pages/stats_page/sum_up_chart.dart'; +import 'package:titan/mypayment/ui/pages/stats_page/transactions_detail.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; class StatsPage extends HookConsumerWidget { diff --git a/lib/paiement/ui/pages/stats_page/sum_up_card.dart b/lib/mypayment/ui/pages/stats_page/sum_up_card.dart similarity index 95% rename from lib/paiement/ui/pages/stats_page/sum_up_card.dart rename to lib/mypayment/ui/pages/stats_page/sum_up_card.dart index 2ce69c285d..d8833725ab 100644 --- a/lib/paiement/ui/pages/stats_page/sum_up_card.dart +++ b/lib/mypayment/ui/pages/stats_page/sum_up_card.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:titan/paiement/ui/pages/stats_page/description_shape.dart'; +import 'package:titan/mypayment/ui/pages/stats_page/description_shape.dart'; class SumUpCard extends StatelessWidget { final String title; diff --git a/lib/paiement/ui/pages/stats_page/sum_up_chart.dart b/lib/mypayment/ui/pages/stats_page/sum_up_chart.dart similarity index 95% rename from lib/paiement/ui/pages/stats_page/sum_up_chart.dart rename to lib/mypayment/ui/pages/stats_page/sum_up_chart.dart index 83f83f80e5..5d65b1cbc5 100644 --- a/lib/paiement/ui/pages/stats_page/sum_up_chart.dart +++ b/lib/mypayment/ui/pages/stats_page/sum_up_chart.dart @@ -3,12 +3,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/providers/my_history_provider.dart'; -import 'package:titan/paiement/providers/selected_transactions_provider.dart'; -import 'package:titan/paiement/ui/pages/stats_page/month_section_summary.dart'; -import 'package:titan/paiement/ui/pages/stats_page/transaction_chart.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/providers/my_history_provider.dart'; +import 'package:titan/mypayment/providers/selected_transactions_provider.dart'; +import 'package:titan/mypayment/ui/pages/stats_page/month_section_summary.dart'; +import 'package:titan/mypayment/ui/pages/stats_page/transaction_chart.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; class SumUpChart extends HookConsumerWidget { diff --git a/lib/paiement/ui/pages/stats_page/transaction_chart.dart b/lib/mypayment/ui/pages/stats_page/transaction_chart.dart similarity index 93% rename from lib/paiement/ui/pages/stats_page/transaction_chart.dart rename to lib/mypayment/ui/pages/stats_page/transaction_chart.dart index 1dcd4ee712..9c8d0b0104 100644 --- a/lib/paiement/ui/pages/stats_page/transaction_chart.dart +++ b/lib/mypayment/ui/pages/stats_page/transaction_chart.dart @@ -4,11 +4,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/providers/selected_transactions_provider.dart'; -import 'package:titan/paiement/tools/functions.dart'; -import 'package:titan/paiement/ui/pages/stats_page/sum_up_card.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/providers/selected_transactions_provider.dart'; +import 'package:titan/mypayment/tools/functions.dart'; +import 'package:titan/mypayment/ui/pages/stats_page/sum_up_card.dart'; class TransactionChart extends HookConsumerWidget { final Map> transactionPerStore; diff --git a/lib/paiement/ui/pages/stats_page/transactions_detail.dart b/lib/mypayment/ui/pages/stats_page/transactions_detail.dart similarity index 81% rename from lib/paiement/ui/pages/stats_page/transactions_detail.dart rename to lib/mypayment/ui/pages/stats_page/transactions_detail.dart index e7ec2d0dbf..a6fcab6422 100644 --- a/lib/paiement/ui/pages/stats_page/transactions_detail.dart +++ b/lib/mypayment/ui/pages/stats_page/transactions_detail.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/providers/selected_transactions_provider.dart'; -import 'package:titan/paiement/ui/components/transaction_card.dart'; +import 'package:titan/mypayment/providers/selected_transactions_provider.dart'; +import 'package:titan/mypayment/ui/components/transaction_card.dart'; class TransactionsDetail extends ConsumerWidget { final DateTime currentMonth; diff --git a/lib/paiement/ui/pages/store_admin_page/right_check_box.dart b/lib/mypayment/ui/pages/store_admin_page/right_check_box.dart similarity index 90% rename from lib/paiement/ui/pages/store_admin_page/right_check_box.dart rename to lib/mypayment/ui/pages/store_admin_page/right_check_box.dart index 16ca35a351..f7aff4bf8a 100644 --- a/lib/paiement/ui/pages/store_admin_page/right_check_box.dart +++ b/lib/mypayment/ui/pages/store_admin_page/right_check_box.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/providers/seller_rights_list_providder.dart'; +import 'package:titan/mypayment/providers/seller_rights_list_providder.dart'; class RightCheckBox extends ConsumerWidget { final int index; diff --git a/lib/paiement/ui/pages/store_admin_page/search_result.dart b/lib/mypayment/ui/pages/store_admin_page/search_result.dart similarity index 92% rename from lib/paiement/ui/pages/store_admin_page/search_result.dart rename to lib/mypayment/ui/pages/store_admin_page/search_result.dart index 8ffc540fb2..23ec496418 100644 --- a/lib/paiement/ui/pages/store_admin_page/search_result.dart +++ b/lib/mypayment/ui/pages/store_admin_page/search_result.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/seller.dart'; -import 'package:titan/paiement/providers/new_admin_provider.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/providers/seller_rights_list_providder.dart'; -import 'package:titan/paiement/providers/store_sellers_list_provider.dart'; -import 'package:titan/paiement/ui/pages/store_admin_page/right_check_box.dart'; -import 'package:titan/paiement/ui/pages/store_admin_page/seller_right_dialog.dart'; +import 'package:titan/mypayment/class/seller.dart'; +import 'package:titan/mypayment/providers/new_admin_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/providers/seller_rights_list_providder.dart'; +import 'package:titan/mypayment/providers/store_sellers_list_provider.dart'; +import 'package:titan/mypayment/ui/pages/store_admin_page/right_check_box.dart'; +import 'package:titan/mypayment/ui/pages/store_admin_page/seller_right_dialog.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; diff --git a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart b/lib/mypayment/ui/pages/store_admin_page/seller_right_card.dart similarity index 98% rename from lib/paiement/ui/pages/store_admin_page/seller_right_card.dart rename to lib/mypayment/ui/pages/store_admin_page/seller_right_card.dart index 65272ec9dc..8db850ebf6 100644 --- a/lib/paiement/ui/pages/store_admin_page/seller_right_card.dart +++ b/lib/mypayment/ui/pages/store_admin_page/seller_right_card.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/seller.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/providers/store_sellers_list_provider.dart'; +import 'package:titan/mypayment/class/seller.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/providers/store_sellers_list_provider.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; diff --git a/lib/paiement/ui/pages/store_admin_page/seller_right_dialog.dart b/lib/mypayment/ui/pages/store_admin_page/seller_right_dialog.dart similarity index 100% rename from lib/paiement/ui/pages/store_admin_page/seller_right_dialog.dart rename to lib/mypayment/ui/pages/store_admin_page/seller_right_dialog.dart diff --git a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart b/lib/mypayment/ui/pages/store_admin_page/store_admin_page.dart similarity index 94% rename from lib/paiement/ui/pages/store_admin_page/store_admin_page.dart rename to lib/mypayment/ui/pages/store_admin_page/store_admin_page.dart index 67e3ea4b97..edc3c8e15d 100644 --- a/lib/paiement/ui/pages/store_admin_page/store_admin_page.dart +++ b/lib/mypayment/ui/pages/store_admin_page/store_admin_page.dart @@ -3,11 +3,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/providers/store_sellers_list_provider.dart'; -import 'package:titan/paiement/ui/pages/store_admin_page/search_result.dart'; -import 'package:titan/paiement/ui/pages/store_admin_page/seller_right_card.dart'; -import 'package:titan/paiement/ui/paiement.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/providers/store_sellers_list_provider.dart'; +import 'package:titan/mypayment/ui/pages/store_admin_page/search_result.dart'; +import 'package:titan/mypayment/ui/pages/store_admin_page/seller_right_card.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/card_button.dart'; diff --git a/lib/paiement/ui/pages/store_pages/add_edit_store.dart b/lib/mypayment/ui/pages/store_pages/add_edit_store.dart similarity index 92% rename from lib/paiement/ui/pages/store_pages/add_edit_store.dart rename to lib/mypayment/ui/pages/store_pages/add_edit_store.dart index 5efe7a9783..ae4f485560 100644 --- a/lib/paiement/ui/pages/store_pages/add_edit_store.dart +++ b/lib/mypayment/ui/pages/store_pages/add_edit_store.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/store.dart' as store_class; -import 'package:titan/paiement/class/structure.dart'; -import 'package:titan/paiement/providers/my_stores_provider.dart'; -import 'package:titan/paiement/providers/selected_structure_provider.dart'; -import 'package:titan/paiement/providers/store_provider.dart'; -import 'package:titan/paiement/providers/stores_list_provider.dart'; -import 'package:titan/paiement/ui/paiement.dart'; +import 'package:titan/mypayment/class/store.dart' as store_class; +import 'package:titan/mypayment/class/structure.dart'; +import 'package:titan/mypayment/providers/my_stores_provider.dart'; +import 'package:titan/mypayment/providers/selected_structure_provider.dart'; +import 'package:titan/mypayment/providers/store_provider.dart'; +import 'package:titan/mypayment/providers/stores_list_provider.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; import 'package:titan/tools/ui/layouts/add_edit_button_layout.dart'; diff --git a/lib/paiement/ui/pages/store_stats_page/refund_page.dart b/lib/mypayment/ui/pages/store_stats_page/refund_page.dart similarity index 93% rename from lib/paiement/ui/pages/store_stats_page/refund_page.dart rename to lib/mypayment/ui/pages/store_stats_page/refund_page.dart index 4a9c5346a0..5a0212770f 100644 --- a/lib/paiement/ui/pages/store_stats_page/refund_page.dart +++ b/lib/mypayment/ui/pages/store_stats_page/refund_page.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/class/refund.dart'; -import 'package:titan/paiement/providers/refund_amount_provider.dart'; -import 'package:titan/paiement/providers/selected_store_history.dart'; -import 'package:titan/paiement/providers/transaction_provider.dart'; -import 'package:titan/paiement/ui/components/digit_fade_in_animation.dart'; -import 'package:titan/paiement/ui/components/keyboard.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/class/refund.dart'; +import 'package:titan/mypayment/providers/refund_amount_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_history.dart'; +import 'package:titan/mypayment/providers/transaction_provider.dart'; +import 'package:titan/mypayment/ui/components/digit_fade_in_animation.dart'; +import 'package:titan/mypayment/ui/components/keyboard.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/paiement/ui/pages/store_stats_page/store_stats_page.dart b/lib/mypayment/ui/pages/store_stats_page/store_stats_page.dart similarity index 73% rename from lib/paiement/ui/pages/store_stats_page/store_stats_page.dart rename to lib/mypayment/ui/pages/store_stats_page/store_stats_page.dart index c3323ed507..2d665caa48 100644 --- a/lib/paiement/ui/pages/store_stats_page/store_stats_page.dart +++ b/lib/mypayment/ui/pages/store_stats_page/store_stats_page.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/providers/selected_interval_provider.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/ui/pages/store_stats_page/interval_selector.dart'; -import 'package:titan/paiement/ui/pages/store_stats_page/store_transactions_detail.dart'; -import 'package:titan/paiement/ui/pages/store_stats_page/summary_card.dart'; -import 'package:titan/paiement/ui/paiement.dart'; -import 'package:titan/paiement/providers/selected_store_history.dart'; +import 'package:titan/mypayment/providers/selected_interval_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/ui/pages/store_stats_page/tool_bar.dart'; +import 'package:titan/mypayment/ui/pages/store_stats_page/store_transactions_detail.dart'; +import 'package:titan/mypayment/ui/pages/store_stats_page/summary_card.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; +import 'package:titan/mypayment/providers/selected_store_history.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; @@ -19,6 +19,7 @@ class StoreStatsPage extends ConsumerWidget { final selectedHistory = ref.watch(sellerHistoryProvider); final selectedHistoryNotifier = ref.read(sellerHistoryProvider.notifier); final selectedInterval = ref.watch(selectedIntervalProvider); + return PaymentTemplate( child: Refresher( controller: ScrollController(), @@ -37,7 +38,7 @@ class StoreStatsPage extends ConsumerWidget { return Column( children: [ const SizedBox(height: 20), - const IntervalSelector(), + ToolBar(), const SizedBox(height: 20), SummaryCard(history: sortedByDate), const SizedBox(height: 20), diff --git a/lib/paiement/ui/pages/store_stats_page/store_transactions_detail.dart b/lib/mypayment/ui/pages/store_stats_page/store_transactions_detail.dart similarity index 81% rename from lib/paiement/ui/pages/store_stats_page/store_transactions_detail.dart rename to lib/mypayment/ui/pages/store_stats_page/store_transactions_detail.dart index d7be584b45..b1ec561902 100644 --- a/lib/paiement/ui/pages/store_stats_page/store_transactions_detail.dart +++ b/lib/mypayment/ui/pages/store_stats_page/store_transactions_detail.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/class/history.dart'; -import 'package:titan/paiement/providers/refund_amount_provider.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/ui/components/transaction_card.dart'; -import 'package:titan/paiement/ui/pages/store_stats_page/refund_page.dart'; +import 'package:titan/mypayment/class/history.dart'; +import 'package:titan/mypayment/providers/refund_amount_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/ui/components/transaction_card.dart'; +import 'package:titan/mypayment/ui/pages/store_stats_page/refund_page.dart'; class StoreTransactionsDetail extends ConsumerWidget { final List history; diff --git a/lib/paiement/ui/pages/store_stats_page/summary_card.dart b/lib/mypayment/ui/pages/store_stats_page/summary_card.dart similarity index 98% rename from lib/paiement/ui/pages/store_stats_page/summary_card.dart rename to lib/mypayment/ui/pages/store_stats_page/summary_card.dart index 507cb5b92f..96fa2aafa5 100644 --- a/lib/paiement/ui/pages/store_stats_page/summary_card.dart +++ b/lib/mypayment/ui/pages/store_stats_page/summary_card.dart @@ -4,8 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/history.dart'; import 'package:titan/tools/providers/locale_notifier.dart'; +import 'package:titan/mypayment/class/history.dart'; class SummaryCard extends ConsumerWidget { final List history; diff --git a/lib/mypayment/ui/pages/store_stats_page/tool_bar.dart b/lib/mypayment/ui/pages/store_stats_page/tool_bar.dart new file mode 100644 index 0000000000..44641527ce --- /dev/null +++ b/lib/mypayment/ui/pages/store_stats_page/tool_bar.dart @@ -0,0 +1,303 @@ +import 'package:flutter/material.dart'; +import 'dart:typed_data'; + +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:titan/mypayment/providers/history_export_csv_provider.dart'; +import 'package:titan/mypayment/providers/selected_interval_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_history.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/tools/functions.dart'; + +class ToolBar extends ConsumerWidget { + const ToolBar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final now = DateTime.now(); + final selectedStore = ref.watch(selectedStoreProvider); + final selectedHistoryNotifier = ref.read(sellerHistoryProvider.notifier); + final selectedInterval = ref.watch(selectedIntervalProvider); + final selectedIntervalNotifier = ref.read( + selectedIntervalProvider.notifier, + ); + + void displayMyPaymentToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + Future getDate(DateTime initialDate) async { + return await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: now.subtract(const Duration(days: 365 * 5)), + lastDate: now, + builder: (BuildContext context, Widget? child) { + return Theme( + data: ThemeData.light().copyWith( + colorScheme: const ColorScheme.light( + primary: Color(0xff017f80), + onPrimary: Colors.white, + surface: Colors.white, + onSurface: Colors.black, + ), + dialogTheme: DialogThemeData(backgroundColor: Colors.white), + ), + child: child!, + ); + }, + ); + } + + Future getTime(DateTime initialDate) async { + return await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(initialDate), + builder: (BuildContext context, Widget? child) { + return Theme( + data: ThemeData.light().copyWith( + colorScheme: const ColorScheme.light( + primary: Color(0xff017f80), + onPrimary: Colors.white, + surface: Colors.white, + onSurface: Colors.black, + ), + dialogTheme: DialogThemeData(backgroundColor: Colors.white), + ), + child: child!, + ); + }, + ); + } + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + children: [ + GestureDetector( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + decoration: BoxDecoration( + color: Color(0xff017f80).withAlpha(50), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + DateFormat( + "dd MMM yyyy", + "fr_FR", + ).format(selectedInterval.start), + style: TextStyle( + color: const Color(0xff204550), + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + onTap: () async { + final date = await getDate(selectedInterval.start); + if (date != null) { + selectedIntervalNotifier.updateStart(date); + await selectedHistoryNotifier.getHistory( + selectedStore.id, + date, + selectedInterval.end, + ); + } + }, + ), + SizedBox(width: 5), + GestureDetector( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + decoration: BoxDecoration( + color: Color(0xff017f80).withAlpha(50), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + DateFormat( + "HH:mm", + "fr_FR", + ).format(selectedInterval.start), + style: TextStyle( + color: const Color(0xff204550), + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + onTap: () async { + final time = await getTime(selectedInterval.start); + if (time != null) { + final date = DateTime( + selectedInterval.start.year, + selectedInterval.start.month, + selectedInterval.start.day, + time.hour, + time.minute, + ); + selectedIntervalNotifier.updateStart(date); + await selectedHistoryNotifier.getHistory( + selectedStore.id, + date, + selectedInterval.end, + ); + } + }, + ), + ], + ), + SizedBox(width: 5), + HeroIcon( + HeroIcons.arrowRight, + color: const Color(0xff204550), + size: 20, + ), + SizedBox(width: 5), + Row( + children: [ + GestureDetector( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + decoration: BoxDecoration( + color: Color(0xff017f80).withAlpha(50), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + DateFormat( + "dd MMM yyyy", + "fr_FR", + ).format(selectedInterval.end), + style: TextStyle( + color: const Color(0xff204550), + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + onTap: () async { + final date = await getDate(selectedInterval.end); + if (date != null) { + selectedIntervalNotifier.updateEnd(date); + await selectedHistoryNotifier.getHistory( + selectedStore.id, + selectedInterval.start, + date, + ); + } + }, + ), + SizedBox(width: 5), + GestureDetector( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + decoration: BoxDecoration( + color: Color(0xff017f80).withAlpha(50), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + DateFormat( + "HH:mm", + "fr_FR", + ).format(selectedInterval.end), + style: TextStyle( + color: const Color(0xff204550), + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + onTap: () async { + final time = await getTime(selectedInterval.end); + if (time != null) { + final date = DateTime( + selectedInterval.end.year, + selectedInterval.end.month, + selectedInterval.end.day, + time.hour, + time.minute, + ); + selectedIntervalNotifier.updateEnd(date); + await selectedHistoryNotifier.getHistory( + selectedStore.id, + selectedInterval.start, + date, + ); + } + }, + ), + ], + ), + ], + ), + ), + const SizedBox(height: 10), + ElevatedButton.icon( + onPressed: () async { + late final Uint8List csvBytes; + + try { + csvBytes = await ref.read( + historyExportCsvProvider(selectedStore).future, + ); + } catch (e) { + displayMyPaymentToastWithContext(TypeMsg.error, e.toString()); + return; + } + + final fileName = + "Store_history_${selectedStore.name.replaceAll(" ", "_")}"; + + final path = kIsWeb + ? await FileSaver.instance.saveFile( + name: fileName, + bytes: csvBytes, + ext: "csv", + mimeType: MimeType.csv, + ) + : await FileSaver.instance.saveAs( + name: fileName, + bytes: csvBytes, + ext: "csv", + mimeType: MimeType.csv, + ); + + if (!context.mounted) return; + + if (path != null) { + displayMyPaymentToastWithContext( + TypeMsg.msg, + "Exportation CSV réussie !", + ); + } + }, + icon: const Icon(Icons.download), + label: const Text('Exporter CSV'), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xff017f80), + foregroundColor: Colors.white, + ), + ), + ], + ); + } +} diff --git a/lib/mypayment/ui/pages/structure_admin_page/add_store_card.dart b/lib/mypayment/ui/pages/structure_admin_page/add_store_card.dart new file mode 100644 index 0000000000..e52b3d925c --- /dev/null +++ b/lib/mypayment/ui/pages/structure_admin_page/add_store_card.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/providers/store_provider.dart'; +import 'package:titan/mypayment/router.dart'; +import 'package:qlevar_router/qlevar_router.dart'; + +class AddStoreCard extends ConsumerWidget { + const AddStoreCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final storeNotifier = ref.watch(storeProvider.notifier); + return GestureDetector( + onTap: () { + storeNotifier.updateStore(Store.empty()); + QR.to( + PaymentRouter.root + + PaymentRouter.structureStores + + PaymentRouter.addEditStore, + ); + }, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: const Color.fromARGB( + 255, + 0, + 29, + 29, + ).withValues(alpha: 0.2), + spreadRadius: 1, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + ), + child: const Center( + child: HeroIcon( + HeroIcons.plus, + size: 40, + color: Color.fromARGB(255, 0, 29, 29), + ), + ), + ), + ); + } +} diff --git a/lib/mypayment/ui/pages/structure_admin_page/admin_store_card.dart b/lib/mypayment/ui/pages/structure_admin_page/admin_store_card.dart new file mode 100644 index 0000000000..5723d8bfd1 --- /dev/null +++ b/lib/mypayment/ui/pages/structure_admin_page/admin_store_card.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +<<<<<<<< HEAD:lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/paiement/class/store.dart'; +import 'package:titan/paiement/providers/store_provider.dart'; +import 'package:titan/paiement/providers/stores_list_provider.dart'; +import 'package:titan/paiement/router.dart'; +======== +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/providers/store_provider.dart'; +import 'package:titan/mypayment/providers/stores_list_provider.dart'; +import 'package:titan/mypayment/router.dart'; +>>>>>>>> main:lib/mypayment/ui/pages/structure_admin_page/admin_store_card.dart +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/tools/ui/builders/waiting_button.dart'; +import 'package:titan/tools/ui/layouts/card_button.dart'; +import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; +import 'package:qlevar_router/qlevar_router.dart'; + +class AdminStoreCard extends ConsumerWidget { + final Store store; + const AdminStoreCard({super.key, required this.store}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final storeNotifier = ref.watch(storeProvider.notifier); + final storeListNotifier = ref.watch(storeListProvider.notifier); + + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + return Container( + margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: const Color.fromARGB(255, 0, 29, 29).withValues(alpha: 0.2), + spreadRadius: 1, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + store.name, + style: const TextStyle( + fontSize: 20, + color: Color.fromARGB(255, 0, 29, 29), + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + GestureDetector( + onTap: () { + storeNotifier.updateStore(store); + QR.to( + PaymentRouter.root + + PaymentRouter.structureStores + + PaymentRouter.addEditStore, + ); + }, + child: const CardButton( + colors: [ + Color.fromARGB(255, 6, 75, 75), + Color.fromARGB(255, 0, 29, 29), + ], + child: HeroIcon(HeroIcons.pencilSquare, color: Colors.white), + ), + ), + const SizedBox(width: 10), + WaitingButton( + onTap: () async { + await showDialog( + context: context, + builder: (context) => CustomDialogBox( + title: AppLocalizations.of(context)!.paiementDeleteStore, + descriptions: AppLocalizations.of( + context, + )!.paiementDeleteStoreDescription, + onYes: () { + tokenExpireWrapper(ref, () async { + final storeDeletedMsg = AppLocalizations.of( + context, + )!.paiementStoreDeleted; + final storeDeleteErrorMsg = AppLocalizations.of( + context, + )!.paiementDeleteStoreError; + final value = await storeListNotifier.deleteStore( + store, + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + storeDeletedMsg, + ); + } else { + displayToastWithContext( + TypeMsg.error, + storeDeleteErrorMsg, + ); + } + }); + }, + ), + ); + }, + builder: (child) => CardButton( + colors: const [Color(0xFF9E131F), Color(0xFF590512)], + child: child, + ), + child: const HeroIcon(HeroIcons.trash, color: Colors.white), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart b/lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart new file mode 100644 index 0000000000..98f28d8f97 --- /dev/null +++ b/lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +<<<<<<<< HEAD:lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart +import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/paiement/providers/selected_structure_provider.dart'; +import 'package:titan/paiement/providers/stores_list_provider.dart'; +import 'package:titan/paiement/ui/pages/structure_admin_page/add_store_card.dart'; +import 'package:titan/paiement/ui/pages/structure_admin_page/admin_store_card.dart'; +import 'package:titan/paiement/ui/paiement.dart'; +======== +import 'package:titan/mypayment/providers/selected_structure_provider.dart'; +import 'package:titan/mypayment/providers/stores_list_provider.dart'; +import 'package:titan/mypayment/ui/pages/structure_admin_page/add_store_card.dart'; +import 'package:titan/mypayment/ui/pages/structure_admin_page/admin_store_card.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; +>>>>>>>> main:lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/refresher.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; + +<<<<<<<< HEAD:lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart +class StructureStoresPage extends ConsumerWidget { + const StructureStoresPage({super.key}); +======== +class StructureAdminPage extends ConsumerWidget { + const StructureAdminPage({super.key}); +>>>>>>>> main:lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart + + @override + Widget build(BuildContext context, WidgetRef ref) { + final storeList = ref.watch(storeListProvider); + final storeListNotifier = ref.read(storeListProvider.notifier); + final structure = ref.watch(selectedStructureProvider); + return PaymentTemplate( + child: Refresher( + controller: ScrollController(), + onRefresh: () async { + await storeListNotifier.getStores(); + }, + child: Column( + children: [ + const SizedBox(height: 10), + AlignLeftText( + AppLocalizations.of( + context, + )!.paiementStructureManagement(structure.name), + color: Colors.grey, + fontSize: 20, + fontWeight: FontWeight.bold, + padding: EdgeInsets.only(left: 30), + ), + const SizedBox(height: 10), + const AddStoreCard(), + AsyncChild( + value: storeList, + builder: (context, stores) { + final storeFromStructures = stores.where( + (store) => store.structure.id == structure.id, + ); + return Column( + children: storeFromStructures + .map((store) => AdminStoreCard(store: store)) + .toList(), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/paiement/ui/pages/transfer_structure_page/search_result.dart b/lib/mypayment/ui/pages/transfer_structure_page/search_result.dart similarity index 96% rename from lib/paiement/ui/pages/transfer_structure_page/search_result.dart rename to lib/mypayment/ui/pages/transfer_structure_page/search_result.dart index 558201fcb1..946f6dc985 100644 --- a/lib/paiement/ui/pages/transfer_structure_page/search_result.dart +++ b/lib/mypayment/ui/pages/transfer_structure_page/search_result.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/paiement/providers/transfer_structure_provider.dart'; +import 'package:titan/mypayment/providers/selected_store_provider.dart'; +import 'package:titan/mypayment/providers/transfer_structure_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; diff --git a/lib/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart b/lib/mypayment/ui/pages/transfer_structure_page/transfer_structure_page.dart similarity index 95% rename from lib/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart rename to lib/mypayment/ui/pages/transfer_structure_page/transfer_structure_page.dart index def4355d33..f3ddf4c296 100644 --- a/lib/paiement/ui/pages/transfer_structure_page/transfer_structure_page.dart +++ b/lib/mypayment/ui/pages/transfer_structure_page/transfer_structure_page.dart @@ -3,8 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/ui/pages/transfer_structure_page/search_result.dart'; -import 'package:titan/paiement/ui/paiement.dart'; +import 'package:titan/mypayment/ui/pages/transfer_structure_page/search_result.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; import 'package:titan/tools/ui/widgets/styled_search_bar.dart'; import 'package:titan/user/providers/user_list_provider.dart'; diff --git a/lib/navigation/providers/email_popup_state_provider.dart b/lib/navigation/providers/email_popup_state_provider.dart new file mode 100644 index 0000000000..4915e580f9 --- /dev/null +++ b/lib/navigation/providers/email_popup_state_provider.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class EmailPopupStateProvider extends StateNotifier { + EmailPopupStateProvider() : super(false); + + void open() { + state = true; + } + + void close() { + state = false; + } +} + +final emailPopupStateProvider = + StateNotifierProvider((ref) { + return EmailPopupStateProvider(); + }); diff --git a/lib/others/ui/loading_page.dart b/lib/others/ui/loading_page.dart index e0c98dc3dc..6be5c01179 100644 --- a/lib/others/ui/loading_page.dart +++ b/lib/others/ui/loading_page.dart @@ -4,9 +4,11 @@ import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/login/router.dart'; import 'package:titan/router.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:titan/tools/ui/widgets/loader.dart'; import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/version/providers/minimal_hyperion_version_provider.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:titan/version/providers/version_verifier_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -18,32 +20,45 @@ class LoadingPage extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final versionVerifier = ref.watch(versionVerifierProvider); final titanVersion = ref.watch(titanVersionProvider); + final minimalHyperionVersion = ref.watch(minimalHyperionVersionProvider); final isLoggedIn = ref.watch(isLoggedInProvider); final check = versionVerifier.whenData( (value) => value.minimalTitanVersion <= titanVersion, ); final pathForwarding = ref.read(pathForwardingProvider); + final isHyperionVersionCompatible = versionVerifier.whenData( + (value) => isVersionCompatible(value.version, minimalHyperionVersion), + ); check.when( data: (value) { if (!value) { QR.to(AppRouter.update); } - if (!isLoggedIn) { - QR.to(LoginRouter.root); - } - final user = ref.watch(asyncUserProvider); - user.when( - data: (data) { - QR.to(pathForwarding.path); - }, - error: (error, s) { - QR.to(LoginRouter.root); + isHyperionVersionCompatible.when( + data: (value) { + if (!value) { + QR.to(AppRouter.rollback); + } + if (!isLoggedIn) { + QR.to(LoginRouter.root); + } + final user = ref.watch(asyncUserProvider); + user.when( + data: (_) { + QR.to(pathForwarding.path); + }, + error: (error, _) { + QR.to(LoginRouter.root); + }, + loading: () {}, + ); }, + error: (error, _) => QR.to(AppRouter.noInternet), loading: () {}, ); }, + error: (error, _) => QR.to(AppRouter.noInternet), loading: () {}, - error: (error, stack) => QR.to(AppRouter.noInternet), ); return const Scaffold( backgroundColor: ColorConstants.background, diff --git a/lib/others/ui/no_internet_page.dart b/lib/others/ui/no_internet_page.dart index 41fa322577..bf8bd3c872 100644 --- a/lib/others/ui/no_internet_page.dart +++ b/lib/others/ui/no_internet_page.dart @@ -6,6 +6,7 @@ import 'package:titan/home/router.dart'; import 'package:titan/tools/constants.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:titan/tools/repository/repository.dart'; class NoInternetPage extends HookConsumerWidget { const NoInternetPage({super.key}); @@ -27,7 +28,9 @@ class NoInternetPage extends HookConsumerWidget { const SizedBox(height: 20), Center( child: Text( - AppLocalizations.of(context)!.othersUnableToConnectToServer, + AppLocalizations.of( + context, + )!.othersUnableToConnectToServer(Repository.host), textAlign: TextAlign.center, style: const TextStyle(fontSize: 20), ), diff --git a/lib/others/ui/no_module.dart b/lib/others/ui/no_module.dart index d08943dc5d..f77aefb3f4 100644 --- a/lib/others/ui/no_module.dart +++ b/lib/others/ui/no_module.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/module_root_list_provider.dart'; import 'package:titan/tools/constants.dart'; +import 'package:titan/admin/providers/permission_name_list_provider.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/l10n/app_localizations.dart'; @@ -12,9 +12,9 @@ class NoModulePage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final moduleVisibilityList = ref.watch(moduleRootListProvider); + final permissionsNamesList = ref.watch(permissionsNamesListProvider); final pathForwarding = ref.read(pathForwardingProvider); - moduleVisibilityList.maybeWhen( + permissionsNamesList.maybeWhen( data: (data) { QR.to(pathForwarding.path); }, diff --git a/lib/others/ui/rollback_page.dart b/lib/others/ui/rollback_page.dart new file mode 100644 index 0000000000..88255bc987 --- /dev/null +++ b/lib/others/ui/rollback_page.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/others/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/repository/repository.dart'; +import 'package:titan/version/providers/minimal_hyperion_version_provider.dart'; +import 'package:titan/version/providers/titan_version_provider.dart'; +import 'package:titan/version/providers/version_verifier_provider.dart'; + +class RollbackPage extends HookConsumerWidget { + const RollbackPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final minimalHyperionVersion = ref.watch(minimalHyperionVersionProvider); + final versionVerifier = ref.watch(versionVerifierProvider); + final titanVersion = ref.watch(titanVersionProvider); + + return Scaffold( + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + children: [ + const Spacer(flex: 2), + const HeroIcon(HeroIcons.rocketLaunch, size: 100), + const SizedBox(height: 50), + Center( + child: Text( + getAppFlavor() == "dev" + ? OthersTextConstants.tooRecentVersionDevFlavor + : OthersTextConstants.tooRecentVersion, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 20), + ), + ), + const Spacer(flex: 3), + Text( + "${OthersTextConstants.version} $titanVersion, flavor ${getAppFlavor()}", + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + const SizedBox(height: 20), + Text( + "${OthersTextConstants.minimalHyperionVersion} : $minimalHyperionVersion", + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + const SizedBox(height: 20), + Text( + "${OthersTextConstants.hyperionVersion} (${Repository.host}) : ${versionVerifier.whenOrNull(data: (value) { + return value.version; + })}", + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } +} diff --git a/lib/paiement/providers/selected_store_provider.dart b/lib/paiement/providers/selected_store_provider.dart deleted file mode 100644 index 43c6cdb59a..0000000000 --- a/lib/paiement/providers/selected_store_provider.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/user_store.dart'; -import 'package:titan/paiement/providers/last_used_store_id_provider.dart'; -import 'package:titan/paiement/providers/my_stores_provider.dart'; - -class SelectedStoreNotifier extends StateNotifier { - final LastUsedStoreIdNotifier lastUsedStoreIdNotifier; - SelectedStoreNotifier(this.lastUsedStoreIdNotifier, super.store); - - void updateStore(UserStore store) { - state = store; - lastUsedStoreIdNotifier.saveLastUsedStoreIdToSharedPreferences(store.id); - } -} - -final selectedStoreProvider = - StateNotifierProvider((ref) { - final myStores = ref.watch(myStoresProvider); - final lastUsedStoreId = ref.read(lastUsedStoreIdProvider); - final lastUsedStoreIdNotifier = ref.read( - lastUsedStoreIdProvider.notifier, - ); - final store = myStores.maybeWhen( - orElse: () => UserStore.empty(), - data: (value) { - if (value.isEmpty) { - return UserStore.empty(); - } - return value.firstWhere( - (store) => store.id == lastUsedStoreId, - orElse: () => value.first, - ); - }, - ); - return SelectedStoreNotifier(lastUsedStoreIdNotifier, store); - }); diff --git a/lib/paiement/ui/pages/devices_page/device_item.dart b/lib/paiement/ui/pages/devices_page/device_item.dart deleted file mode 100644 index 8d844aa0ee..0000000000 --- a/lib/paiement/ui/pages/devices_page/device_item.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/wallet_device.dart'; -import 'package:titan/paiement/tools/functions.dart'; - -class DeviceItem extends ConsumerWidget { - final WalletDevice device; - final bool isActual; - final Future Function() onRevoke; - const DeviceItem({ - super.key, - required this.device, - required this.isActual, - required this.onRevoke, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 30), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(15)), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: Container( - color: Colors.grey.shade200.withValues(alpha: 0.5), - child: SizedBox( - height: isActual ? 80 : 70, - child: Row( - children: [ - const SizedBox(width: 20), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - device.name, - style: const TextStyle( - color: Color(0xff204550), - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - if (isActual) - Text( - AppLocalizations.of(context)!.paiementThisDevice, - style: TextStyle( - color: Color(0xff204550), - fontSize: 15, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(width: 10), - Spacer(), - getStatusTag(device.status), - if (device.status != WalletDeviceStatus.revoked) ...[ - const SizedBox(width: 20), - GestureDetector( - onTap: onRevoke, - child: const HeroIcon( - HeroIcons.trash, - size: 25, - color: Color(0xff204550), - ), - ), - ], - const SizedBox(width: 20), - ], - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/paiement/ui/pages/devices_page/devices_page.dart b/lib/paiement/ui/pages/devices_page/devices_page.dart deleted file mode 100644 index c34c6e1b07..0000000000 --- a/lib/paiement/ui/pages/devices_page/devices_page.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'dart:convert'; - -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/l10n/app_localizations.dart'; -import 'package:titan/paiement/class/create_device.dart'; -import 'package:titan/paiement/class/wallet_device.dart'; -import 'package:titan/paiement/providers/device_list_provider.dart'; -import 'package:titan/paiement/providers/device_provider.dart'; -import 'package:titan/paiement/providers/has_accepted_tos_provider.dart'; -import 'package:titan/paiement/providers/key_service_provider.dart'; -import 'package:titan/paiement/tools/functions.dart'; -import 'package:titan/paiement/ui/pages/devices_page/add_device_button.dart'; -import 'package:titan/paiement/ui/pages/devices_page/device_item.dart'; -import 'package:titan/paiement/ui/pages/main_page/account_card/device_dialog_box.dart'; -import 'package:titan/paiement/ui/paiement.dart'; -import 'package:titan/tools/functions.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/layouts/refresher.dart'; -import 'package:titan/tools/ui/widgets/custom_dialog_box.dart'; - -class DevicesPage extends HookConsumerWidget { - const DevicesPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final devices = ref.watch(deviceListProvider); - final devicesNotifier = ref.read(deviceListProvider.notifier); - final deviceNotifier = ref.read(deviceProvider.notifier); - final keyService = ref.watch(keyServiceProvider); - final deviceKey = keyService.getKeyId(); - final displayAddDevice = useState(true); - final hasAcceptedToS = ref.watch(hasAcceptedTosProvider); - - void displayToastWithContext(TypeMsg type, String msg) { - displayToast(context, type, msg); - } - - Future getDeviceName() async { - final deviceInfo = DeviceInfoPlugin(); - if (Theme.of(context).platform == TargetPlatform.android) { - return deviceInfo.androidInfo.then((info) => info.model); - } else if (Theme.of(context).platform == TargetPlatform.iOS) { - return deviceInfo.iosInfo.then((info) => info.utsname.machine); - } else { - return Future.value( - AppLocalizations.of(context)!.paiementUnknownDevice, - ); - } - } - - return PaymentTemplate( - child: Refresher( - controller: ScrollController(), - onRefresh: () async { - await devicesNotifier.getDeviceList(); - }, - child: FutureBuilder( - future: deviceKey, - builder: (context, snapshot) { - return AsyncChild( - value: devices, - builder: (context, devices) { - final sortedDevices = devices.toList() - ..sort((a, b) { - if (a.id == snapshot.data) return -1; - if (b.id == snapshot.data) return 1; - return statusOrder( - a.status, - ).compareTo(statusOrder(b.status)); - }); - - final firstDevice = - devices.map((e) => e.id).contains(snapshot.data) - ? devices - .where((element) => element.id == snapshot.data) - .first - : null; - - final shouldDisplayAddDevice = - (snapshot.data == null || - firstDevice == null || - firstDevice.status == WalletDeviceStatus.revoked) && - displayAddDevice.value; - - if (firstDevice != null) { - sortedDevices.remove(firstDevice); - sortedDevices.insert(0, firstDevice); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (shouldDisplayAddDevice && !kIsWeb) - AddDeviceButton( - onTap: () async { - if (!hasAcceptedToS) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.paiementPleaseAcceptTOS, - ); - return; - } - final name = await getDeviceName(); - final keyPair = await keyService.generateKeyPair(); - final publicKey = - (await keyPair.extractPublicKey()).bytes; - final base64PublicKey = base64Encode(publicKey); - final body = CreateDevice( - name: name, - ed25519PublicKey: base64PublicKey, - ); - final value = await deviceNotifier.registerDevice( - body, - ); - if (value != null) { - await keyService.saveKeyPair(keyPair); - await keyService.saveKeyId(value); - await devicesNotifier.getDeviceList(); - displayAddDevice.value = false; - if (context.mounted) { - await showDialog( - context: context, - builder: (context) { - return DeviceDialogBox( - title: AppLocalizations.of( - context, - )!.paiementAskDeviceActivation, - descriptions: AppLocalizations.of( - context, - )!.paiementDeviceActivationReceived, - buttonText: "Ok", - onClick: () { - Navigator.of(context).pop(); - }, - ); - }, - ); - } - } - }, - ), - ...sortedDevices.map((device) { - return DeviceItem( - device: device, - isActual: device.id == snapshot.data, - onRevoke: () async { - if (!hasAcceptedToS) { - displayToastWithContext( - TypeMsg.error, - AppLocalizations.of( - context, - )!.paiementPleaseAcceptTOS, - ); - return; - } - await showDialog( - context: context, - builder: (context) { - return CustomDialogBox( - title: AppLocalizations.of( - context, - )!.paiementRevokeDevice, - descriptions: AppLocalizations.of( - context, - )!.paiementRevokeDeviceDescription, - onYes: () async { - tokenExpireWrapper(ref, () async { - final deviceRevokedMsg = - AppLocalizations.of( - context, - )!.paiementDeviceRevoked; - final deviceRevokingErrorMsg = - AppLocalizations.of( - context, - )!.paiementDeviceRevokingError; - final value = await devicesNotifier - .revokeDevice( - device.copyWith( - status: WalletDeviceStatus.revoked, - ), - ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - deviceRevokedMsg, - ); - final savedId = await keyService - .getKeyId(); - if (savedId == device.id) { - await keyService.clear(); - } - } else { - displayToastWithContext( - TypeMsg.error, - deviceRevokingErrorMsg, - ); - } - }); - }, - ); - }, - ); - }, - ); - }), - SizedBox(height: 80), - ], - ); - }, - ); - }, - ), - ), - ); - } -} diff --git a/lib/paiement/ui/pages/pay_page/qr_code.dart b/lib/paiement/ui/pages/pay_page/qr_code.dart deleted file mode 100644 index d26710fe01..0000000000 --- a/lib/paiement/ui/pages/pay_page/qr_code.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/paiement/providers/key_service_provider.dart'; -import 'package:titan/paiement/providers/pay_amount_provider.dart'; -import 'package:titan/paiement/tools/functions.dart'; -import 'package:titan/tools/ui/widgets/loader.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:uuid/uuid.dart'; - -class QrCode extends ConsumerWidget { - const QrCode({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final payAmount = ref.watch(payAmountProvider); - final id = const Uuid().v4(); - final keyService = ref.watch(keyServiceProvider); - return FutureBuilder( - future: getQRCodeContent(id, payAmount, keyService, true), - builder: (context, snapshot) { - if (snapshot.data == null) { - return SizedBox( - width: MediaQuery.of(context).size.width * 0.8, - height: MediaQuery.of(context).size.width * 0.8, - child: const Loader(), - ); - } - return Center( - child: QrImageView( - data: snapshot.data!, - version: QrVersions.auto, - size: min( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.8, - ), - eyeStyle: const QrEyeStyle( - color: Colors.black, - eyeShape: QrEyeShape.square, - ), - dataModuleStyle: const QrDataModuleStyle( - dataModuleShape: QrDataModuleShape.square, - color: Colors.black, - ), - ), - ); - }, - ); - } -} diff --git a/lib/paiement/ui/pages/store_stats_page/interval_selector.dart b/lib/paiement/ui/pages/store_stats_page/interval_selector.dart deleted file mode 100644 index 18ba1a626f..0000000000 --- a/lib/paiement/ui/pages/store_stats_page/interval_selector.dart +++ /dev/null @@ -1,240 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:intl/intl.dart'; -import 'package:titan/paiement/providers/selected_interval_provider.dart'; -import 'package:titan/paiement/providers/selected_store_history.dart'; -import 'package:titan/paiement/providers/selected_store_provider.dart'; -import 'package:titan/tools/providers/locale_notifier.dart'; - -class IntervalSelector extends ConsumerWidget { - const IntervalSelector({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final now = DateTime.now(); - final locale = ref.watch(localeProvider); - final selectedStore = ref.watch(selectedStoreProvider); - final selectedHistoryNotifier = ref.read(sellerHistoryProvider.notifier); - final selectedInterval = ref.watch(selectedIntervalProvider); - final selectedIntervalNotifier = ref.read( - selectedIntervalProvider.notifier, - ); - - Future getDate(DateTime initialDate) async { - return await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: now.subtract(const Duration(days: 365 * 5)), - lastDate: now, - builder: (BuildContext context, Widget? child) { - return Theme( - data: ThemeData.light().copyWith( - colorScheme: const ColorScheme.light( - primary: Color(0xff017f80), - onPrimary: Colors.white, - surface: Colors.white, - onSurface: Colors.black, - ), - dialogTheme: DialogThemeData(backgroundColor: Colors.white), - ), - child: child!, - ); - }, - ); - } - - Future getTime(DateTime initialDate) async { - return await showTimePicker( - context: context, - initialTime: TimeOfDay.fromDateTime(initialDate), - builder: (BuildContext context, Widget? child) { - return Theme( - data: ThemeData.light().copyWith( - colorScheme: const ColorScheme.light( - primary: Color(0xff017f80), - onPrimary: Colors.white, - surface: Colors.white, - onSurface: Colors.black, - ), - dialogTheme: DialogThemeData(backgroundColor: Colors.white), - ), - child: child!, - ); - }, - ); - } - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Row( - children: [ - GestureDetector( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - decoration: BoxDecoration( - color: Color(0xff017f80).withAlpha(50), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DateFormat.yMd( - locale.toString(), - ).format(selectedInterval.start), - style: TextStyle( - color: const Color(0xff204550), - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), - onTap: () async { - final date = await getDate(selectedInterval.start); - if (date != null) { - selectedIntervalNotifier.updateStart(date); - await selectedHistoryNotifier.getHistory( - selectedStore.id, - date, - selectedInterval.end, - ); - } - }, - ), - SizedBox(width: 5), - GestureDetector( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - decoration: BoxDecoration( - color: Color(0xff017f80).withAlpha(50), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DateFormat.Hm( - locale.toString(), - ).format(selectedInterval.start), - style: TextStyle( - color: const Color(0xff204550), - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), - onTap: () async { - final time = await getTime(selectedInterval.start); - if (time != null) { - final date = DateTime( - selectedInterval.start.year, - selectedInterval.start.month, - selectedInterval.start.day, - time.hour, - time.minute, - ); - selectedIntervalNotifier.updateStart(date); - await selectedHistoryNotifier.getHistory( - selectedStore.id, - date, - selectedInterval.end, - ); - } - }, - ), - ], - ), - SizedBox(width: 5), - HeroIcon( - HeroIcons.arrowRight, - color: const Color(0xff204550), - size: 20, - ), - SizedBox(width: 5), - Row( - children: [ - GestureDetector( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - decoration: BoxDecoration( - color: Color(0xff017f80).withAlpha(50), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DateFormat.yMd( - locale.toString(), - ).format(selectedInterval.end), - style: TextStyle( - color: const Color(0xff204550), - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), - onTap: () async { - final date = await getDate(selectedInterval.end); - if (date != null) { - selectedIntervalNotifier.updateEnd(date); - await selectedHistoryNotifier.getHistory( - selectedStore.id, - selectedInterval.start, - date, - ); - } - }, - ), - SizedBox(width: 5), - GestureDetector( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - decoration: BoxDecoration( - color: Color(0xff017f80).withAlpha(50), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DateFormat.Hm( - locale.toString(), - ).format(selectedInterval.end), - style: TextStyle( - color: const Color(0xff204550), - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), - onTap: () async { - final time = await getTime(selectedInterval.end); - if (time != null) { - final date = DateTime( - selectedInterval.end.year, - selectedInterval.end.month, - selectedInterval.end.day, - time.hour, - time.minute, - ); - selectedIntervalNotifier.updateEnd(date); - await selectedHistoryNotifier.getHistory( - selectedStore.id, - selectedInterval.start, - date, - ); - } - }, - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/paiement/ui/pages/structure_admin_page/add_store_card.dart b/lib/paiement/ui/pages/structure_admin_page/add_store_card.dart index c43c414c4b..e52b3d925c 100644 --- a/lib/paiement/ui/pages/structure_admin_page/add_store_card.dart +++ b/lib/paiement/ui/pages/structure_admin_page/add_store_card.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/paiement/class/store.dart'; -import 'package:titan/paiement/providers/store_provider.dart'; -import 'package:titan/paiement/router.dart'; +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/providers/store_provider.dart'; +import 'package:titan/mypayment/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; class AddStoreCard extends ConsumerWidget { diff --git a/lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart b/lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart index 1f81751a1b..5723d8bfd1 100644 --- a/lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart +++ b/lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart @@ -1,11 +1,18 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +<<<<<<<< HEAD:lib/paiement/ui/pages/structure_admin_page/admin_store_card.dart import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/class/store.dart'; import 'package:titan/paiement/providers/store_provider.dart'; import 'package:titan/paiement/providers/stores_list_provider.dart'; import 'package:titan/paiement/router.dart'; +======== +import 'package:titan/mypayment/class/store.dart'; +import 'package:titan/mypayment/providers/store_provider.dart'; +import 'package:titan/mypayment/providers/stores_list_provider.dart'; +import 'package:titan/mypayment/router.dart'; +>>>>>>>> main:lib/mypayment/ui/pages/structure_admin_page/admin_store_card.dart import 'package:titan/tools/functions.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; import 'package:titan/tools/ui/builders/waiting_button.dart'; diff --git a/lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart b/lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart index 420614ac27..98f28d8f97 100644 --- a/lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart +++ b/lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart @@ -1,17 +1,30 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +<<<<<<<< HEAD:lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart import 'package:titan/l10n/app_localizations.dart'; import 'package:titan/paiement/providers/selected_structure_provider.dart'; import 'package:titan/paiement/providers/stores_list_provider.dart'; import 'package:titan/paiement/ui/pages/structure_admin_page/add_store_card.dart'; import 'package:titan/paiement/ui/pages/structure_admin_page/admin_store_card.dart'; import 'package:titan/paiement/ui/paiement.dart'; +======== +import 'package:titan/mypayment/providers/selected_structure_provider.dart'; +import 'package:titan/mypayment/providers/stores_list_provider.dart'; +import 'package:titan/mypayment/ui/pages/structure_admin_page/add_store_card.dart'; +import 'package:titan/mypayment/ui/pages/structure_admin_page/admin_store_card.dart'; +import 'package:titan/mypayment/ui/mypayment.dart'; +>>>>>>>> main:lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/tools/ui/widgets/align_left_text.dart'; +<<<<<<<< HEAD:lib/paiement/ui/pages/structure_admin_page/structure_admin_page.dart class StructureStoresPage extends ConsumerWidget { const StructureStoresPage({super.key}); +======== +class StructureAdminPage extends ConsumerWidget { + const StructureAdminPage({super.key}); +>>>>>>>> main:lib/mypayment/ui/pages/structure_admin_page/structure_admin_page.dart @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/ph/providers/is_ph_admin_provider.dart b/lib/ph/providers/is_ph_admin_provider.dart index 0497d8ace7..f7cd57c24a 100644 --- a/lib/ph/providers/is_ph_admin_provider.dart +++ b/lib/ph/providers/is_ph_admin_provider.dart @@ -1,9 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/ph/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; final isPhAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("4ec5ae77-f955-4309-96a5-19cc3c8be71c"); // admin_ph + return hasUserPermission(ref, PhPermissionConstants.managePh); }); diff --git a/lib/ph/tools/constants.dart b/lib/ph/tools/constants.dart new file mode 100644 index 0000000000..2c42eceb86 --- /dev/null +++ b/lib/ph/tools/constants.dart @@ -0,0 +1,4 @@ +class PhPermissionConstants { + static const String accessPh = "access_ph"; + static const String managePh = "manage_ph"; +} diff --git a/lib/phonebook/class/association_groupement.dart b/lib/phonebook/class/association_groupement.dart index 67b84e765e..975117f5fb 100644 --- a/lib/phonebook/class/association_groupement.dart +++ b/lib/phonebook/class/association_groupement.dart @@ -1,26 +1,37 @@ class AssociationGroupement { - AssociationGroupement({required this.id, required this.name}); + AssociationGroupement({ + required this.id, + required this.name, + required this.managerGroupId, + }); late final String id; late final String name; + late final String managerGroupId; AssociationGroupement.fromJson(Map json) { id = json['id']; name = json['name']; + managerGroupId = json['manager_group_id']; } Map toJson() { - final data = {'id': id, 'name': name}; + final data = { + 'id': id, + 'name': name, + 'manager_group_id': managerGroupId, + }; return data; } AssociationGroupement.empty() { id = ""; name = ""; + managerGroupId = ""; } @override String toString() { - return 'AssociationGroupement(kinds: $id, name: $name)'; + return 'AssociationGroupement(kinds: $id, name: $name, managerGroupId: $managerGroupId)'; } } diff --git a/lib/phonebook/providers/association_filtered_list_provider.dart b/lib/phonebook/providers/association_filtered_list_provider.dart index ae533612ba..9d6ee198b3 100644 --- a/lib/phonebook/providers/association_filtered_list_provider.dart +++ b/lib/phonebook/providers/association_filtered_list_provider.dart @@ -30,8 +30,7 @@ final associationFilteredListProvider = Provider>((ref) { .toList(); } return associationGroupements.maybeWhen( - data: (groupements) => - sortedAssociationByKind(filteredAssociations, groupements), + data: (kinds) => sortedAssociationByKind(filteredAssociations, kinds), orElse: () => filteredAssociations, ); }, diff --git a/lib/phonebook/providers/association_groupement_list_provider.dart b/lib/phonebook/providers/association_groupement_list_provider.dart index e61666d9df..ba41d858c1 100644 --- a/lib/phonebook/providers/association_groupement_list_provider.dart +++ b/lib/phonebook/providers/association_groupement_list_provider.dart @@ -4,6 +4,7 @@ import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/repositories/association_groupement_repository.dart'; import 'package:titan/tools/providers/list_notifier.dart'; import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:titan/user/providers/user_provider.dart'; class AssociationGroupementListNotifier extends ListNotifier { @@ -71,3 +72,18 @@ final associationGroupementListProvider = }); return notifier; }); + +final groupementAdminProvider = Provider>((ref) { + final associationGroupements = ref.watch(associationGroupementListProvider); + final me = ref.watch(userProvider); + return associationGroupements.maybeWhen( + data: (groupements) { + return groupements.where((groupement) { + return me.groups.any( + (userGroup) => userGroup.id == groupement.managerGroupId, + ); + }).toList(); + }, + orElse: () => [], + ); +}); diff --git a/lib/phonebook/providers/association_member_sorted_list_provider.dart b/lib/phonebook/providers/association_member_sorted_list_provider.dart index b41782785e..f12f66db0b 100644 --- a/lib/phonebook/providers/association_member_sorted_list_provider.dart +++ b/lib/phonebook/providers/association_member_sorted_list_provider.dart @@ -11,7 +11,7 @@ final associationMemberSortedListProvider = Provider>(( final association = ref.watch(associationProvider); return memberListProvider.maybeWhen( data: (members) { - return sortedMembers(members, association); + return sortedMembers(members, association.id, association.mandateYear); }, orElse: () => List.empty(), ); diff --git a/lib/phonebook/providers/is_phonebook_admin_provider.dart b/lib/phonebook/providers/is_phonebook_admin_provider.dart index 3ab06abd8d..1f7c108442 100644 --- a/lib/phonebook/providers/is_phonebook_admin_provider.dart +++ b/lib/phonebook/providers/is_phonebook_admin_provider.dart @@ -1,46 +1,41 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; -import 'package:titan/phonebook/class/complete_member.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; -import 'package:titan/phonebook/providers/roles_tags_provider.dart'; -import 'package:titan/phonebook/tools/function.dart'; +import 'package:titan/phonebook/tools/constant.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/user/providers/user_provider.dart'; -final isPhonebookAdminProvider = StateProvider((ref) { - final user = ref.watch(userProvider); - return user.groups - .map((e) => e.id) - .contains("d3f91313-d7e5-49c6-b01f-c19932a7e09b"); // admin_phonebook -}); - -final hasPhonebookAdminAccessProvider = StateProvider((ref) { - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isAdminProvider); - return isPhonebookAdmin || isAdmin; +final isPhonebookAdminProvider = Provider((ref) { + return hasUserPermission(ref, PhonebookPermissionConstants.managePhonebook); }); final isAssociationPresidentProvider = Provider((ref) { final association = ref.watch(associationProvider); - final rolesTags = ref.watch(rolesTagsProvider); final membersList = ref.watch(associationMemberListProvider); final me = ref.watch(userProvider); + bool isPresident = false; + membersList.whenData((members) { + if (members.map((e) => e.member.id).contains(me.id)) { + if (members + .firstWhere((completeMember) => completeMember.member.id == me.id) + .memberships + .firstWhere( + (membership) => membership.associationId == association.id, + ) + .rolesTags + .contains(PhonebookTextConstants.presidentRoleTag)) { + isPresident = true; + } + } + }); + return isPresident; +}); - return membersList.maybeWhen( - data: (members) { - final member = members.firstWhere( - (m) => m.member.id == me.id, - orElse: () => CompleteMember.empty(), - ); - if (member.member.id == "") return false; - final membership = getMembershipForAssociation(member, association); - return rolesTags.maybeWhen( - data: (tags) { - return membership.rolesTags.contains(tags.first); - }, - orElse: () => false, - ); - }, - orElse: () => false, - ); +final hasPhonebookAdminAccessProvider = Provider((ref) { + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final isAnyGroupementAdmin = ref.watch(groupementAdminProvider); + final isAdmin = ref.watch(isAdminProvider); + return isPhonebookAdmin || isAdmin || isAnyGroupementAdmin.isNotEmpty; }); diff --git a/lib/phonebook/router.dart b/lib/phonebook/router.dart index c62e6c4282..851d72f1e6 100644 --- a/lib/phonebook/router.dart +++ b/lib/phonebook/router.dart @@ -6,14 +6,21 @@ import 'package:titan/navigation/class/module.dart'; import 'package:titan/phonebook/providers/is_phonebook_admin_provider.dart'; import 'package:titan/phonebook/ui/pages/add_edit_groupement_page/groupement_add_edit_page.dart' deferred as groupement_add_edit_page; -import 'package:titan/phonebook/ui/pages/admin_page/admin_page.dart' - deferred as admin_page; import 'package:titan/phonebook/ui/pages/association_add_edit_page/association_add_edit_page.dart' deferred as association_add_edit_page; import 'package:titan/phonebook/ui/pages/association_groups_page/association_groups_page.dart' deferred as association_groups_page; import 'package:titan/phonebook/ui/pages/association_members_page/association_members_page.dart' deferred as association_members_page; +import 'package:titan/phonebook/providers/is_phonebook_admin_provider.dart'; +import 'package:titan/phonebook/ui/pages/add_edit_groupement/add_edit_groupement_page.dart' + deferred as add_edit_groupement_page; +import 'package:titan/phonebook/ui/pages/admin_page/admin_page.dart' + deferred as admin_page; +import 'package:titan/phonebook/ui/pages/association_creation_page/association_creation_page.dart' + deferred as association_creation_page; +import 'package:titan/phonebook/ui/pages/association_editor_page/association_editor_page.dart' + deferred as association_editor_page; import 'package:titan/phonebook/ui/pages/association_page/association_page.dart' deferred as association_page; import 'package:titan/phonebook/ui/pages/main_page/main_page.dart' diff --git a/lib/phonebook/tools/constant.dart b/lib/phonebook/tools/constant.dart new file mode 100644 index 0000000000..176284c497 --- /dev/null +++ b/lib/phonebook/tools/constant.dart @@ -0,0 +1,8 @@ +class PhonebookPermissionConstants { + static const String accessPhonebook = 'access_phonebook'; + static const String managePhonebook = 'manage_phonebook'; +} + +class PhonebookTextConstants { + static const String presidentRoleTag = 'president'; +} diff --git a/lib/phonebook/tools/function.dart b/lib/phonebook/tools/function.dart index 0b0d535fcd..284713f17f 100644 --- a/lib/phonebook/tools/function.dart +++ b/lib/phonebook/tools/function.dart @@ -1,33 +1,31 @@ import 'package:diacritic/diacritic.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/phonebook/class/association.dart'; import 'package:titan/phonebook/class/association_groupement.dart'; import 'package:titan/phonebook/class/complete_member.dart'; import 'package:titan/phonebook/class/membership.dart'; +import 'package:titan/phonebook/providers/roles_tags_provider.dart'; -Membership getMembershipForAssociation( - CompleteMember member, - Association association, -) { - return member.memberships.firstWhere( +int getPosition(CompleteMember member, String associationId, int year) { + Membership membership = member.memberships.firstWhere( (element) => - element.associationId == association.id && - element.mandateYear == association.mandateYear, - orElse: () => Membership.empty(), + element.associationId == associationId && element.mandateYear == year, ); -} - -int getPosition(CompleteMember member, Association association) { - final membership = getMembershipForAssociation(member, association); return membership.order; } List sortedMembers( List members, - Association association, + String associationId, + int year, ) { return members..sort( - (a, b) => - getPosition(a, association).compareTo(getPosition(b, association)), + (a, b) => getPosition( + a, + associationId, + year, + ).compareTo(getPosition(b, associationId, year)), ); } @@ -51,3 +49,27 @@ List sortedAssociationByKind( // Flatten the sorted map values into a single list return sortedByGroupement.values.expand((list) => list).toList(); } + +Color getColorFromTagList(WidgetRef ref, List tags) { + final rolesTags = ref.watch(rolesTagsProvider); + return rolesTags.maybeWhen( + orElse: () => Colors.white, + data: (data) { + int index = 3; + for (String tag in tags) { + if (data.indexOf(tag) < index) { + index = data.indexOf(tag); + } + } + switch (index) { + case 0: + return const Color.fromARGB(255, 251, 109, 16); + case 1: + return const Color.fromARGB(255, 252, 145, 74); + case 2: + return const Color.fromARGB(255, 253, 193, 153); + } + return Colors.white; + }, + ); +} diff --git a/lib/phonebook/ui/pages/add_edit_groupement/add_edit_groupement_page.dart b/lib/phonebook/ui/pages/add_edit_groupement/add_edit_groupement_page.dart new file mode 100644 index 0000000000..16658231b1 --- /dev/null +++ b/lib/phonebook/ui/pages/add_edit_groupement/add_edit_groupement_page.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/providers/group_list_provider.dart'; +import 'package:titan/admin/tools/constants.dart'; +import 'package:titan/phonebook/class/association_groupement.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; +import 'package:titan/phonebook/providers/association_groupement_provider.dart'; +import 'package:titan/phonebook/tools/constants.dart'; +import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/tools/token_expire_wrapper.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/ui/builders/async_child.dart'; +import 'package:titan/tools/ui/layouts/button.dart'; +import 'package:titan/tools/ui/layouts/item_chip.dart'; +import 'package:titan/tools/ui/widgets/align_left_text.dart'; +import 'package:titan/tools/ui/widgets/text_entry.dart'; + +class AssociationGroupementAddEditPage extends HookConsumerWidget { + const AssociationGroupementAddEditPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final associationGroupement = ref.watch(associationGroupementProvider); + final associaitonGroupementListNotifier = ref.watch( + associationGroupementListProvider.notifier, + ); + final groups = ref.watch(allGroupListProvider); + final name = useTextEditingController(text: associationGroupement.name); + void displayToastWithContext(TypeMsg type, String msg) { + displayToast(context, type, msg); + } + + final selectedGroup = useState(associationGroupement.managerGroupId); + + return PhonebookTemplate( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 30.0), + child: Column( + children: [ + const SizedBox(height: 30), + Align( + alignment: Alignment.centerLeft, + child: Text( + associationGroupement.id.isNotEmpty + ? PhonebookTextConstants.editAssociationGroupement + : PhonebookTextConstants.addAssociationGroupement, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorConstants.title, + ), + ), + ), + const SizedBox(height: 30), + TextEntry( + controller: name, + label: AdminTextConstants.name, + canBeEmpty: false, + ), + const SizedBox(height: 20), + AlignLeftText( + PhonebookTextConstants.selectManagerGroup, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + AsyncChild( + value: groups, + builder: (context, groupList) => SizedBox( + height: 40, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + final group = groupList[index]; + return ItemChip( + selected: selectedGroup.value == group.id, + onTap: () { + if (selectedGroup.value != group.id) { + selectedGroup.value = group.id; + } else { + selectedGroup.value = ""; + } + }, + child: Text( + group.name, + style: TextStyle( + color: selectedGroup.value == group.id + ? Colors.white + : Colors.black, + ), + ), + ); + }, + itemCount: groupList.length, + ), + ), + ), + const SizedBox(height: 50), + Button( + text: associationGroupement.id.isEmpty + ? AdminTextConstants.add + : AdminTextConstants.edit, + onPressed: () async { + if (name.text.isEmpty) { + displayToastWithContext( + TypeMsg.error, + AdminTextConstants.emptyFieldError, + ); + return; + } + if (selectedGroup.value.isEmpty) { + displayToastWithContext( + TypeMsg.error, + PhonebookTextConstants.emptyManagerGroupError, + ); + return; + } + await tokenExpireWrapper(ref, () async { + if (associationGroupement.id.isNotEmpty) { + final value = await associaitonGroupementListNotifier + .updateAssociationGroupement( + AssociationGroupement( + id: associationGroupement.id, + name: name.text, + managerGroupId: selectedGroup.value, + ), + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + PhonebookTextConstants.updatedAssociationGroupement, + ); + QR.back(); + } else { + displayToastWithContext( + TypeMsg.error, + AdminTextConstants.updatingError, + ); + } + return; + } + final value = await associaitonGroupementListNotifier + .createAssociationGroupement( + AssociationGroupement( + id: "", + name: name.text, + managerGroupId: selectedGroup.value, + ), + ); + if (value) { + displayToastWithContext( + TypeMsg.msg, + PhonebookTextConstants.addedAssociationGroupement, + ); + QR.back(); + } else { + displayToastWithContext( + TypeMsg.error, + AdminTextConstants.addingError, + ); + } + }); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/phonebook/ui/pages/admin_page/admin_page.dart b/lib/phonebook/ui/pages/admin_page/admin_page.dart index fa5bd81bfb..9bd78a4491 100644 --- a/lib/phonebook/ui/pages/admin_page/admin_page.dart +++ b/lib/phonebook/ui/pages/admin_page/admin_page.dart @@ -37,8 +37,8 @@ class AdminPage extends HookConsumerWidget { ); final associationFilteredList = ref.watch(associationFilteredListProvider); final roleNotifier = ref.watch(rolesTagsProvider.notifier); - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final isAdmin = ref.watch(isAdminProvider); + final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); final localizeWithContext = AppLocalizations.of(context)!; diff --git a/lib/phonebook/ui/pages/association_page/association_page.dart b/lib/phonebook/ui/pages/association_page/association_page.dart index 38c3719a51..f1c533fe5a 100644 --- a/lib/phonebook/ui/pages/association_page/association_page.dart +++ b/lib/phonebook/ui/pages/association_page/association_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; @@ -10,6 +11,7 @@ import 'package:titan/phonebook/providers/is_phonebook_admin_provider.dart'; import 'package:titan/phonebook/ui/components/member_card.dart'; import 'package:titan/phonebook/ui/pages/association_page/association_edition_modal.dart'; import 'package:titan/phonebook/ui/phonebook.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/l10n/app_localizations.dart'; diff --git a/lib/phonebook/ui/pages/main_page/main_page.dart b/lib/phonebook/ui/pages/main_page/main_page.dart index 892d1af527..3e6a1c513b 100644 --- a/lib/phonebook/ui/pages/main_page/main_page.dart +++ b/lib/phonebook/ui/pages/main_page/main_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:heroicons/heroicons.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/admin/providers/is_admin_provider.dart'; import 'package:titan/advert/ui/components/special_action_button.dart'; import 'package:titan/phonebook/providers/association_filtered_list_provider.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; @@ -23,8 +22,7 @@ class PhonebookMainPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); - final isAdmin = ref.watch(isAdminProvider); + final adminAcces = ref.watch(hasPhonebookAdminAccessProvider); final associationListNotifier = ref.watch(associationListProvider.notifier); final associationList = ref.watch(associationListProvider); final associationGroupementList = ref.watch( @@ -55,7 +53,7 @@ class PhonebookMainPage extends HookConsumerWidget { Row( children: [ Expanded(child: AssociationResearchBar()), - if (isPhonebookAdmin || isAdmin) ...[ + if (adminAcces) ...[ SizedBox(width: 10), SpecialActionButton( icon: HeroIcon(HeroIcons.userGroup, color: Colors.white), diff --git a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart index 86eeba0b48..48c8818f82 100644 --- a/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart +++ b/lib/phonebook/ui/pages/membership_editor_page/membership_editor_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/phonebook/class/membership.dart'; +import 'package:titan/phonebook/providers/association_groupement_list_provider.dart'; import 'package:titan/phonebook/providers/association_member_list_provider.dart'; import 'package:titan/phonebook/providers/association_provider.dart'; import 'package:titan/phonebook/providers/membership_provider.dart'; @@ -39,6 +40,10 @@ class MembershipEditorPage extends HookConsumerWidget { ); final associationMembers = ref.watch(associationMemberListProvider); final isPhonebookAdmin = ref.watch(isPhonebookAdminProvider); + final groupementAdminProviderList = ref.watch(groupementAdminProvider); + final isGroupementAdmin = groupementAdminProviderList.any( + (groupement) => groupement.id == association.groupementId, + ); void displayToastWithContext(TypeMsg type, String msg) { displayToast(context, type, msg); @@ -183,7 +188,10 @@ class MembershipEditorPage extends HookConsumerWidget { .map( (tag) => ToggleListItem( title: tag, - onTap: tagList.first == tag && !isPhonebookAdmin + onTap: + tagList.first == tag && + !isPhonebookAdmin && + !isGroupementAdmin ? () {} : () { final tags = [...selectedTags.value]; diff --git a/lib/phonebook/ui/phonebook.dart b/lib/phonebook/ui/phonebook.dart index 628d3c1a43..c7575b606d 100644 --- a/lib/phonebook/ui/phonebook.dart +++ b/lib/phonebook/ui/phonebook.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:qlevar_router/qlevar_router.dart'; import 'package:titan/phonebook/providers/association_groupement_provider.dart'; import 'package:titan/phonebook/router.dart'; -import 'package:titan/tools/constants.dart'; import 'package:titan/tools/ui/widgets/top_bar.dart'; +import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/tools/constants.dart'; class PhonebookTemplate extends HookConsumerWidget { final Widget child; diff --git a/lib/purchases/class/product.dart b/lib/purchases/class/product.dart index 3aa82361d7..5c3ef4cd16 100644 --- a/lib/purchases/class/product.dart +++ b/lib/purchases/class/product.dart @@ -8,6 +8,7 @@ class Product { required this.descriptionFR, required this.descriptionEN, required this.ticketGenerators, + required this.year, }); late final String id; @@ -16,6 +17,7 @@ class Product { late final String? descriptionFR; late final String? descriptionEN; late final List ticketGenerators; + late final int year; Product.fromJson(Map json) { id = json['id']; @@ -26,6 +28,7 @@ class Product { ticketGenerators = List.from( (json['tickets'] as List).map((x) => TicketGenerator.fromJson(x)), ); + year = json['year']; } Map toJson() { @@ -36,6 +39,7 @@ class Product { 'description_fr': descriptionFR, 'description_en': descriptionEN, 'tickets': ticketGenerators.map((x) => x.toJson()).toList(), + 'year': year, }; return data; } @@ -47,6 +51,7 @@ class Product { String? descriptionFR, String? descriptionEN, List? ticketGenerators, + int? year, }) { return Product( id: id ?? this.id, @@ -55,6 +60,7 @@ class Product { descriptionFR: descriptionFR ?? this.descriptionFR, descriptionEN: descriptionEN ?? this.descriptionEN, ticketGenerators: ticketGenerators ?? this.ticketGenerators, + year: year ?? this.year, ); } @@ -65,10 +71,11 @@ class Product { descriptionFR = ""; descriptionEN = ""; ticketGenerators = []; + year = 1970; } @override String toString() { - return 'Product(id: $id, nameFR: $nameFR, nameEN: $nameEN, descriptionFR: $descriptionFR, descriptionEN: $descriptionEN, ticketGenerators: $ticketGenerators)'; + return 'Product(id: $id, nameFR: $nameFR, nameEN: $nameEN, descriptionFR: $descriptionFR, descriptionEN: $descriptionEN, ticketGenerators: $ticketGenerators, year: $year)'; } } diff --git a/lib/purchases/providers/purchase_list_provider.dart b/lib/purchases/providers/purchase_list_provider.dart index 3ad91fb6f3..c8db7d67fd 100644 --- a/lib/purchases/providers/purchase_list_provider.dart +++ b/lib/purchases/providers/purchase_list_provider.dart @@ -24,13 +24,13 @@ class PurchaseListNotifier extends ListNotifier { orElse: () => [], data: (value) { for (Purchase purchase in value) { - if (!years.contains(purchase.purchasedOn.year)) { - years.add(purchase.purchasedOn.year); + if (!years.contains(purchase.product.year)) { + years.add(purchase.product.year); } } }, ); - return years; + return years.reversed.toList(); } } diff --git a/lib/purchases/repositories/user_purchase_repository.dart b/lib/purchases/repositories/user_purchase_repository.dart index 4406e3a825..a9d4e708d1 100644 --- a/lib/purchases/repositories/user_purchase_repository.dart +++ b/lib/purchases/repositories/user_purchase_repository.dart @@ -8,7 +8,7 @@ class UserPurchaseRepository extends Repository { Future> getPurchaseList() async { return List.from( - (await getList(suffix: "purchases/")).map((x) => Purchase.fromJson(x)), + (await getList(suffix: "purchases/all")).map((x) => Purchase.fromJson(x)), ); } } diff --git a/lib/purchases/ui/pages/history_page/history_page.dart b/lib/purchases/ui/pages/history_page/history_page.dart index a75b81d0ff..d3b74868bd 100644 --- a/lib/purchases/ui/pages/history_page/history_page.dart +++ b/lib/purchases/ui/pages/history_page/history_page.dart @@ -37,7 +37,7 @@ class HistoryPage extends HookConsumerWidget { List years = purchasesListNotifier.getPurchasesYears(); children.addAll( purchases.map((purchase) { - if (purchase.purchasedOn.year == selectedYear.value) { + if (purchase.product.year == selectedYear.value) { return PurchaseCard( purchase: purchase, onClicked: () { diff --git a/lib/purchases/ui/pages/scan_page/qr_code_scanner.dart b/lib/purchases/ui/pages/scan_page/qr_code_scanner.dart index ae1c4b5ed8..0b2dfec656 100644 --- a/lib/purchases/ui/pages/scan_page/qr_code_scanner.dart +++ b/lib/purchases/ui/pages/scan_page/qr_code_scanner.dart @@ -10,7 +10,7 @@ class QRCodeScannerScreen extends StatefulWidget { required this.scanner, }); - final Function onScan; + final Function(String) onScan; final AsyncValue scanner; @override @@ -46,10 +46,13 @@ class QRCodeScannerScreenState extends State { ); }, onDetect: (BarcodeCapture capture) async { + final rawValue = capture.barcodes.firstOrNull?.rawValue; setState(() { - qrCode = capture.barcodes.first.rawValue; + qrCode = rawValue; }); - widget.onScan(qrCode!); + if (rawValue != null) { + widget.onScan(rawValue); + } }, ); } diff --git a/lib/purchases/ui/pages/scan_page/scan_dialog.dart b/lib/purchases/ui/pages/scan_page/scan_dialog.dart index 88d64a70cb..a209d90d6b 100644 --- a/lib/purchases/ui/pages/scan_page/scan_dialog.dart +++ b/lib/purchases/ui/pages/scan_page/scan_dialog.dart @@ -196,72 +196,68 @@ class ScanDialog extends HookConsumerWidget { ), ), const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - children: [ - GestureDetector( - onTap: () { - scannerNotifier.reset(); - }, - child: const SizedBox( - width: 100, - child: AddEditButtonLayout( - color: Colors.red, - child: Text( - "Annuler", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 20, + if (data.qrCodeSecret != "") + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + ), + child: Row( + children: [ + GestureDetector( + onTap: () { + scannerNotifier.reset(); + }, + child: const SizedBox( + width: 100, + child: AddEditButtonLayout( + color: Colors.red, + child: Text( + "Annuler", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), ), ), ), - ), - const Spacer(), - GestureDetector( - onTap: () async { - await tokenExpireWrapper(ref, () async { - final value = await ticketListNotifier - .consumeTicket( - sellerId, - data, - ticket.id, - tag, + const Spacer(), + GestureDetector( + onTap: () async { + await tokenExpireWrapper(ref, () async { + await (ticketListNotifier.consumeTicket( + sellerId, + data, + ticket.id, + tag == "" ? "no tag" : tag, + )).then((_) { + displayToastWithContext( + TypeMsg.msg, + "Scan validé", ); - if (value) { - displayToastWithContext( - TypeMsg.msg, - "Scan validé", - ); - scannerNotifier.reset(); - } else { - displayToastWithContext( - TypeMsg.error, - "Erreur lors de la validation", - ); - } - }); - }, - child: const SizedBox( - width: 100, - child: AddEditButtonLayout( - color: Colors.green, - child: Text( - "Valider", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 20, + scannerNotifier.reset(); + }); + }); + }, + child: const SizedBox( + width: 100, + child: AddEditButtonLayout( + color: Colors.green, + child: Text( + "Suivant", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), ), ), ), - ), - ], + ], + ), ), - ), ], ); }, diff --git a/lib/raffle/providers/is_raffle_admin.dart b/lib/raffle/providers/is_raffle_admin.dart index 8aca22f6d8..1e71bf087f 100644 --- a/lib/raffle/providers/is_raffle_admin.dart +++ b/lib/raffle/providers/is_raffle_admin.dart @@ -1,9 +1,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/raffle/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; -final isRaffleAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("0a25cb76-4b63-4fd3-b939-da6d9feabf28"); +final isRaffleAdminProvider = Provider((ref) { + return hasUserPermission(ref, RafflePermissionConstants.manageRaffle); +}); + +final isRaffleCashManagerProvider = Provider((ref) { + return hasUserPermission(ref, RafflePermissionConstants.manageCash); +}); + +final hasRaffleAdminAccessProvider = Provider((ref) { + final isRaffleAdmin = ref.watch(isRaffleAdminProvider); + final isRaffleCashManager = ref.watch(isRaffleCashManagerProvider); + return isRaffleAdmin || isRaffleCashManager; }); diff --git a/lib/raffle/router.dart b/lib/raffle/router.dart index 600bfcb165..0685a4c8fa 100644 --- a/lib/raffle/router.dart +++ b/lib/raffle/router.dart @@ -52,7 +52,7 @@ class RaffleRouter { path: admin, builder: () => admin_module_page.AdminModulePage(), middleware: [ - AdminMiddleware(ref, isRaffleAdminProvider), + AdminMiddleware(ref, hasRaffleAdminAccessProvider), DeferredLoadingMiddleware(admin_module_page.loadLibrary), ], ), diff --git a/lib/raffle/tools/constants.dart b/lib/raffle/tools/constants.dart index 64eea5565f..2ec7e90377 100644 --- a/lib/raffle/tools/constants.dart +++ b/lib/raffle/tools/constants.dart @@ -15,3 +15,9 @@ class RaffleColorConstants extends ColorConstants { static const Color redGradient3 = Color.fromARGB(255, 255, 34, 34); static const Color ticketBack = Color(0xff000031); } + +class RafflePermissionConstants { + static const String accessRaffle = "access_raffle"; + static const String manageRaffle = "manage_raffle"; + static const String manageCash = "manage_cash"; +} diff --git a/lib/recommendation/providers/is_recommendation_admin_provider.dart b/lib/recommendation/providers/is_recommendation_admin_provider.dart index 391e7acaed..358b4e5a6a 100644 --- a/lib/recommendation/providers/is_recommendation_admin_provider.dart +++ b/lib/recommendation/providers/is_recommendation_admin_provider.dart @@ -1,9 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/recommendation/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; final isRecommendationAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("389215b2-ea45-4991-adc1-4d3e471541cf"); // admin_recommandation + return hasUserPermission( + ref, + RecommendationPermissionConstants.manageRecommendations, + ); }); diff --git a/lib/recommendation/tools/constants.dart b/lib/recommendation/tools/constants.dart index 8b13789179..c270753d38 100644 --- a/lib/recommendation/tools/constants.dart +++ b/lib/recommendation/tools/constants.dart @@ -1 +1,4 @@ - +class RecommendationPermissionConstants { + static const String accessRecommendations = "access_recommendation"; + static const String manageRecommendations = "manage_recommendation"; +} diff --git a/lib/router.dart b/lib/router.dart index fbb4d55e90..323a19b6a2 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -20,8 +20,9 @@ import 'package:titan/others/ui/no_internet_page.dart' import 'package:titan/navigation/ui/all_module_page.dart' deferred as all_module_page; import 'package:titan/others/ui/no_module.dart' deferred as no_module_page; +import 'package:titan/others/ui/rollback_page.dart' deferred as rollback_page; import 'package:titan/others/ui/update_page.dart' deferred as update_page; -import 'package:titan/paiement/router.dart'; +import 'package:titan/mypayment/router.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/ph/router.dart'; import 'package:titan/purchases/router.dart'; @@ -36,6 +37,7 @@ import 'package:titan/tools/ui/styleguide/router.dart'; import 'package:titan/vote/router.dart'; import 'package:qlevar_router/qlevar_router.dart'; +import 'package:titan/centralassociation/router.dart'; final appRouterProvider = Provider((ref) => AppRouter(ref)); @@ -48,6 +50,7 @@ class AppRouter { static const String noInternet = '/no_internet'; static const String noModule = '/no_module'; static const String allModules = '/all_modules'; + static const String rollback = '/rollback'; AppRouter(this.ref) { routes = [ @@ -91,10 +94,17 @@ class AppRouter { FadeTransition(opacity: animation, child: child), ), ), + QRoute( + path: rollback, + builder: () => rollback_page.RollbackPage(), + middleware: [DeferredLoadingMiddleware(rollback_page.loadLibrary)], + ), + AdminRouter(ref).route(), AdvertRouter(ref).route(), AmapRouter(ref).route(), BookingRouter(ref).route(), CentralisationRouter(ref).route(), + CentralassociationRouter(ref).route(), CinemaRouter(ref).route(), EventRouter(ref).route(), FlappyBirdRouter(ref).route(), diff --git a/lib/seed-library/providers/is_seed_library_admin_provider.dart b/lib/seed-library/providers/is_seed_library_admin_provider.dart index 8561852d46..7a2b5ef6fa 100644 --- a/lib/seed-library/providers/is_seed_library_admin_provider.dart +++ b/lib/seed-library/providers/is_seed_library_admin_provider.dart @@ -1,9 +1,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/seed-library/tools/constants.dart'; +import 'package:titan/tools/functions.dart'; final isSeedLibraryAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("09153d2a-14f4-49a4-be57-5d0f265261b9"); // admin_seed_library + return hasUserPermission( + ref, + SeedLibraryPermissionConstants.manageSeedLibrary, + ); }); diff --git a/lib/seed-library/tools/constants.dart b/lib/seed-library/tools/constants.dart index 2ab55152f6..2e1230b3d1 100644 --- a/lib/seed-library/tools/constants.dart +++ b/lib/seed-library/tools/constants.dart @@ -142,3 +142,8 @@ class SeedLibraryColorConstants { static const Color greenGradient1 = Color(0xff79a400); static const Color textDark = Color(0xff255000); } + +class SeedLibraryPermissionConstants { + static const String accessSeedLibrary = "access_seed_library"; + static const String manageSeedLibrary = "manage_seed_library"; +} diff --git a/lib/settings/providers/module_list_provider.dart b/lib/settings/providers/module_list_provider.dart index 542f7cdf4c..cd443dfbb8 100644 --- a/lib/settings/providers/module_list_provider.dart +++ b/lib/settings/providers/module_list_provider.dart @@ -1,38 +1,59 @@ +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/admin/router.dart'; import 'package:titan/admin/providers/is_admin_provider.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; +import 'package:titan/admin/router.dart'; import 'package:titan/advert/router.dart'; -import 'package:titan/super_admin/providers/all_my_module_roots_list_provider.dart'; import 'package:titan/amap/router.dart'; import 'package:titan/booking/router.dart'; import 'package:titan/centralisation/router.dart'; import 'package:titan/cinema/router.dart'; import 'package:titan/event/router.dart'; +import 'package:titan/feed/router.dart'; import 'package:titan/loan/router.dart'; +import 'package:titan/mypayment/router.dart'; import 'package:titan/navigation/class/module.dart'; -import 'package:collection/collection.dart'; -import 'package:titan/home/router.dart'; -import 'package:titan/paiement/router.dart'; +import 'package:titan/phonebook/router.dart'; import 'package:titan/ph/router.dart'; import 'package:titan/phonebook/router.dart'; import 'package:titan/purchases/router.dart'; import 'package:titan/raffle/router.dart'; import 'package:titan/recommendation/router.dart'; import 'package:titan/seed-library/router.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:titan/settings/router.dart'; import 'package:titan/super_admin/providers/is_super_admin_provider.dart'; import 'package:titan/super_admin/router.dart'; +import 'package:titan/user/providers/user_provider.dart'; import 'package:titan/vote/router.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:titan/centralassociation/router.dart'; final modulesProvider = StateNotifierProvider>(( ref, ) { - final myModulesRoot = ref - .watch(allMyModuleRootList) - .map((root) => '/$root') - .toList(); + final me = ref.watch(userProvider); + final modulesPermissionNames = ref.watch(moduleGroupedPermissionsProvider); + final permissions = ref.watch(mappedPermissionsProvider); + List myModulesRoot = []; + for (String module in modulesPermissionNames.keys) { + final accessPermissions = modulesPermissionNames[module]!.firstWhere( + (p) => p.startsWith("access_"), + orElse: () => "", + ); + if (accessPermissions != "") { + final hasAccess = + me.groups.any( + (g) => permissions[accessPermissions]!.authorizedGroupIds.contains( + g.id, + ), + ) || + permissions[accessPermissions]!.authorizedAccountTypes.contains( + me.accountType.type, + ); + if (hasAccess) myModulesRoot.add("/$module"); + } + } final isAdmin = ref.watch(isAdminProvider); final isSuperAdmin = ref.watch(isSuperAdminProvider); @@ -52,11 +73,12 @@ class ModulesNotifier extends StateNotifier> { final bool isSuperAdmin; final eq = const DeepCollectionEquality.unordered(); List allModules = [ - HomeRouter.module, + FeedRouter.module, AdvertRouter.module, AmapRouter.module, BookingRouter.module, CentralisationRouter.module, + CentralassociationRouter.module, CinemaRouter.module, EventRouter.module, LoanRouter.module, @@ -102,6 +124,7 @@ class ModulesNotifier extends StateNotifier> { Future loadModules(List roots) async { final prefs = await SharedPreferences.getInstance(); + await prefs.reload(); List modulesName = prefs.getStringList(dbModule) ?? []; List allSavedModulesName = prefs.getStringList(dbAllModules) ?? []; final allModulesName = allModules.map((e) => e.root.toString()).toList(); diff --git a/lib/settings/tools/functions.dart b/lib/settings/tools/functions.dart index 47bf664eed..6167b769f9 100644 --- a/lib/settings/tools/functions.dart +++ b/lib/settings/tools/functions.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:titan/settings/class/notification_topic.dart'; import 'package:titan/settings/providers/module_list_provider.dart'; +import 'package:url_launcher/url_launcher.dart'; Map> groupNotificationTopicsByModuleRoot( List topics, @@ -37,3 +38,11 @@ Map> groupNotificationTopicsByModuleRoot( return result; } + +void openLink(String url) async { + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url), mode: LaunchMode.inAppBrowserView); + } else { + throw 'Unable to open: $url'; + } +} diff --git a/lib/super_admin/class/module_visibility.dart b/lib/super_admin/class/module_visibility.dart deleted file mode 100644 index b4f8452b8d..0000000000 --- a/lib/super_admin/class/module_visibility.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:titan/super_admin/class/account_type.dart'; - -class ModuleVisibility { - ModuleVisibility({ - required this.root, - required this.allowedGroupIds, - required this.allowedAccountTypes, - }); - late final String root; - late final List allowedGroupIds; - late final List allowedAccountTypes; - - ModuleVisibility.fromJson(Map json) { - root = json['root']; - allowedGroupIds = List.from(json['allowed_group_ids']); - allowedAccountTypes = List.from( - json['allowed_account_types'], - ).map((x) => AccountType(type: x)).toList(); - } - - Map toJson() { - final data = {}; - data['root'] = root; - data['allowed_group_ids'] = allowedGroupIds; - data['allowed_account_types'] = allowedAccountTypes; - return data; - } - - ModuleVisibility copyWith({ - String? root, - List? allowedGroupIds, - List? allowedAccountTypes, - }) => ModuleVisibility( - root: root ?? this.root, - allowedGroupIds: allowedGroupIds ?? this.allowedGroupIds, - allowedAccountTypes: allowedAccountTypes ?? this.allowedAccountTypes, - ); - - ModuleVisibility.empty() { - root = ''; - allowedGroupIds = []; - allowedAccountTypes = []; - } - - @override - String toString() { - return 'ModuleVisibility(root: $root, allowedGroupIds: $allowedGroupIds, allowedAccounTypes: $allowedAccountTypes)'; - } -} diff --git a/lib/super_admin/providers/all_my_module_roots_list_provider.dart b/lib/super_admin/providers/all_my_module_roots_list_provider.dart deleted file mode 100644 index f9a90bd7fd..0000000000 --- a/lib/super_admin/providers/all_my_module_roots_list_provider.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/providers/module_root_list_provider.dart'; - -final allMyModuleRootList = Provider>((ref) { - return ref - .watch(moduleRootListProvider) - .maybeWhen(data: (data) => data, orElse: () => []); -}); diff --git a/lib/super_admin/providers/is_expanded_list_provider.dart b/lib/super_admin/providers/is_expanded_list_provider.dart index c2297526ac..d835478325 100644 --- a/lib/super_admin/providers/is_expanded_list_provider.dart +++ b/lib/super_admin/providers/is_expanded_list_provider.dart @@ -1,10 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/module_visibility.dart'; -import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; +import 'package:titan/admin/providers/permission_name_list_provider.dart'; class IsExpandedListProvider extends StateNotifier> { - IsExpandedListProvider(List modules) - : super(List.generate(modules.length, (index) => false)); + IsExpandedListProvider(List permissionsNames) + : super(List.generate(permissionsNames.length, (index) => false)); void toggle(int i) { var copy = state.toList(); @@ -15,7 +14,7 @@ class IsExpandedListProvider extends StateNotifier> { final isExpandedListProvider = StateNotifierProvider>((ref) { - final modules = ref.read(moduleVisibilityListProvider); + final modules = ref.read(permissionsNamesListProvider); return modules.maybeWhen( data: (data) => IsExpandedListProvider(data), orElse: () { diff --git a/lib/super_admin/providers/module_root_list_provider.dart b/lib/super_admin/providers/module_root_list_provider.dart deleted file mode 100644 index e55fce6485..0000000000 --- a/lib/super_admin/providers/module_root_list_provider.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/repositories/module_visibility_repository.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/tools/providers/list_notifier.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; -import 'package:titan/user/providers/user_provider.dart'; - -class ModuleListNotifier extends ListNotifier { - ModuleVisibilityRepository repository = ModuleVisibilityRepository(); - ModuleListNotifier({required String token}) - : super(const AsyncValue.loading()) { - repository.setToken(token); - } - - Future>> loadMyModuleRoots() async { - return await loadList(repository.getAccessibleModule); - } -} - -final moduleRootListProvider = - StateNotifierProvider>>((ref) { - final token = ref.watch(tokenProvider); - final userProvider = ref.watch(asyncUserProvider); - ModuleListNotifier notifier = ModuleListNotifier(token: token); - userProvider.maybeWhen( - data: (data) => tokenExpireWrapperAuth(ref, () async { - await notifier.loadMyModuleRoots(); - }), - orElse: () {}, - ); - return notifier; - }); diff --git a/lib/super_admin/providers/module_visibility_list_provider.dart b/lib/super_admin/providers/module_visibility_list_provider.dart deleted file mode 100644 index 5917c76b7e..0000000000 --- a/lib/super_admin/providers/module_visibility_list_provider.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/super_admin/class/module_visibility.dart'; -import 'package:titan/super_admin/repositories/module_visibility_repository.dart'; -import 'package:titan/auth/providers/openid_provider.dart'; -import 'package:titan/tools/providers/list_notifier.dart'; -import 'package:titan/tools/token_expire_wrapper.dart'; - -class ModuleVisibilityListNotifier extends ListNotifier { - ModuleVisibilityRepository repository = ModuleVisibilityRepository(); - ModuleVisibilityListNotifier({required String token}) - : super(const AsyncValue.loading()) { - repository.setToken(token); - } - - Future>> loadModuleVisibility() async { - return await loadList(repository.getModuleVisibilityList); - } - - Future addGroupToModule( - ModuleVisibility moduleVisibility, - String allowedGroupId, - ) async { - return await update( - (moduleVisibility) async => - repository.addGroupToModule(moduleVisibility.root, allowedGroupId), - (list, moduleVisibility) => list - ..[list.indexWhere((m) => m.root == moduleVisibility.root)] = - moduleVisibility, - moduleVisibility, - ); - } - - Future deleteGroupAccessForModule( - ModuleVisibility moduleVisibility, - String allowedGroupId, - ) async { - return await update( - (moduleVisibility) async => repository.deleteGroupAccessForModule( - moduleVisibility.root, - allowedGroupId, - ), - (list, moduleVisibility) => list - ..[list.indexWhere((m) => m.root == moduleVisibility.root)] = - moduleVisibility, - moduleVisibility, - ); - } - - Future addAccountTypeToModule( - ModuleVisibility moduleVisibility, - String allowedAccountType, - ) async { - return await update( - (moduleVisibility) async => repository.addAccountTypeToModule( - moduleVisibility.root, - allowedAccountType, - ), - (list, moduleVisibility) => list - ..[list.indexWhere((m) => m.root == moduleVisibility.root)] = - moduleVisibility, - moduleVisibility, - ); - } - - Future deleteAccountTypeAccessForModule( - ModuleVisibility moduleVisibility, - String allowedAccountType, - ) async { - return await update( - (moduleVisibility) async => repository.deleteAccountTypeAccessForModule( - moduleVisibility.root, - allowedAccountType, - ), - (list, moduleVisibility) => list - ..[list.indexWhere((m) => m.root == moduleVisibility.root)] = - moduleVisibility, - moduleVisibility, - ); - } - - void setState(List modules) { - state = AsyncValue.data(modules); - } -} - -final moduleVisibilityListProvider = - StateNotifierProvider< - ModuleVisibilityListNotifier, - AsyncValue> - >((ref) { - final token = ref.watch(tokenProvider); - ModuleVisibilityListNotifier notifier = ModuleVisibilityListNotifier( - token: token, - ); - tokenExpireWrapperAuth(ref, () async { - await notifier.loadModuleVisibility(); - }); - return notifier; - }); diff --git a/lib/super_admin/repositories/module_visibility_repository.dart b/lib/super_admin/repositories/module_visibility_repository.dart deleted file mode 100644 index 08fcfb1770..0000000000 --- a/lib/super_admin/repositories/module_visibility_repository.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:titan/super_admin/class/module_visibility.dart'; -import 'package:titan/tools/repository/repository.dart'; - -class ModuleVisibilityRepository extends Repository { - @override - // ignore: overridden_fields - final ext = "module-visibility/"; - - Future> getModuleVisibilityList() async { - return List.from( - (await getList()).map((x) => ModuleVisibility.fromJson(x)), - ); - } - - Future> getAccessibleModule() async { - return List.from(await getList(suffix: "me")); - } - - Future addGroupToModule(String root, String allowedGroupId) async { - await create({'root': root, 'allowed_group_id': allowedGroupId}); - return true; - } - - Future addAccountTypeToModule( - String root, - String allowedAccounTypes, - ) async { - await create({'root': root, 'allowed_account_type': allowedAccounTypes}); - return true; - } - - Future deleteGroupAccessForModule( - String root, - String allowedGroupId, - ) async { - return await delete("$root/groups/$allowedGroupId"); - } - - Future deleteAccountTypeAccessForModule( - String root, - String allowedAccounTypes, - ) async { - return await delete("$root/account-types/$allowedAccounTypes"); - } -} diff --git a/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart b/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart deleted file mode 100644 index 86809dbae9..0000000000 --- a/lib/super_admin/ui/pages/edit_module_visibility/edit_module_visibility.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/providers/all_account_types_list_provider.dart'; -import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; -import 'package:titan/super_admin/ui/admin.dart'; -import 'package:titan/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart'; -import 'package:titan/tools/constants.dart'; -import 'package:titan/tools/ui/builders/async_child.dart'; -import 'package:titan/tools/ui/widgets/loader.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class EditModulesVisibilityPage extends HookConsumerWidget { - const EditModulesVisibilityPage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final modulesProvider = ref.watch(moduleVisibilityListProvider); - final groups = ref.watch(allGroupList); - final accountTypes = ref.watch(allAccountTypes); - return SuperAdminTemplate( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 20), - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Column( - children: [ - SizedBox( - child: Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - AppLocalizations.of( - context, - )!.adminModifyModuleVisibility, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorConstants.gradient1, - ), - ), - ), - const SizedBox(height: 30), - AsyncChild( - value: modulesProvider, - builder: (context, modules) => - modules.isEmpty || - groups.isEmpty || - accountTypes.isEmpty - ? const Loader() - : ModulesExpansionPanel( - modules: modules, - accountTypes: accountTypes, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart b/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart deleted file mode 100644 index 7cee25b697..0000000000 --- a/lib/super_admin/ui/pages/edit_module_visibility/modules_expansion_panel.dart +++ /dev/null @@ -1,219 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:heroicons/heroicons.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:titan/super_admin/class/account_type.dart'; -import 'package:titan/super_admin/class/module_visibility.dart'; -import 'package:titan/super_admin/providers/all_account_types_list_provider.dart'; -import 'package:titan/admin/providers/all_groups_list_provider.dart'; -import 'package:titan/super_admin/providers/is_expanded_list_provider.dart'; -import 'package:titan/super_admin/providers/module_visibility_list_provider.dart'; -import 'package:titan/l10n/app_localizations.dart'; - -class ModulesExpansionPanel extends HookConsumerWidget { - final List modules; - final List accountTypes; - - const ModulesExpansionPanel({ - super.key, - required this.modules, - required this.accountTypes, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final modulesNotifier = ref.watch(moduleVisibilityListProvider.notifier); - final groups = ref.watch(allGroupList); - final accountTypes = ref.watch(allAccountTypes); - final isExpandedList = ref.watch(isExpandedListProvider); - final isExpandedListNotifier = ref.watch(isExpandedListProvider.notifier); - return ExpansionPanelList( - expansionCallback: (i, isOpen) { - isExpandedListNotifier.toggle(i); - }, - children: modules - .map( - (moduleVisibility) => ExpansionPanel( - canTapOnHeader: true, - isExpanded: isExpandedList[modules.indexOf(moduleVisibility)], - headerBuilder: (context, isOpen) => Container( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - moduleVisibility.root, - style: const TextStyle( - color: Color.fromARGB(255, 0, 0, 0), - fontSize: 22, - fontWeight: FontWeight.w800, - ), - textAlign: TextAlign.center, - ), - ), - body: Column( - children: [ - Column( - children: [ - const Divider(), - Text( - AppLocalizations.of(context)!.adminAccountTypes, - style: TextStyle( - color: Color.fromARGB(255, 0, 0, 0), - fontSize: 20, - fontWeight: FontWeight.w800, - ), - ), - ...accountTypes.map( - (accountType) => Container( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 20, - ), - child: Row( - children: [ - Text( - accountType.type, - style: const TextStyle( - color: Color.fromARGB(255, 0, 0, 0), - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - const Spacer(), - moduleVisibility.allowedAccountTypes.contains( - accountType, - ) - ? GestureDetector( - onTap: () async { - final newModuleVisibility = - moduleVisibility.copyWith( - allowedAccountTypes: - moduleVisibility - .allowedAccountTypes - .where( - (type) => - type != accountType, - ) - .toList(), - ); - await modulesNotifier - .deleteAccountTypeAccessForModule( - newModuleVisibility, - accountType.type, - ); - }, - child: const HeroIcon( - HeroIcons.eye, - size: 40, - ), - ) - : GestureDetector( - onTap: () async { - final newModuleVisibility = - moduleVisibility.copyWith( - allowedAccountTypes: - moduleVisibility - .allowedAccountTypes + - [accountType], - ); - await modulesNotifier - .addAccountTypeToModule( - newModuleVisibility, - accountType.type, - ); - }, - child: const HeroIcon( - HeroIcons.eyeSlash, - size: 40, - ), - ), - ], - ), - ), - ), - ], - ), - const Divider(), - Column( - children: [ - Text( - AppLocalizations.of(context)!.adminGroups, - style: const TextStyle( - color: Color.fromARGB(255, 0, 0, 0), - fontSize: 20, - fontWeight: FontWeight.w900, - ), - ), - ...groups.map( - (group) => Container( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 20, - ), - child: Row( - children: [ - Text( - group.name, - style: const TextStyle( - color: Color.fromARGB(255, 0, 0, 0), - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - const Spacer(), - moduleVisibility.allowedGroupIds.contains( - group.id, - ) - ? GestureDetector( - onTap: () async { - final newModuleVisibility = - moduleVisibility.copyWith( - allowedGroupIds: moduleVisibility - .allowedGroupIds - .where( - (groupId) => - groupId != group.id, - ) - .toList(), - ); - await modulesNotifier - .deleteGroupAccessForModule( - newModuleVisibility, - group.id, - ); - }, - child: const HeroIcon( - HeroIcons.eye, - size: 40, - ), - ) - : GestureDetector( - onTap: () async { - final newModuleVisibility = - moduleVisibility.copyWith( - allowedGroupIds: - moduleVisibility - .allowedGroupIds + - [group.id], - ); - await modulesNotifier.addGroupToModule( - newModuleVisibility, - group.id, - ); - }, - child: const HeroIcon( - HeroIcons.eyeSlash, - size: 40, - ), - ), - ], - ), - ), - ), - ], - ), - ], - ), - ), - ) - .toList(), - ); - } -} diff --git a/lib/tools/constants.dart b/lib/tools/constants.dart index 05dc3eae01..1968657f0b 100644 --- a/lib/tools/constants.dart +++ b/lib/tools/constants.dart @@ -19,13 +19,6 @@ class ColorConstants { static const Color mainBorder = Color(0xFF950303); } -const String previousEmailRegex = - r'^[\w\-.]*@((ecl\d{2})|(alternance\d{4})|(master)|(auditeur)).ec-lyon.fr$'; - -const String previousStaffEmailRegex = r'^[\w\-.]*@ec-lyon.fr$'; - -const String studentRegex = r'^[\w\-.]*@etu(-enise)?.ec-lyon.fr$'; - const String unableToOpen = 'Impossible d\'ouvrir le lien'; const int maxHyperionFileSize = 4194304; diff --git a/lib/tools/functions.dart b/lib/tools/functions.dart index 88809f8f5b..8ba89ae832 100644 --- a/lib/tools/functions.dart +++ b/lib/tools/functions.dart @@ -3,10 +3,21 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:titan/l10n/app_localizations.dart'; +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:heroicons/heroicons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; import 'package:titan/tools/constants.dart'; import 'package:titan/tools/plausible/plausible.dart'; +import 'package:titan/tools/repository/repository.dart'; +import 'package:titan/version/repositories/version_repository.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:toastification/toastification.dart'; +import 'package:titan/user/providers/user_provider.dart'; +import 'package:yaml/yaml.dart'; /// Parses CSV content with automatic separator detection /// Supports common separators: comma, semicolon, tab, pipe @@ -476,20 +487,18 @@ int generateIntFromString(String s) { return s.codeUnits.reduce((value, element) => value + 100 * element); } -bool isEmailInValid(String email) { - final regex = RegExp(previousEmailRegex); - return regex.hasMatch(email); -} - -bool isStudent(String email) { - final regex = RegExp(studentRegex); - return regex.hasMatch(email); +bool hasUserPermission(Ref ref, String permission) { + final me = ref.watch(userProvider); + final permissions = ref.watch(mappedPermissionsProvider); + return me.groups.any( + (g) => permissions[permission]!.authorizedGroupIds.contains(g.id), + ) || + permissions[permission]!.authorizedAccountTypes.contains( + me.accountType.type, + ); } -bool isNotStaff(String email) { - final regex = RegExp(previousStaffEmailRegex); - return !regex.hasMatch(email); -} +/// getAppFlavor and functions depending on it String getAppFlavor() { if (appFlavor != null) { @@ -549,6 +558,9 @@ String getTitanURL() { if (titanUrl.isEmpty) { throw StateError("Could not find TITAN_URL in config.json"); } + if (titanUrl[titanUrl.length - 1] != "/") { + throw StateError("TITAN_URL in config.json should end with a /"); + } return titanUrl; } @@ -588,3 +600,63 @@ String getAppName() { String getTitanLogo() { return "assets/images/logo_${getAppFlavor()}.png"; } + +/// Start of functions to choose back-end + +bool isVersionCompatible(String currentVersion, String minimalVersion) { + final [major, minor, patch] = currentVersion + .split('.') + .map(int.parse) + .toList(); + final [minimalMajor, minimalMinor, minimalPatch] = minimalVersion + .split('.') + .map(int.parse) + .toList(); + if (major < minimalMajor || + (major == minimalMajor && minor < minimalMinor) || + (major == minimalMajor && + minor == minimalMinor && + patch < minimalPatch)) { + return false; + } + return true; +} + +Future getMinimalHyperionVersion() async { + final String pubspecString = await rootBundle.loadString("pubspec.yaml"); + final YamlMap pubspec = loadYaml(pubspecString); + final String minimalHyperionVersion = pubspec["minimal_hyperion_version"]; + return minimalHyperionVersion; +} + +Future setHyperionAndGetVersion(String flavor) async { + final String? host = getTitanHost(); + if (host == null || host == "") { + throw StateError("Could not retrieve the base URL for the $flavor flavor"); + } + Repository.host = host; // set Titan's back-end + final String hyperionVersion = await VersionRepository().getVersion().then( + (value) => value.version, + ); + return hyperionVersion; +} + +Future setHyperionHost() async { + final String flavor = getAppFlavor(); + final String minimalHyperionVersion = await getMinimalHyperionVersion(); + + try { + if (!isVersionCompatible( + await setHyperionAndGetVersion(flavor), + minimalHyperionVersion, + )) { + if (flavor != "alpha") { + await setHyperionAndGetVersion("alpha"); + } + } + } catch (_) { + return; + } +} + +/// End of functions to choose back-end and functions depending on getAppFlavor diff --git a/lib/tools/middlewares/authenticated_middleware.dart b/lib/tools/middlewares/authenticated_middleware.dart index 4f4c54ce37..9a9d55a318 100644 --- a/lib/tools/middlewares/authenticated_middleware.dart +++ b/lib/tools/middlewares/authenticated_middleware.dart @@ -5,7 +5,10 @@ import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/feed/router.dart'; import 'package:titan/login/router.dart'; import 'package:titan/router.dart'; +import 'package:titan/settings/providers/module_list_provider.dart'; +import 'package:titan/tools/functions.dart'; import 'package:titan/tools/providers/path_forwarding_provider.dart'; +import 'package:titan/version/providers/minimal_hyperion_version_provider.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:titan/version/providers/version_verifier_provider.dart'; import 'package:qlevar_router/qlevar_router.dart'; @@ -26,6 +29,7 @@ class AuthenticatedMiddleware extends QMiddleware { final pathForwardingNotifier = ref.watch(pathForwardingProvider.notifier); final versionVerifier = ref.watch(versionVerifierProvider); final titanVersion = ref.watch(titanVersionProvider); + final minimalHyperionVersion = ref.watch(minimalHyperionVersionProvider); final isLoggedIn = ref.watch(isLoggedInProvider); final check = versionVerifier.whenData( (value) => value.minimalTitanVersion <= titanVersion, @@ -35,25 +39,45 @@ class AuthenticatedMiddleware extends QMiddleware { path != "/") { pathForwardingNotifier.forward(path); } + final isHyperionVersionCompatible = versionVerifier.whenData( + (value) => isVersionCompatible(value.version, minimalHyperionVersion), + ); return check.when( data: (value) { if (!value) { return AppRouter.update; } - if (!isLoggedIn) { - return LoginRouter.root; - } - if (!pathForwardingNotifier.state.isLoggedIn) { - pathForwardingNotifier.login(); - } - if (pathForwardingNotifier.state.path == "/") { - pathForwardingNotifier.forward(FeedRouter.root); - return FeedRouter.root; - } - if (pathForwardingNotifier.state.path != path) { - return pathForwardingNotifier.state.path; - } - return null; + return isHyperionVersionCompatible.when( + data: (value) { + if (!value) { + return AppRouter.rollback; + } + if (path == LoginRouter.root && + !pathForwardingNotifier.state.isLoggedIn && + !isLoggedIn) { + return null; + } + if (!isLoggedIn) { + return LoginRouter.root; + } + if (!pathForwardingNotifier.state.isLoggedIn) { + pathForwardingNotifier.login(); + } + if (pathForwardingNotifier.state.path == "/") { + if (modules.isEmpty) { + return AppRouter.noModule; + } + pathForwardingNotifier.forward(modules.first.root); + return modules.first.root; + } + if (pathForwardingNotifier.state.path != path) { + return pathForwardingNotifier.state.path; + } + return null; + }, + loading: () => AppRouter.loading, + error: (error, stack) => AppRouter.noInternet, + ); }, loading: () => AppRouter.loading, error: (error, stack) => AppRouter.noInternet, diff --git a/lib/tools/repository/csv_repository.dart b/lib/tools/repository/csv_repository.dart new file mode 100644 index 0000000000..cfdc84e0b7 --- /dev/null +++ b/lib/tools/repository/csv_repository.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:titan/tools/exception.dart'; +import 'package:titan/tools/logs/log.dart'; +import 'package:titan/tools/repository/repository.dart'; +import 'package:http/http.dart' as http; + +abstract class CsvRepository extends Repository { + static const String expiredTokenDetail = "Could not validate credentials"; + + Future getCsv(String id, {String suffix = ""}) async { + try { + final response = await http.get( + Uri.parse("${Repository.host}$ext$id$suffix"), + headers: headers, + ); + if (response.statusCode == 200) { + try { + return response.bodyBytes; + } catch (e) { + Repository.logger.writeLog( + Log( + message: "GET $ext$id$suffix\nError while decoding response", + level: LogLevel.error, + ), + ); + rethrow; + } + } else if (response.statusCode == 403) { + Repository.logger.writeLog( + Log( + message: + "GET $ext$id$suffix\n${response.statusCode} ${response.body}", + level: LogLevel.error, + ), + ); + String resp = utf8.decode(response.body.runes.toList()); + final decoded = json.decode(resp); + if (decoded["detail"] == expiredTokenDetail) { + throw AppException(ErrorType.tokenExpire, decoded["detail"]); + } else { + throw AppException(ErrorType.notFound, decoded["detail"]); + } + } else { + Repository.logger.writeLog( + Log( + message: + "GET $ext$id$suffix\n${response.statusCode} ${response.body}", + level: LogLevel.error, + ), + ); + throw AppException(ErrorType.notFound, response.body); + } + } on AppException { + rethrow; + } catch (e) { + Repository.logger.writeLog( + Log( + message: "GET $ext$id$suffix\nCould not load the csv", + level: LogLevel.error, + ), + ); + rethrow; + } + } +} diff --git a/lib/tools/repository/repository.dart b/lib/tools/repository/repository.dart index f5eb0f3693..01ae7ec3a1 100644 --- a/lib/tools/repository/repository.dart +++ b/lib/tools/repository/repository.dart @@ -4,13 +4,12 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:titan/tools/cache/cache_manager.dart'; import 'package:titan/tools/exception.dart'; -import 'package:titan/tools/functions.dart'; import 'package:titan/tools/logs/logger.dart'; abstract class Repository { - static final String host = getTitanHost(); + static String host = ""; // see lib/main.dart static const String expiredTokenDetail = "Could not validate credentials"; - final String ext = ""; + final ext = ""; final Map headers = { "Content-Type": "application/json; charset=UTF-8", "Accept": "application/json", @@ -229,10 +228,11 @@ abstract class Repository { } /// DELETE ext/id/suffix - Future delete(String tId, {String suffix = ""}) async { + Future delete(String tId, {String suffix = "", dynamic body}) async { final response = await http.delete( Uri.parse(host + ext + tId + suffix), headers: headers, + body: body != null ? jsonEncode(body) : null, ); if (response.statusCode == 204) { return true; diff --git a/lib/tools/ui/builders/async_child.dart b/lib/tools/ui/builders/async_child.dart index 74c0b69ab2..45a3692573 100644 --- a/lib/tools/ui/builders/async_child.dart +++ b/lib/tools/ui/builders/async_child.dart @@ -176,7 +176,12 @@ class Async4Children extends StatelessWidget { }); @override Widget build(BuildContext context) { - List listValues = [values.item1, values.item2, values.item3]; + List listValues = [ + values.item1, + values.item2, + values.item3, + values.item4, + ]; if (listValues.any((value) => value.hasError || value.isLoading)) { return handleLoadingAndError( listValues, diff --git a/lib/tools/ui/layouts/app_template.dart b/lib/tools/ui/layouts/app_template.dart index d9dfa6094b..3872dff50f 100644 --- a/lib/tools/ui/layouts/app_template.dart +++ b/lib/tools/ui/layouts/app_template.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:titan/auth/providers/openid_provider.dart'; import 'package:titan/navigation/ui/navigation_template.dart'; +import 'package:titan/drawer/ui/drawer_template.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/version/providers/minimal_hyperion_version_provider.dart'; import 'package:titan/version/providers/titan_version_provider.dart'; import 'package:titan/version/providers/version_verifier_provider.dart'; @@ -13,20 +16,32 @@ class AppTemplate extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final versionVerifier = ref.watch(versionVerifierProvider); final titanVersion = ref.watch(titanVersionProvider); + final minimalHyperionVersion = ref.watch(minimalHyperionVersionProvider); final isLoggedIn = ref.watch(isLoggedInProvider); final check = versionVerifier.whenData( (value) => value.minimalTitanVersion <= titanVersion, ); + final isHyperionVersionCompatible = versionVerifier.whenData( + (value) => isVersionCompatible(value.version, minimalHyperionVersion), + ); return check.maybeWhen( data: (value) { if (!value) { return child; } - if (!isLoggedIn) { - return child; - } - return NavigationTemplate(child: child); + return isHyperionVersionCompatible.maybeWhen( + data: (value) { + if (!value) { + return child; + } + if (!isLoggedIn) { + return child; + } + return NavigationTemplate(child: child); + }, + orElse: () => child, + ); }, orElse: () => child, ); diff --git a/lib/tools/ui/layouts/bottom_modal_template.dart b/lib/tools/ui/layouts/bottom_modal_template.dart new file mode 100644 index 0000000000..38c0725b37 --- /dev/null +++ b/lib/tools/ui/layouts/bottom_modal_template.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +enum BottomModalType { main, danger } + +class BottomModalTemplate extends StatelessWidget { + final Widget child; + final String title; + final String? description; + final List? actions; + final BottomModalType type; + final String? animationKey; + + const BottomModalTemplate({ + super.key, + required this.child, + this.type = BottomModalType.main, + this.animationKey, + required this.title, + this.description, + this.actions, + }); + + const BottomModalTemplate.danger({ + super.key, + required this.child, + this.animationKey, + required this.title, + this.description, + this.actions, + }) : type = BottomModalType.danger; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Container( + margin: const EdgeInsets.only(bottom: 12), + width: 120, + height: 4, + decoration: BoxDecoration( + color: ColorConstants.onTertiary, + borderRadius: BorderRadius.circular(2), + boxShadow: [ + BoxShadow( + color: ColorConstants.onTertiary.withAlpha(50), + blurRadius: 4, + spreadRadius: 1, + ), + ], + ), + ), + ), + Hero( + tag: animationKey ?? 'bottom_modal', + child: Container( + decoration: BoxDecoration( + color: type == BottomModalType.main + ? ColorConstants.background + : ColorConstants.main, + borderRadius: BorderRadius.vertical(top: Radius.circular(30)), + ), + padding: EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w900, + color: type == BottomModalType.main + ? ColorConstants.tertiary + : ColorConstants.background, + ), + ), + SizedBox(height: 20), + if (description != null) + Text( + description!, + style: TextStyle( + fontSize: 15, + color: type == BottomModalType.main + ? ColorConstants.tertiary + : ColorConstants.background, + ), + ), + child, + if (actions != null && actions!.isNotEmpty) + Column(children: actions!), + SizedBox(height: 20), + ], + ), + ), + ), + ], + ); + } +} + +Future showCustomBottomModal({ + required BuildContext context, + required Widget modal, + Function? onCloseCallback, +}) async { + await showModalBottomSheet( + elevation: 3, + backgroundColor: Colors.transparent, + isScrollControlled: true, + useRootNavigator: true, + context: context, + builder: (_) => modal, + ); +} diff --git a/lib/tools/ui/layouts/button.dart b/lib/tools/ui/layouts/button.dart new file mode 100644 index 0000000000..cdd4c08d9b --- /dev/null +++ b/lib/tools/ui/layouts/button.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:titan/tools/constants.dart'; + +enum ButtonType { main, danger, onDanger, secondary } + +class Button extends StatelessWidget { + final ButtonType type; + final String text; + final bool? disabled; + final double? fontSize; + final Function() onPressed; + + const Button({ + super.key, + this.type = ButtonType.main, + required this.text, + required this.onPressed, + this.disabled = false, + this.fontSize, + }); + + const Button.danger({ + super.key, + required this.text, + required this.onPressed, + this.disabled = false, + this.fontSize, + }) : type = ButtonType.danger; + + const Button.onDanger({ + super.key, + required this.text, + required this.onPressed, + this.disabled = false, + this.fontSize, + }) : type = ButtonType.onDanger; + + const Button.secondary({ + super.key, + required this.text, + required this.onPressed, + this.disabled = false, + this.fontSize, + }) : type = ButtonType.secondary; + + Color get backgroundColor { + switch (type) { + case ButtonType.main: + return ColorConstants.tertiary; + case ButtonType.danger: + return ColorConstants.main; + case ButtonType.onDanger: + return ColorConstants.onMain; + case ButtonType.secondary: + return ColorConstants.background; + } + } + + Color get borderColor { + switch (type) { + case ButtonType.main: + return ColorConstants.onTertiary; + case ButtonType.danger: + return ColorConstants.mainBorder; + case ButtonType.onDanger: + return ColorConstants.mainBorder; + case ButtonType.secondary: + return ColorConstants.onBackground; + } + } + + Color get textColor { + Color color; + switch (type) { + case ButtonType.main: + color = ColorConstants.background; + case ButtonType.onDanger: + color = ColorConstants.background; + case ButtonType.danger: + color = ColorConstants.background; + case ButtonType.secondary: + color = ColorConstants.tertiary; + } + if (disabled == true) { + return color.withAlpha(150); + } + return color; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: disabled == true ? null : onPressed, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: borderColor), + ), + child: Center( + child: Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + color: textColor, + fontSize: fontSize ?? 18, + fontWeight: FontWeight.w900, + ), + ), + ), + ), + ); + } +} diff --git a/lib/tools/ui/layouts/item_chip.dart b/lib/tools/ui/layouts/item_chip.dart index 7b3838e3f7..3d8f079777 100644 --- a/lib/tools/ui/layouts/item_chip.dart +++ b/lib/tools/ui/layouts/item_chip.dart @@ -3,11 +3,15 @@ import 'package:flutter/material.dart'; class ItemChip extends StatelessWidget { final bool selected; final Function()? onTap; + final Function()? onLongPress; final Widget child; + final bool vertical; const ItemChip({ super.key, this.selected = false, this.onTap, + this.onLongPress, + this.vertical = false, required this.child, }); @@ -15,14 +19,19 @@ class ItemChip extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: onTap, + onLongPress: onLongPress, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10.0), + width: vertical ? double.infinity : null, + margin: EdgeInsets.symmetric( + horizontal: vertical ? 0.0 : 10.0, + vertical: vertical ? 5.0 : 0.0, + ), padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30.0), color: selected ? Colors.black : Colors.grey.shade200, ), - child: child, + child: Center(child: child), ), ); } diff --git a/lib/user/providers/user_provider.dart b/lib/user/providers/user_provider.dart index 2eb81d26db..110ed14bae 100644 --- a/lib/user/providers/user_provider.dart +++ b/lib/user/providers/user_provider.dart @@ -31,18 +31,6 @@ class UserNotifier extends SingleNotifier { return await update(userRepository.updateMe, user); } - Future changePassword( - String oldPassword, - String newPassword, - User user, - ) async { - return await userRepository.changePassword( - oldPassword, - newPassword, - user.email, - ); - } - Future deletePersonal() async { return await userRepository.deletePersonalData(); } diff --git a/lib/user/repositories/user_repository.dart b/lib/user/repositories/user_repository.dart index f883b33e45..f8c412e5cd 100644 --- a/lib/user/repositories/user_repository.dart +++ b/lib/user/repositories/user_repository.dart @@ -42,26 +42,6 @@ class UserRepository extends Repository { return await update(nullTrimmedBody, "me"); } - Future createUser(User user) async { - return User.fromJson(await create(user)); - } - - Future changePassword( - String oldPassword, - String newPassword, - String mail, - ) async { - try { - return (await create({ - "old_password": oldPassword, - "new_password": newPassword, - "email": mail, - }, suffix: "change-password"))["success"]; - } catch (e) { - return false; - } - } - Future deletePersonalData() async { try { return await create({}, suffix: "me/ask-deletion"); diff --git a/lib/version/providers/minimal_hyperion_version_provider.dart b/lib/version/providers/minimal_hyperion_version_provider.dart new file mode 100644 index 0000000000..299205b2a1 --- /dev/null +++ b/lib/version/providers/minimal_hyperion_version_provider.dart @@ -0,0 +1,20 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class MinimalHyperionVersionNotifier extends StateNotifier { + late PackageInfo packageInfo; + MinimalHyperionVersionNotifier() : super(""); + + Future loadVersionFromStorage() async { + state = await getMinimalHyperionVersion(); + return state; + } +} + +final minimalHyperionVersionProvider = + StateNotifierProvider((ref) { + final notifier = MinimalHyperionVersionNotifier(); + notifier.loadVersionFromStorage(); + return notifier; + }); diff --git a/lib/vote/providers/can_vote_provider.dart b/lib/vote/providers/can_vote_provider.dart deleted file mode 100644 index 2236137947..0000000000 --- a/lib/vote/providers/can_vote_provider.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; -import 'package:titan/vote/providers/voter_list_provider.dart'; - -final canVoteProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - final votingGroupList = ref.watch(voterListProvider); - final myGroupIds = me.groups.map((e) => e.id).toList(); - return votingGroupList.maybeWhen( - data: (voters) => voters.any((e) => myGroupIds.contains(e.groupId)), - orElse: () => false, - ); -}); diff --git a/lib/vote/providers/is_vote_admin_provider.dart b/lib/vote/providers/is_vote_admin_provider.dart index 9f18dc731a..5ab5224b17 100644 --- a/lib/vote/providers/is_vote_admin_provider.dart +++ b/lib/vote/providers/is_vote_admin_provider.dart @@ -1,9 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:titan/user/providers/user_provider.dart'; +import 'package:titan/tools/functions.dart'; +import 'package:titan/vote/tools/constants.dart'; final isVoteAdminProvider = StateProvider((ref) { - final me = ref.watch(userProvider); - return me.groups - .map((e) => e.id) - .contains("2ca57402-605b-4389-a471-f2fea7b27db5"); // admin_vote + return hasUserPermission(ref, VotePermissionConstants.manageVotes); +}); + +final canVoteProvider = StateProvider((ref) { + return hasUserPermission(ref, VotePermissionConstants.vote); }); diff --git a/lib/vote/tools/constants.dart b/lib/vote/tools/constants.dart new file mode 100644 index 0000000000..2a8e7cbd94 --- /dev/null +++ b/lib/vote/tools/constants.dart @@ -0,0 +1,96 @@ +class VoteTextConstants { + static const String add = 'Ajouter'; + static const String addMember = 'Ajouter un membre'; + static const String addedPretendance = 'Liste ajoutée'; + static const String addedSection = 'Section ajoutée'; + static const String addingError = 'Erreur lors de l\'ajout'; + static const String addPretendance = 'Ajouter une liste'; + static const String addSection = 'Ajouter une section'; + static const String all = "Tous"; + static const String alreadyAddedMember = 'Membre déjà ajouté'; + static const String alreadyVoted = "Vote enregistré"; + static const String chooseList = 'Choisir une liste'; + static const String clear = 'Réinitialiser'; + static const String clearVotes = "Réinitialiser les votes"; + static const String closedVote = 'Votes clos'; + static const String closeVote = 'Fermer les votes'; + static const String confirmVote = 'Confirmer le vote'; + static const String countVote = 'Dépouiller les votes'; + static const String deletedAll = "Tout supprimé"; + static const String deletedPipo = "Listes pipos supprimées"; + static const String deletedSection = 'Section supprimée'; + static const String deleteAll = "Supprimer tout"; + static const String deleteAllDescription = + "Voulez-vous vraiment supprimer tout ?"; + static const String deletePipo = "Supprimer les listes pipos"; + static const String deletePipoDescription = + "Voulez-vous vraiment supprimer les listes pipos ?"; + static const String deletePretendance = 'Supprimer la liste'; + static const String deletePretendanceDesc = + 'Voulez-vous vraiment supprimer cette liste ?'; + static const String deleteSection = 'Supprimer la section'; + static const String deleteSectionDescription = + 'Voulez-vous vraiment supprimer cette section ?'; + static const String deletingError = 'Erreur lors de la suppression'; + static const String description = 'Description'; + static const String edit = 'Modifier'; + static const String editedPretendance = 'Liste modifiée'; + static const String editedSection = 'Section modifiée'; + static const String editingError = 'Erreur lors de la modification'; + static const String errorClosingVotes = + 'Erreur lors de la fermeture des votes'; + static const String errorCountingVotes = + 'Erreur lors du dépouillement des votes'; + static const String errorResetingVotes = + 'Erreur lors de la réinitialisation des votes'; + static const String errorOpeningVotes = + 'Erreur lors de l\'ouverture des votes'; + static const String incorrectOrMissingFields = + 'Champs incorrects ou manquants'; + static const String members = 'Membres'; + static const String name = 'Nom'; + static const String noPretendanceList = 'Aucune liste de prétendance'; + static const String noSection = 'Aucune section'; + static const String canNotVote = 'Vous ne pouvez pas voter'; + static const String noSectionList = 'Aucune section'; + static const String notOpenedVote = 'Vote non ouvert'; + static const String onGoingCount = 'Dépouillement en cours'; + static const String openVote = 'Ouvrir les votes'; + static const String pipo = "Pipo"; + static const String pretendance = 'Listes'; + static const String pretendanceDeleted = 'Prétendance supprimée'; + static const String pretendanceNotDeleted = 'Erreur lors de la suppression'; + static const String program = 'Programme'; + static const String publish = 'Publier'; + static const String publishVoteDescription = + 'Voulez-vous vraiment publier les votes ?'; + static const String resetedVotes = 'Votes réinitialisés'; + static const String resetVote = 'Réinitialiser les votes'; + static const String resetVoteDescription = "Que voulez-vous faire ?"; + static const String role = 'Rôle'; + static const String sectionDescription = 'Description de la section'; + static const String section = 'Section'; + static const String sectionName = 'Nom de la section'; + static const String seeMore = 'Voir plus'; + static const String selected = 'Sélectionné'; + static const String showVotes = 'Voir les votes'; + static const String vote = 'Vote'; + static const String voteError = 'Erreur lors de l\'enregistrement du vote'; + static const String voteFor = 'Voter pour '; + static const String voteNotStarted = 'Vote non ouvert'; + static const String voters = 'Groupes votants'; + static const String voteSuccess = 'Vote enregistré'; + static const String votes = 'Voix'; + static const String votesClosed = 'Votes clos'; + static const String votesCounted = 'Votes dépouillés'; + static const String votesOpened = 'Votes ouverts'; + static const String warning = "Attention"; + static const String warningMessage = + "La sélection ne sera pas sauvegardée.\nVoulez-vous continuer ?"; +} + +class VotePermissionConstants { + static const String accessVotes = "access_campaign"; + static const String manageVotes = "manage_campaign"; + static const String vote = "vote"; +} diff --git a/lib/vote/ui/pages/main_page/main_page.dart b/lib/vote/ui/pages/main_page/main_page.dart index 8ac787b965..0f52061497 100644 --- a/lib/vote/ui/pages/main_page/main_page.dart +++ b/lib/vote/ui/pages/main_page/main_page.dart @@ -8,7 +8,6 @@ import 'package:titan/tools/ui/widgets/admin_button.dart'; import 'package:titan/tools/ui/builders/async_child.dart'; import 'package:titan/tools/ui/layouts/refresher.dart'; import 'package:titan/vote/class/contender.dart'; -import 'package:titan/vote/providers/can_vote_provider.dart'; import 'package:titan/vote/providers/is_vote_admin_provider.dart'; import 'package:titan/vote/providers/contender_list_provider.dart'; import 'package:titan/vote/providers/contender_logo_provider.dart'; diff --git a/nginx.conf b/nginx.conf index c6154f0fab..a3330e786a 100644 --- a/nginx.conf +++ b/nginx.conf @@ -13,6 +13,27 @@ http { server_tokens off; keepalive_timeout 65; + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 9; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_min_length 512; + gzip_types + application/javascript + text/javascript + text/css + text/plain + font/woff2 + application/wasm + application/json + font/ttf + image/svg+xml; + gzip_static on; + gunzip on; + server { listen 80; @@ -40,6 +61,7 @@ http { location / { add_header Cache-Control 'no-store'; add_header Cache-Control 'no-cache'; + add_header Cache-Control 'must-revalidate'; expires 0; try_files $uri $uri/ /index.html; } diff --git a/pubspec.lock b/pubspec.lock index ddea27696e..ef341c0750 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -953,10 +953,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "023a71afb4d7cfb5529d0f2636aa8b43db66257905b9486d702085989769c5f2" + sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044 url: "https://pub.dev" source: hosted - version: "7.1.3" + version: "7.1.4" mocktail: dependency: "direct dev" description: @@ -1659,21 +1659,13 @@ packages: source: hosted version: "6.5.0" yaml: - dependency: transitive + dependency: "direct main" description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted version: "3.1.3" - zxcvbn: - dependency: "direct main" - description: - name: zxcvbn - sha256: "5d860ab87c0e7f295902697afd364aa722d89d4e5839e8800ad1b0faf3d63b08" - url: "https://pub.dev" - source: hosted - version: "1.0.0" sdks: - dart: ">=3.9.2 <4.0.0" - flutter: "3.38.9" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.6 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7073df148e..c9d2f357a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,9 +4,11 @@ description: Titan est le frontend d'une application de gestion de la vie associ publish_to: "none" version: 2.3.2+196 +minimal_hyperion_version: 5.0.2 + environment: - sdk: ^3.9.2 - flutter: 3.38.9 + sdk: ^3.10.0 + flutter: ^3.38.6 dependencies: app_links: ^6.4.0 @@ -72,7 +74,7 @@ dependencies: url_launcher: ^6.2.5 uuid: ^4.5.1 webview_flutter: ^4.10.0 - zxcvbn: ^1.0.0 + yaml: ^3.1.3 dev_dependencies: dependency_validator: ^5.0.2 @@ -84,6 +86,8 @@ dev_dependencies: flutter: assets: + - .env + - pubspec.yaml - assets/images/logo_prod.png - assets/images/logo_alpha.png - assets/images/logo_dev.png diff --git a/test/amap/amap_test.dart b/test/amap/amap_test.dart index ea484d8dd8..d3c89ea0f2 100644 --- a/test/amap/amap_test.dart +++ b/test/amap/amap_test.dart @@ -50,19 +50,23 @@ void main() { expect(newCash.balance, 1); newCash = cash.copyWith(user: SimpleUser.empty().copyWith(name: 'Name')); expect(newCash.user.name, 'Name'); + newCash = cash.copyWith(lastOrderDate: DateTime.parse('2025-01-01')); + expect(newCash.lastOrderDate, DateTime.parse("2025-01-01")); }); test('Should print properly', () async { - final cash = Cash.empty(); + final cash = Cash.empty().copyWith( + lastOrderDate: DateTime.parse('2025-01-01'), + ); expect( cash.toString(), - 'Cash{balance: 0.0, user: SimpleUser {name: Nom, firstname: Prénom, nickname: null, id: , accountType: external}}', + 'Cash{balance: 0, user: SimpleUser {name: Nom, firstname: Prénom, nickname: null, id: , accountType: external}, lastOrderDate: 2025-01-01 00:00:00.000}', ); }); test('Should parse a Cash from json', () async { final cash = Cash.fromJson({ - "balance": 0.0, + "balance": 0, "user": { "name": "Name", "firstname": "Firstname", @@ -77,16 +81,18 @@ void main() { "phone": "phone", "promo": null, }, + "last_order_date": "2025-01-01 00:00:00.000", }); expect(cash, isA()); expect(cash.balance, 0); + expect(cash.lastOrderDate, DateTime.parse('2025-01-01')); expect(cash.user.name, 'Name'); expect(cash.user.nickname, null); }); test('Should return correct json', () async { final cash = Cash.fromJson({ - "balance": 0.0, + "balance": 0, "user": { "name": "Name", "firstname": "Firstname", @@ -94,8 +100,12 @@ void main() { "id": "id", "account_type": "external", }, + "last_order_date": "2025-01-01T00:00:00.000Z", + }); + expect(cash.toJson(), { + "balance": 0, + "last_order_date": "2025-01-01T00:00:00.000Z", }); - expect(cash.toJson(), {"balance": 0.0}); }); }); @@ -107,12 +117,13 @@ void main() { test('Should parse an Delivery from json', () async { final delivery = Delivery.fromJson({ + "name": "Livraison", "delivery_date": "2021-01-01", "products": [ { "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", "quantity": 0, }, @@ -138,29 +149,33 @@ void main() { expect(newDelivery.status, DeliveryStatus.delivered); newDelivery = delivery.copyWith(expanded: true); expect(newDelivery.expanded, true); + newDelivery = delivery.copyWith(name: "Livraison"); + expect(newDelivery.name, "Livraison"); }); test('Should print properly', () async { final delivery = Delivery.empty().copyWith( deliveryDate: DateTime.parse('2021-01-01'), + name: "Livraison", products: [Product.empty().copyWith(name: 'Name')], id: 'id', status: DeliveryStatus.creation, ); expect( delivery.toString(), - 'Delivery{deliveryDate: 2021-01-01 00:00:00.000, products: [Product{id: , name: Name, price: 0.0, quantity: 0, category: }], id: id, status: DeliveryStatus.creation, expanded: false}', + 'Delivery{name: Livraison, deliveryDate: 2021-01-01 00:00:00.000, products: [Product{id: , name: Name, price: 0, quantity: 0, category: }], id: id, status: DeliveryStatus.creation, expanded: false}', ); }); test('Should return correct json', () async { final delivery = Delivery.fromJson({ + "name": "Livraison", "delivery_date": "2021-01-01", "products": [ { "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", "quantity": 0, }, @@ -169,6 +184,7 @@ void main() { "status": "creation", }); expect(delivery.toJson(), { + "name": "Livraison", "delivery_date": "2021-01-01", "products_ids": ["id"], "id": "id", @@ -206,11 +222,14 @@ void main() { expect(newOrder.orderingDate, DateTime.parse('2021-01-01')); newOrder = order.copyWith(deliveryDate: DateTime.parse('2021-01-01')); expect(newOrder.deliveryDate, DateTime.parse('2021-01-01')); + newOrder = order.copyWith(deliveryName: "Livraison"); + expect(newOrder.deliveryName, "Livraison"); }); test('Should print properly', () async { final order = Order.empty().copyWith( id: 'id', + deliveryName: "Livraison", deliveryId: 'delivery_id', amount: 0, products: [ @@ -228,7 +247,7 @@ void main() { ); expect( order.toString(), - 'Order{id: id, orderingDate: 2021-01-01 00:00:00.000, deliveryDate: 2021-01-01 00:00:00.000, productsDetail: [id], productsQuantity: [0], deliveryId: delivery_id, products: [Product{id: id, name: name, price: 0.0, quantity: 0, category: }], amount: 0.0, lastAmount: 0.0, collectionSlot: CollectionSlot.midDay, user: SimpleUser {name: Name, firstname: Prénom, nickname: null, id: , accountType: external}, expanded: false}', + 'Order{id: id, deliveryName: Livraison, orderingDate: 2021-01-01 00:00:00.000, deliveryDate: 2021-01-01 00:00:00.000, productsDetail: [id], productsQuantity: [0], deliveryId: delivery_id, products: [Product{id: id, name: name, price: 0, quantity: 0, category: }], amount: 0, lastAmount: 0, collectionSlot: CollectionSlot.midDay, user: SimpleUser {name: Name, firstname: Prénom, nickname: null, id: , accountType: external}, expanded: false}', ); }); @@ -236,12 +255,13 @@ void main() { final order = Order.fromJson({ "order_id": "id", "delivery_id": "delivery_id", - "amount": 0.0, + "delivery_name": "Livraison", + "amount": 0, "productsdetail": [ { "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", "quantity": 0, }, @@ -265,12 +285,13 @@ void main() { final order = Order.fromJson({ "order_id": "id", "delivery_id": "delivery_id", - "amount": 0.0, + "delivery_name": "Livraison", + "amount": 0, "productsdetail": [ { "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", "quantity": 0, }, @@ -289,7 +310,8 @@ void main() { expect(order.toJson(), { "order_id": "id", "delivery_id": "delivery_id", - "amount": 0.0, + "delivery_name": "Livraison", + "amount": 0, "products_ids": ["id"], "products_quantity": [0], "collection_slot": "midi", @@ -361,7 +383,7 @@ void main() { final product = Product.fromJson({ "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", }); expect(product, isA()); @@ -373,7 +395,7 @@ void main() { "product": { "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", }, }); @@ -386,7 +408,7 @@ void main() { expect(newProduct.id, "id"); newProduct = product.copyWith(name: "name"); expect(newProduct.name, "name"); - newProduct = product.copyWith(price: 0.0); + newProduct = product.copyWith(price: 0); expect(newProduct.price, 0.0); newProduct = product.copyWith(category: "category"); expect(newProduct.category, "category"); @@ -398,13 +420,13 @@ void main() { final product = Product.empty().copyWith( id: "id", name: "name", - price: 0.0, + price: 0, category: "category", quantity: 0, ); expect( product.toString(), - 'Product{id: id, name: name, price: 0.0, quantity: 0, category: category}', + 'Product{id: id, name: name, price: 0, quantity: 0, category: category}', ); }); @@ -412,14 +434,14 @@ void main() { final product = Product.fromJson({ "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", "quantity": 0, }); expect(product.toJson(), { "id": "id", "name": "name", - "price": 0.0, + "price": 0, "category": "category", "quantity": 0, }); diff --git a/test/amap/is_amap_admin_provider_test.dart b/test/amap/is_amap_admin_provider_test.dart index 8ff091f076..25701e762e 100644 --- a/test/amap/is_amap_admin_provider_test.dart +++ b/test/amap/is_amap_admin_provider_test.dart @@ -1,7 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:titan/admin/class/permissions.dart'; import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; import 'package:titan/amap/providers/is_amap_admin_provider.dart'; +import 'package:titan/amap/tools/constants.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -21,6 +24,13 @@ void main() { ], ), ), + mappedPermissionsProvider.overrideWithValue({ + AMAPPermissionConstants.manageAMAP: CorePermission( + permissionName: AMAPPermissionConstants.manageAMAP, + authorizedGroupIds: ['70db65ee-d533-4f6b-9ffa-a4d70a17b7ef'], + authorizedAccountTypes: [], + ), + }), ], ); @@ -39,6 +49,13 @@ void main() { ], ), ), + mappedPermissionsProvider.overrideWithValue({ + AMAPPermissionConstants.manageAMAP: CorePermission( + permissionName: AMAPPermissionConstants.manageAMAP, + authorizedGroupIds: ['70db65ee-d533-4f6b-9ffa-a4d70a17b7ef'], + authorizedAccountTypes: [], + ), + }), ], ); diff --git a/test/amap/product_provider_test.dart b/test/amap/product_provider_test.dart index 4c2541a459..4bd05b573d 100644 --- a/test/amap/product_provider_test.dart +++ b/test/amap/product_provider_test.dart @@ -17,10 +17,7 @@ void main() { }); test('setProduct updates state', () { - final product = Product.empty().copyWith( - name: 'Test Product', - price: 10.0, - ); + final product = Product.empty().copyWith(name: 'Test Product', price: 10); productNotifier.setProduct(product); expect(productNotifier.state, product); }); @@ -42,10 +39,7 @@ void main() { test('setProduct updates product', () { final container = ProviderContainer(); - final product = Product.empty().copyWith( - name: 'Test Product', - price: 10.0, - ); + final product = Product.empty().copyWith(name: 'Test Product', price: 10); container.read(productProvider.notifier).setProduct(product); expect(container.read(productProvider), product); }); diff --git a/test/amap/user_amount_provider_test.dart b/test/amap/user_amount_provider_test.dart index bab5707229..49df6a8bae 100644 --- a/test/amap/user_amount_provider_test.dart +++ b/test/amap/user_amount_provider_test.dart @@ -20,7 +20,11 @@ void main() { group('loadCashByUser', () { test('returns cash for valid user id', () async { final user = SimpleUser.empty().copyWith(id: '123'); - final cash = Cash(balance: 100.0, user: user); + final cash = Cash( + balance: 100, + user: user, + lastOrderDate: DateTime(2025), + ); when( () => mockRepository.getCashByUser('123'), ).thenAnswer((_) async => cash); @@ -51,10 +55,14 @@ void main() { group('updateCash', () { test('updates cash balance', () async { - final cash = Cash(balance: 100.0, user: SimpleUser.empty()); + final cash = Cash( + balance: 100, + user: SimpleUser.empty(), + lastOrderDate: DateTime(2025), + ); notifier.state = AsyncValue.data(cash); - await notifier.updateCash(50.0); + await notifier.updateCash(50); expect( notifier.state.when( @@ -62,14 +70,14 @@ void main() { loading: () => 0.0, error: (error, stackTrace) => 0.0, ), - equals(150.0), + equals(150), ); }); test('returns error when loading', () async { notifier.state = const AsyncValue.loading(); - await notifier.updateCash(50.0); + await notifier.updateCash(50); expect( notifier.state, @@ -84,7 +92,7 @@ void main() { const error = 'User not found'; notifier.state = const AsyncValue.error(error, StackTrace.empty); - await notifier.updateCash(50.0); + await notifier.updateCash(50); expect(notifier.state.error, equals(error)); }); diff --git a/test/booking/is_booking_admin_provider_test.dart b/test/booking/is_booking_admin_provider_test.dart deleted file mode 100644 index 90906b759f..0000000000 --- a/test/booking/is_booking_admin_provider_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:titan/admin/class/simple_group.dart'; -import 'package:titan/booking/providers/is_admin_provider.dart'; -import 'package:titan/user/class/user.dart'; -import 'package:titan/user/providers/user_provider.dart'; - -void main() { - group('isBookingAdminProvider', () { - test('should return true if user is a booking admin', () { - final container = ProviderContainer( - overrides: [ - userProvider.overrideWithValue( - User.empty().copyWith( - groups: [ - SimpleGroup.empty().copyWith( - id: '0a25cb76-4b63-4fd3-b939-da6d9feabf28', - name: 'Booking Admin', - ), - SimpleGroup.empty().copyWith(id: '123', name: 'Other Group'), - ], - ), - ), - ], - ); - - final result = container.read(isAdminProvider); - - expect(result, true); - }); - - test('should return false if user is not a booking admin', () { - final container = ProviderContainer( - overrides: [ - userProvider.overrideWithValue( - User.empty().copyWith( - groups: [ - SimpleGroup.empty().copyWith(id: '123', name: 'Other Group'), - ], - ), - ), - ], - ); - - final result = container.read(isAdminProvider); - - expect(result, false); - }); - }); -} diff --git a/test/cinema/is_cinema_admin_test.dart b/test/cinema/is_cinema_admin_test.dart index dc728268c0..b62c1c5429 100644 --- a/test/cinema/is_cinema_admin_test.dart +++ b/test/cinema/is_cinema_admin_test.dart @@ -1,7 +1,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/class/permissions.dart'; import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; import 'package:titan/cinema/providers/is_cinema_admin.dart'; +import 'package:titan/cinema/tools/constants.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -20,6 +23,13 @@ void main() { ], ), ), + mappedPermissionsProvider.overrideWithValue({ + CinemaPermissionConstants.manageSessions: CorePermission( + permissionName: CinemaPermissionConstants.manageSessions, + authorizedGroupIds: ['ce5f36e6-5377-489f-9696-de70e2477300'], + authorizedAccountTypes: [], + ), + }), ], ); @@ -39,6 +49,13 @@ void main() { ], ), ), + mappedPermissionsProvider.overrideWithValue({ + CinemaPermissionConstants.manageSessions: CorePermission( + permissionName: CinemaPermissionConstants.manageSessions, + authorizedGroupIds: ['ce5f36e6-5377-489f-9696-de70e2477300'], + authorizedAccountTypes: [], + ), + }), ], ); diff --git a/test/event/is_admin_provider_test.dart b/test/event/is_admin_provider_test.dart index d0dfa85d8d..73234e3e32 100644 --- a/test/event/is_admin_provider_test.dart +++ b/test/event/is_admin_provider_test.dart @@ -1,7 +1,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:titan/admin/class/permissions.dart'; import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; import 'package:titan/event/providers/is_admin_provider.dart'; +import 'package:titan/event/tools/constants.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; @@ -21,6 +24,13 @@ void main() { ], ), ), + mappedPermissionsProvider.overrideWithValue({ + EventPermissionConstants.manageEvents: CorePermission( + permissionName: EventPermissionConstants.manageEvents, + authorizedGroupIds: ['53a669d6-84b1-4352-8d7c-421c1fbd9c6a'], + authorizedAccountTypes: [], + ), + }), ], ); @@ -37,6 +47,13 @@ void main() { groups: [SimpleGroup.empty().copyWith(id: '123', name: 'User')], ), ), + mappedPermissionsProvider.overrideWithValue({ + EventPermissionConstants.manageEvents: CorePermission( + permissionName: EventPermissionConstants.manageEvents, + authorizedGroupIds: ['70db65ee-d533-4f6b-9ffa-a4d70a17b7ef'], + authorizedAccountTypes: [], + ), + }), ], ); diff --git a/test/user/user_provider_test.dart b/test/user/user_provider_test.dart index cf08570043..96c7ca17c4 100644 --- a/test/user/user_provider_test.dart +++ b/test/user/user_provider_test.dart @@ -69,17 +69,6 @@ void main() { }); }); - group('changePassword', () { - test('should return true when password is changed successfully', () async { - final user = User.empty(); - when( - () => mockUserRepository.changePassword('old', 'new', user.email), - ).thenAnswer((_) async => true); - final result = await userNotifier.changePassword('old', 'new', user); - expect(result, true); - }); - }); - group('deletePersonal', () { test( 'should return true when personal data is deleted successfully', diff --git a/test/user/user_test.dart b/test/user/user_test.dart index bb17818ced..09f3985ac2 100644 --- a/test/user/user_test.dart +++ b/test/user/user_test.dart @@ -398,30 +398,4 @@ void main() { expect(await userNotifier.updateMe(newUser), false); }); }); - - group('Testing changePassword', () { - test('Should change password', () async { - final mockUser = MockUserRepository(); - final User user = User.empty(); - when(() => mockUser.getMe()).thenAnswer((_) async => User.empty()); - when( - () => mockUser.changePassword('old', 'new', user.email), - ).thenAnswer((_) async => true); - final UserNotifier userNotifier = UserNotifier(userRepository: mockUser); - await userNotifier.loadMe(); - expect(await userNotifier.changePassword('old', 'new', user), true); - }); - - test('Should catch error when changePassword fail', () async { - final mockUser = MockUserRepository(); - final User user = User.empty(); - when(() => mockUser.getMe()).thenAnswer((_) async => User.empty()); - when( - () => mockUser.changePassword('old', 'new', user.email), - ).thenAnswer((_) async => false); - final UserNotifier userNotifier = UserNotifier(userRepository: mockUser); - await userNotifier.loadMe(); - expect(await userNotifier.changePassword('old', 'new', user), false); - }); - }); } diff --git a/test/vote/is_vote_admin_provider_test.dart b/test/vote/is_vote_admin_provider_test.dart index 7977cafc2b..dfb850660c 100644 --- a/test/vote/is_vote_admin_provider_test.dart +++ b/test/vote/is_vote_admin_provider_test.dart @@ -1,9 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:titan/admin/class/permissions.dart'; import 'package:titan/admin/class/simple_group.dart'; +import 'package:titan/admin/providers/permissions_list_provider.dart'; import 'package:titan/user/class/user.dart'; import 'package:titan/user/providers/user_provider.dart'; import 'package:titan/vote/providers/is_vote_admin_provider.dart'; +import 'package:titan/vote/tools/constants.dart'; void main() { group('isVoteAdmin', () { @@ -19,6 +22,13 @@ void main() { ], ), ), + mappedPermissionsProvider.overrideWithValue({ + VotePermissionConstants.manageVotes: CorePermission( + permissionName: VotePermissionConstants.manageVotes, + authorizedGroupIds: ['6c6d7e88-fdb8-4e42-b2b5-3d3cfd12e7d6'], + authorizedAccountTypes: [], + ), + }), ], ); @@ -39,6 +49,13 @@ void main() { ], ), ), + mappedPermissionsProvider.overrideWithValue({ + VotePermissionConstants.manageVotes: CorePermission( + permissionName: VotePermissionConstants.manageVotes, + authorizedGroupIds: ['6c6d7e88-fdb8-4e42-b2b5-3d3cfd12e7d6'], + authorizedAccountTypes: [], + ), + }), ], ); diff --git a/web/index.html b/web/index.html index 12cc217d7f..7c286d2d2f 100644 --- a/web/index.html +++ b/web/index.html @@ -1,4 +1,4 @@ - + @@ -125,14 +125,17 @@ - @@ -160,7 +163,7 @@ application. For more information, see: https://developers.google.com/web/fundamentals/primers/service-workers -->