42 Commits

Author SHA1 Message Date
4e53481cdf fix(checklists): auto-dismiss item check snackbar after 6 seconds 2026-05-17 12:11:56 +03:00
116eb18693 fix(macos): esc to dismiss would ring bell and not work 2026-05-16 22:16:22 +03:00
bbdc71c72f build: add macos-deploy to deploy-* tasks 2026-05-16 19:03:16 +03:00
cf2388db2b docs(macos): update release notes 2026-05-16 19:02:59 +03:00
github-actions[bot]
9fdee69cb9 chore(master): release 0.13.0 2026-05-16 18:47:46 +03:00
4a0832a359 ci: update release changelog scopes 2026-05-16 01:36:54 +03:00
88e153b96f feat(macos): go back by using Esc key 2026-05-16 01:36:54 +03:00
cf9135e069 feat: switch between photos in photo view 2026-05-16 01:36:54 +03:00
db7cc6ffab chore(macos): app store preparations, ui adjustments 2026-05-16 01:36:53 +03:00
ec689d2940 ci: route fastlane changelog by commit scope 2026-05-15 23:51:58 +03:00
00241b8ace feat(ios): open full file picker when running on macOS 2026-05-15 23:51:58 +03:00
eb4d8d3c50 feat: add trash view 2026-05-15 23:22:58 +03:00
ddf0c365a1 feat: add undo action for checking items 2026-05-15 23:07:02 +03:00
eafc267e92 feat: improve ui for larger devices 2026-05-15 22:14:43 +03:00
291d8c3bb5 chore(macos): prepare macos compatibility 2026-05-15 21:56:22 +03:00
github-actions[bot]
903fd823d8 chore(master): release 0.12.0 2026-05-15 17:11:17 +03:00
bca375d701 ci: pin release builds to release tag 2026-05-15 17:06:44 +03:00
28f8a269f8 chore(ios): fix build settings 2026-05-15 17:06:44 +03:00
c944ec5140 build: update makefile targets 2026-05-15 01:58:05 +03:00
550027e1bc test: cover list creation, share queues, and photo FAB menu 2026-05-14 17:37:26 +03:00
41e8ac13a0 feat: create new lists from the list selector 2026-05-14 15:22:29 +03:00
d8802690c0 feat: take photos directly from the photo board 2026-05-14 15:15:36 +03:00
60b16aad30 feat: share photos, links, and text to Pantry from other apps 2026-05-14 14:48:57 +03:00
689e4d6cad feat: add setting to show spacing between categories in checklist items 2026-05-14 12:02:57 +03:00
db3bbc0f17 build: add ios submit-only fastlane lane 2026-05-12 12:18:06 +03:00
github-actions[bot]
504da80c09 chore(master): release 0.11.0 2026-05-12 12:00:27 +03:00
0c575eaa26 fix: preserve subpath for Nextcloud instances hosted on sub-paths 2026-05-12 11:56:59 +03:00
38b5d8b464 build: update makefile targets 2026-05-12 11:37:15 +03:00
ef2bc851de feat: add setting to require checkbox tap to complete checklist items 2026-05-12 11:37:15 +03:00
github-actions[bot]
448d85834b chore(master): release 0.10.1 2026-04-26 23:42:06 +03:00
7b5f9c1518 fix: make markdown links clickable 2026-04-26 22:43:52 +03:00
67581d04f0 build: update fastlane 2026-04-21 11:29:39 +03:00
be83067fb7 build: disable preview html for fastlane deliver 2026-04-21 11:18:18 +03:00
ea4590f0ed build: fix makefile syntax 2026-04-21 11:11:41 +03:00
github-actions[bot]
721f32e1ea chore(master): release 0.10.0 2026-04-21 10:08:29 +03:00
36a74b39e1 feat: update notes view & edit ui 2026-04-21 09:52:38 +03:00
08159faec2 fix: bug where note grid would not clip correctly 2026-04-21 09:38:41 +03:00
346bfb9d92 docs: add apple app store link 2026-04-20 09:57:20 +03:00
github-actions[bot]
0b9cda92ca chore(master): release 0.9.10 2026-04-19 17:25:15 +03:00
9f45b2344e build: fix signing
Release-As: 0.9.10
2026-04-19 17:14:18 +03:00
github-actions[bot]
4ae96c37d1 chore(master): release 0.9.9 2026-04-19 15:31:34 +03:00
852e9c47f3 build: re-sign with stripping
Release-As: 0.9.9
2026-04-19 15:28:18 +03:00
141 changed files with 6531 additions and 1014 deletions

View File

@@ -93,9 +93,14 @@ jobs:
uses: chenasraf/workflows/.github/workflows/release-please-fastlane-changelog.yml@master
with:
release-type: dart
fastlane-changelog-dirs: |
fastlane/metadata/android/en-US/changelogs
fastlane/metadata/ios/en-US/changelogs
scopes: |
ios: fastlane/metadata/ios/en-US/changelogs
macos: fastlane/metadata/macos/en-US/changelogs
desktop: fastlane/metadata/macos/en-US/changelogs
apple: |
fastlane/metadata/ios/en-US/changelogs
fastlane/metadata/macos/en-US/changelogs
android: fastlane/metadata/android/en-US/changelogs
build-android:
needs: [setup, release-please]
@@ -105,13 +110,15 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.release-please.outputs.tag_name }}
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
distribution: "temurin"
java-version: "17"
cache: "gradle"
- name: Setup Flutter
uses: subosito/flutter-action@v2
@@ -155,14 +162,6 @@ jobs:
- name: Build split APKs
run: flutter build apk --release --split-per-abi
- name: Strip dependency metadata from APKs
run: |
pip install apksigtool
APK_DIR=build/app/outputs/flutter-apk
for apk in $APK_DIR/app-*-release.apk; do
python3 -m apksigtool remove-signing-block --block-id 0x504b4453 "$apk"
done
- name: Build App Bundle
run: flutter build appbundle --release --obfuscate --split-debug-info=build/debug-info-aab
- name: Rename artifacts
@@ -198,6 +197,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.release-please.outputs.tag_name }}
- name: Setup Flutter
uses: subosito/flutter-action@v2

View File

@@ -1,5 +1,72 @@
# Changelog
## [0.13.0](https://github.com/chenasraf/pantry-flutter/compare/v0.12.0...v0.13.0) (2026-05-15)
### Features
* add trash view ([eb4d8d3](https://github.com/chenasraf/pantry-flutter/commit/eb4d8d3c509e6fe1c6ef998caaa58f6bd9a55a53))
* add undo action for checking items ([ddf0c36](https://github.com/chenasraf/pantry-flutter/commit/ddf0c365a1ea4fb27f66fc19a7c5ffd7d72f6de7))
* improve ui for larger devices ([eafc267](https://github.com/chenasraf/pantry-flutter/commit/eafc267e92b275796e1edb746c3c3b9dc75925e7))
* **ios:** open full file picker when running on macOS ([00241b8](https://github.com/chenasraf/pantry-flutter/commit/00241b8aceeda5175e0989f017031a94b6c53581))
* **macos:** go back by using Esc key ([88e153b](https://github.com/chenasraf/pantry-flutter/commit/88e153b96f249f4a8d8f19465a31cde7aa3ac8e0))
* switch between photos in photo view ([cf9135e](https://github.com/chenasraf/pantry-flutter/commit/cf9135e0695f70b8c8c658421306f6cda610b6b4))
## [0.12.0](https://github.com/chenasraf/pantry-flutter/compare/v0.11.0...v0.12.0) (2026-05-15)
### Features
* add setting to show spacing between categories in checklist items ([689e4d6](https://github.com/chenasraf/pantry-flutter/commit/689e4d6cad89a84621b77e300198d12ac43131ff))
* create new lists from the list selector ([41e8ac1](https://github.com/chenasraf/pantry-flutter/commit/41e8ac13a0ab78436bc2674220ed282f8410862d))
* share photos, links, and text to Pantry from other apps ([60b16aa](https://github.com/chenasraf/pantry-flutter/commit/60b16aad309ec0f02b542c53a7bfafb9f9652da3))
* take photos directly from the photo board ([d880269](https://github.com/chenasraf/pantry-flutter/commit/d8802690c0a3ab80346f84ea1df4b3774ba6e4ee))
## [0.11.0](https://github.com/chenasraf/pantry-flutter/compare/v0.10.1...v0.11.0) (2026-05-12)
### Features
* add setting to require checkbox tap to complete checklist items ([ef2bc85](https://github.com/chenasraf/pantry-flutter/commit/ef2bc851deedc180dda51fbfb7378aef7145cf5f))
### Bug Fixes
* preserve subpath for Nextcloud instances hosted on sub-paths ([0c575ea](https://github.com/chenasraf/pantry-flutter/commit/0c575eaa2601dc8c83c6b874f2d81b54a0f6bf01))
## [0.10.1](https://github.com/chenasraf/pantry-flutter/compare/v0.10.0...v0.10.1) (2026-04-26)
### Bug Fixes
* make markdown links clickable ([7b5f9c1](https://github.com/chenasraf/pantry-flutter/commit/7b5f9c151845dde90275a8289b3114483d2b214d))
## [0.10.0](https://github.com/chenasraf/pantry-flutter/compare/v0.9.10...v0.10.0) (2026-04-21)
### Features
* update notes view & edit ui ([36a74b3](https://github.com/chenasraf/pantry-flutter/commit/36a74b39e1beb37fc2f1446f8d45945a22289c6b))
### Bug Fixes
* bug where note grid would not clip correctly ([08159fa](https://github.com/chenasraf/pantry-flutter/commit/08159faec22422da983f23685c53b45088d74b2a))
## [0.9.10](https://github.com/chenasraf/pantry-flutter/compare/v0.9.9...v0.9.10) (2026-04-19)
### Build System
* fix signing ([9f45b23](https://github.com/chenasraf/pantry-flutter/commit/9f45b2344ef87708d55889e8fb80f465808fb0c7))
## [0.9.9](https://github.com/chenasraf/pantry-flutter/compare/v0.9.8...v0.9.9) (2026-04-19)
### Build System
* re-sign with stripping ([852e9c4](https://github.com/chenasraf/pantry-flutter/commit/852e9c47f3cf11145a72ed78b6415ecd0da2b111))
## [0.9.8](https://github.com/chenasraf/pantry-flutter/compare/v0.9.7...v0.9.8) (2026-04-19)

View File

@@ -8,8 +8,8 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1237.0)
aws-sdk-core (3.244.0)
aws-partitions (1.1246.0)
aws-sdk-core (3.246.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -17,19 +17,19 @@ GEM
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.123.0)
aws-sdk-kms (1.124.0)
aws-sdk-core (~> 3, >= 3.244.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.219.0)
aws-sdk-s3 (1.221.0)
aws-sdk-core (~> 3, >= 3.244.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (4.1.1)
bigdecimal (4.1.2)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
@@ -72,14 +72,14 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.1)
fastlane (2.232.2)
CFPropertyList (>= 2.3, < 4.0.0)
abbrev (~> 0.1.2)
fastlane (2.234.0)
CFPropertyList (>= 2.3, < 5.0.0)
abbrev (~> 0.1)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.197)
babosa (>= 1.0.3, < 2.0.0)
base64 (~> 0.2.0)
base64 (~> 0.2)
benchmark (>= 0.1.0)
bundler (>= 1.17.3, < 5.0.0)
colored (~> 1.2)
@@ -92,7 +92,7 @@ GEM
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0)
fastlane-sirp (>= 1.1.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
@@ -105,9 +105,9 @@ GEM
logger (>= 1.6, < 2.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
mutex_m (~> 0.3.0)
mutex_m (~> 0.3)
naturally (~> 2.2)
nkf (~> 0.2.0)
nkf (~> 0.2)
optparse (>= 0.1.1, < 1.0.0)
ostruct (>= 0.1.0)
plist (>= 3.1.0, < 4.0.0)
@@ -122,10 +122,9 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
fastlane-sirp (1.1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.98.0)
google-apis-androidpublisher_v3 (0.100.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1)
@@ -135,11 +134,11 @@ GEM
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
google-apis-iamcredentials_v1 (0.26.0)
google-apis-iamcredentials_v1 (0.27.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-playcustomapp_v1 (0.17.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.61.0)
google-apis-storage_v1 (0.62.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
@@ -147,7 +146,7 @@ GEM
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.6.0)
google-cloud-storage (1.59.0)
google-cloud-storage (1.60.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-core (>= 0.18, < 2)
@@ -169,13 +168,13 @@ GEM
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.19.3)
json (2.19.5)
jwt (2.10.2)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.19.1)
multi_json (1.21.1)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
@@ -186,7 +185,7 @@ GEM
ostruct (0.6.3)
plist (3.7.2)
public_suffix (7.0.5)
rake (13.3.1)
rake (13.4.2)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -205,7 +204,6 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
@@ -243,15 +241,15 @@ CHECKSUMS
artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263
atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
aws-partitions (1.1237.0) sha256=9b82f529b69ad83a8e4c5e123038924ed5e8f59bd6064a293ef20efc63364841
aws-sdk-core (3.244.0) sha256=3e458c078b0c5bdee95bc370c3a483374b3224cf730c1f9f0faf849a5d9a18ea
aws-sdk-kms (1.123.0) sha256=d405f37e82f8fa32045ca8980be266c0b45b37aaf2012afe0254321a1e811f20
aws-sdk-s3 (1.219.0) sha256=6a755d7377978525758b3c29185ca6a10128ce2b07555ca37c4549de10c2f1c7
aws-partitions (1.1246.0) sha256=809cd7d38b7ba4ea651c9879248ecf9fd0f8289412e76f26478d2b37064faa1d
aws-sdk-core (3.246.0) sha256=393864ec8948560e69fcccc2e4d256b40c7028eb98930608dd295279e3c4ddcc
aws-sdk-kms (1.124.0) sha256=40d00ab706d7e49fd620270bd0dcb546f266295abdd49b54fec2611e2a41f37c
aws-sdk-s3 (1.221.0) sha256=a05488eab2083a1e90b02e18479d8f65e401081d671b2d138992a2c5fef85491
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99
base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
bigdecimal (4.1.1) sha256=1c09efab961da45203c8316b0cdaec0ff391dfadb952dd459584b63ebf8054ca
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
@@ -277,29 +275,29 @@ CHECKSUMS
faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa
faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
fastlane (2.232.2) sha256=978689f60f0fc3d54699de86ef12be4eda9f5b52217c1798965257c390d2b112
fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641
fastlane (2.234.0) sha256=b74835681ad9a8e9c0931a5727dad1bab433895ac534c864a1ed5749625d26e9
fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847
gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
google-apis-androidpublisher_v3 (0.98.0) sha256=094fb952419c1131c16c4dfa66e0c96e6a2fa33adbe266f614b84b22cbc8c5cb
google-apis-androidpublisher_v3 (0.100.0) sha256=7a82935bee985190e8fe23bf5e53df3a27d65dd084114bb71b846b617de16489
google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee
google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3
google-apis-iamcredentials_v1 (0.27.0) sha256=9289f29968610754ef11d98b9ec627f0153f3e2616fef839aef096de529f6d1e
google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78
google-apis-storage_v1 (0.61.0) sha256=b330e599b58e6a01533c189525398d6dbdbaf101ffb0c60145940b57e1c982e8
google-apis-storage_v1 (0.62.0) sha256=f62467c36df53287fb0252ebb4da85f9e25d7b4c5809d045c2aab1fc307760c1
google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf
google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999
google-cloud-errors (1.6.0) sha256=1da8476dd706ad04b9d32e3c4b90d07d3463b37d6407cb56d41342ea7647d0a1
google-cloud-storage (1.59.0) sha256=b8c9a5661d775d65ccb279bb1d6be07fd8152576eb0146c2026bd023c4b186b9
google-cloud-storage (1.60.0) sha256=b21b752d37945d678a4533be5ef4303f15d33a964d8bc709c7c41c3600f650db
googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e
highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479
http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7
multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723
@@ -310,7 +308,7 @@ CHECKSUMS
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
retriable (3.4.1) sha256=fb3f114b7d492121c158c01f3d5152b5a615c5b70d5877d0bc08c7ec3725c3bc
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
@@ -320,7 +318,6 @@ CHECKSUMS
security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7
signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b
simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b
sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75
terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea
terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91
trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3

View File

@@ -18,7 +18,6 @@ help:
@echo ""
@echo " Development:"
@echo " run Run the app in debug mode"
@echo " webapp-run Run the web app in default browser"
@echo " format Format all Dart files"
@echo " analyze Analyze all Dart files"
@echo " check Check all files (format + analyze, no changes)"
@@ -41,20 +40,26 @@ help:
@echo " android-build-aab Build Android App Bundle"
@echo " android-push Build APK and push to device via adb"
@echo " ios-build Build iOS (no codesign)"
@echo " web-build Build web app"
@echo " macos-build Build macOS app (.app bundle, no codesign)"
@echo " macos-build-pkg Build signed macOS .pkg for App Store"
@echo " build-all Build all platforms"
@echo ""
@echo " Release:"
@echo " android-release-apk Build APK and copy to build/release/"
@echo " android-release-aab Build AAB and copy to build/release/"
@echo " ios-release Build iOS and create unsigned IPA in build/release/"
@echo " web-release Build web and create zip in build/release/"
@echo " ios-release Build IPA and copy to build/release/"
@echo " macos-release Build PKG and copy to build/release/"
@echo " release-all Build and release all platforms"
@echo ""
@echo " Deploying:"
@echo " android-deploy Build AAB and upload to Google Play (TRACK=internal|beta|production, STATUS=draft|completed)"
@echo " android-promote Promote release between tracks (FROM=internal, TO=production, STATUS=draft|completed)"
@echo " ios-deploy Build IPA and upload (DEST=testflight|appstore, default: testflight)"
@echo " ios-submit Submit the existing App Store build for review (no upload)"
@echo " macos-deploy Build PKG and upload (DEST=testflight|appstore, default: testflight)"
@echo " macos-submit Submit the existing Mac App Store build for review (no upload)"
@echo " deploy-production Build and deploy to production (Google Play + App Store)"
@echo " deploy-beta Build and deploy to beta (Google Play beta + TestFlight)"
# Setup
.PHONY: get
@@ -84,9 +89,6 @@ i18n-watch:
.PHONY: run
run:
flutter run
.PHONY: webapp-run
webapp-run:
open http://localhost:5111 & flutter run -d web-server --web-port=5111
.PHONY: format
format:
dart format .
@@ -104,12 +106,15 @@ check:
.PHONY: test
test:
ifdef FILES
flutter test $(FILES)else
flutter testendif
flutter test $(FILES)
else
flutter test
endif
.PHONY: test-coverage
test-coverage:
flutter test --coverage @echo "Coverage report generated at coverage/lcov.info"
flutter test --coverage
@echo "Coverage report generated at coverage/lcov.info"
# Building
.PHONY: android-build-apk
@@ -137,11 +142,28 @@ ios-build:
ios-build-ipa:
flutter build ipa --release --obfuscate --split-debug-info=build/debug-info-ios --dart-define-from-file=.env --export-options-plist=ios/ExportOptions.plist
.PHONY: web-build
web-build:
flutter build web --release
.PHONY: macos-build
macos-build:
flutter build macos --release --obfuscate --split-debug-info=build/debug-info-macos
.PHONY: macos-build-pkg
macos-build-pkg:
flutter build macos --config-only --obfuscate --split-debug-info=build/debug-info-macos
rm -rf build/macos/Runner.xcarchive build/macos/pkg
xcodebuild -workspace macos/Runner.xcworkspace \
-scheme Runner \
-configuration Release \
-archivePath build/macos/Runner.xcarchive \
-allowProvisioningUpdates \
archive
xcodebuild -exportArchive \
-archivePath build/macos/Runner.xcarchive \
-exportPath build/macos/pkg \
-exportOptionsPlist macos/ExportOptions.plist \
-allowProvisioningUpdates
.PHONY: build-all
build-all: android-build-apk android-build-aab web-build
build-all: android-build-apk android-build-aab
# Release (build + copy renamed artifacts to build/release/)
.PHONY: android-release-apk
@@ -157,11 +179,17 @@ android-release-aab: android-build-aab
@echo "-> build/release/pantry-$(VERSION).aab"
.PHONY: ios-release
ios-release: ios-build
ios-release: ios-build-ipa
mkdir -p build/release
cp build/ios/ipa/*.ipa build/release/pantry-$(VERSION).ipa
@echo "-> build/release/pantry-$(VERSION).ipa"
.PHONY: macos-release
macos-release: macos-build-pkg
mkdir -p build/release
cp build/macos/pkg/*.pkg build/release/pantry-$(VERSION).pkg
@echo "-> build/release/pantry-$(VERSION).pkg"
.PHONY: android-upload
android-upload:
@echo "$(or $(TRACK),beta)" | grep -qE '^(internal|alpha|beta|production)$$' || (echo "Error: Invalid TRACK '$(TRACK)'. Must be: internal, alpha, beta, production"; exit 1)
@@ -193,14 +221,41 @@ ios-upload:
.PHONY: ios-deploy
ios-deploy: ios-build-ipa ios-upload
.PHONY: web-release
web-release: web-build
mkdir -p build/release
cd build/web && zip -r ../release/pantry-$(VERSION)-web.zip .
@echo "-> build/release/pantry-$(VERSION)-web.zip"
.PHONY: ios-submit
ios-submit:
bundle exec fastlane ios submit
.PHONY: macos-upload
macos-upload:
@echo "$(or $(DEST),testflight)" | grep -qE '^(testflight|appstore)$$' || (echo "Error: Invalid DEST '$(DEST)'. Must be: testflight, appstore"; exit 1)
@echo "Destination: $(or $(DEST),testflight)"
@if [ "$(or $(DEST),testflight)" = "appstore" ]; then \
bundle exec fastlane mac release; \
else \
bundle exec fastlane mac beta; \
fi
.PHONY: macos-deploy
macos-deploy: macos-build-pkg macos-upload
.PHONY: macos-submit
macos-submit:
bundle exec fastlane mac submit
.PHONY: release-all
release-all: build-clean android-release-apk android-release-aab web-release
release-all: android-release-apk android-release-aab
.PHONY: deploy-production
deploy-production:
$(MAKE) android-deploy TRACK=production STATUS=completed
$(MAKE) ios-deploy DEST=appstore
$(MAKE) macos-deploy DEST=appstore
.PHONY: deploy-beta
deploy-beta:
$(MAKE) android-deploy TRACK=beta STATUS=completed
$(MAKE) ios-deploy DEST=testflight
$(MAKE) macos-deploy DEST=testflight
# CocoaPods
.PHONY: pods
@@ -239,6 +294,7 @@ icons:
rsvg-convert -w 1024 -h 1024 assets/logo_icon_squircle.svg > assets/icon/icon.png
rsvg-convert -w 1024 -h 1024 assets/logo_icon_square.svg > assets/icon/icon_ios.png
rsvg-convert -w 1024 -h 1024 assets/logo_icon_foreground.svg > assets/icon/icon_foreground.png
rsvg-convert -w 1024 -h 1024 assets/logo_icon_macos.svg > assets/icon/icon_macos.png
dart run flutter_launcher_icons
rsvg-convert -w 512 -h 512 assets/logo_icon_squircle.svg > fastlane/metadata/android/en-US/images/icon.png

View File

@@ -43,9 +43,9 @@ It may take a few minutes for your tester status to propagate.
Download the latest APK from the
[releases page](https://github.com/chenasraf/pantry-flutter/releases) and sideload onto your device.
### iOS
### App Store (iOS)
[Coming soon — TestFlight]
[Install from the App Store](https://apps.apple.com/us/app/pantry-for-nextcloud/id6762161619)
## Development

View File

@@ -49,6 +49,11 @@ android {
}
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
buildTypes {
debug {
applicationIdSuffix = ".debug"

View File

@@ -27,6 +27,21 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

View File

@@ -17,6 +17,24 @@ subprojects {
}
subprojects {
project.evaluationDependsOn(":app")
// Force any Flutter plugin subproject (e.g. receive_sharing_intent)
// that defaults to a different Kotlin jvmTarget to match this app's
// Java/Kotlin compatibility, avoiding "Inconsistent JVM Target
// Compatibility Between Java and Kotlin Tasks" build failures.
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}
plugins.withId("com.android.library") {
extensions.configure<com.android.build.gradle.LibraryExtension>("android") {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
}
}
tasks.register<Delete>("clean") {

BIN
assets/icon/icon_macos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<defs>
<clipPath id="squircle">
<path transform="translate(100 100) scale(1.609375)" d="
M256,0 C353,0 406,0 443,18 C468,30 482,44 494,69
C512,106 512,159 512,256 C512,353 512,406 494,443
C482,468 468,482 443,494 C406,512 353,512 256,512
C159,512 106,512 69,494 C44,482 30,468 18,443
C0,406 0,353 0,256 C0,159 0,106 18,69
C30,44 44,30 69,18 C106,0 159,0 256,0 Z"/>
</clipPath>
<filter id="dropShadow" x="-10%" y="-10%" width="120%" height="125%">
<feGaussianBlur in="SourceAlpha" stdDeviation="14"/>
<feOffset dy="14"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.35"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="topLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.22"/>
<stop offset="0.55" stop-color="#FFFFFF" stop-opacity="0"/>
<stop offset="1" stop-color="#000000" stop-opacity="0.12"/>
</linearGradient>
</defs>
<g filter="url(#dropShadow)">
<g clip-path="url(#squircle)">
<rect width="1024" height="1024" fill="#0082C9"/>
<g transform="translate(100 100) scale(1.609375) translate(106 106) scale(12.5)">
<path fill="#FFFFFF" d="M12,3L2,12H5V20H19V12H22L12,3M12,8.75A2.25,2.25 0 0,1 14.25,11A2.25,2.25 0 0,1 12,13.25A2.25,2.25 0 0,1 9.75,11A2.25,2.25 0 0,1 12,8.75M12,15C13.5,15 16.5,15.75 16.5,17.25V18H7.5V17.25C7.5,15.75 10.5,15 12,15Z"/>
</g>
<rect width="1024" height="1024" fill="url(#topLight)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,2 +1,2 @@
metadata_path("./fastlane/metadata/ios")
screenshots_path("./fastlane/metadata/ios/screenshots")
screenshots_path("./fastlane/metadata/ios/en-US/screenshots")

View File

@@ -202,6 +202,7 @@ platform :ios do
skip_screenshots: true,
submit_for_review: true,
precheck_include_in_app_purchases: false,
force: true,
)
end
@@ -215,6 +216,108 @@ platform :ios do
skip_screenshots: true,
submit_for_review: false,
precheck_include_in_app_purchases: false,
force: true,
)
end
desc "Submit existing App Store build for review (no IPA upload)"
lane :submit do
deliver(
api_key: api_key,
metadata_path: File.expand_path("metadata/ios", __dir__),
screenshots_path: File.expand_path("metadata/ios/en-US/screenshots", __dir__),
skip_binary_upload: true,
skip_screenshots: true,
submit_for_review: true,
precheck_include_in_app_purchases: false,
force: true,
)
end
end
# -- macOS --
platform :mac do
def api_key
key_id = ENV.fetch("APP_STORE_API_KEY")
app_store_connect_api_key(
key_id: key_id,
issuer_id: ENV.fetch("APP_STORE_ISSUER_ID"),
key_filepath: File.join(ENV.fetch("APP_STORE_KEY_PATH"), "AuthKey_#{key_id}.p8"),
)
end
def find_pkg
pkg_path = Dir[File.expand_path("../build/macos/pkg/*.pkg", __dir__)].first
UI.user_error!("No PKG found in build/macos/pkg/. Run 'make macos-build-pkg' first.") unless pkg_path
pkg_path
end
def sync_release_notes
version_code = version_info[:build]
changelog_file = File.expand_path("metadata/macos/en-US/changelogs/#{version_code}.txt", __dir__)
notes = File.exist?(changelog_file) ? File.read(changelog_file).strip : changelog_notes
release_notes_path = File.expand_path("metadata/macos/en-US/release_notes.txt", __dir__)
File.write(release_notes_path, notes)
UI.message("Synced release notes from build #{version_code} (#{notes.length} chars)")
notes
end
desc "Upload to TestFlight (macOS)"
lane :beta do
notes = sync_release_notes
upload_to_testflight(
api_key: api_key,
pkg: find_pkg,
app_platform: "osx",
changelog: notes,
skip_waiting_for_build_processing: true,
)
end
desc "Upload to Mac App Store"
lane :release do
sync_release_notes
deliver(
api_key: api_key,
pkg: find_pkg,
platform: "osx",
metadata_path: File.expand_path("metadata/macos", __dir__),
screenshots_path: File.expand_path("metadata/macos/en-US/screenshots", __dir__),
skip_screenshots: true,
submit_for_review: true,
precheck_include_in_app_purchases: false,
force: true,
)
end
desc "Sync macOS metadata only (no PKG upload)"
lane :metadata do
deliver(
api_key: api_key,
platform: "osx",
metadata_path: File.expand_path("metadata/macos", __dir__),
screenshots_path: File.expand_path("metadata/macos/en-US/screenshots", __dir__),
skip_binary_upload: true,
skip_screenshots: true,
submit_for_review: false,
precheck_include_in_app_purchases: false,
force: true,
)
end
desc "Submit existing Mac App Store build for review (no PKG upload)"
lane :submit do
deliver(
api_key: api_key,
platform: "osx",
metadata_path: File.expand_path("metadata/macos", __dir__),
screenshots_path: File.expand_path("metadata/macos/en-US/screenshots", __dir__),
skip_binary_upload: true,
skip_screenshots: true,
submit_for_review: true,
precheck_include_in_app_purchases: false,
force: true,
)
end
end

View File

@@ -68,6 +68,51 @@ Upload to App Store
Sync iOS metadata only (no IPA upload)
### ios submit
```sh
[bundle exec] fastlane ios submit
```
Submit existing App Store build for review (no IPA upload)
----
## Mac
### mac beta
```sh
[bundle exec] fastlane mac beta
```
Upload to TestFlight (macOS)
### mac release
```sh
[bundle exec] fastlane mac release
```
Upload to Mac App Store
### mac metadata
```sh
[bundle exec] fastlane mac metadata
```
Sync macOS metadata only (no PKG upload)
### mac submit
```sh
[bundle exec] fastlane mac submit
```
Submit existing Mac App Store build for review (no PKG upload)
----
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.

View File

@@ -0,0 +1,2 @@
Build System
- re-sign with stripping

View File

@@ -0,0 +1,2 @@
Build System
- fix signing

View File

@@ -0,0 +1,4 @@
Features
- update notes view & edit ui
Bug Fixes
- bug where note grid would not clip correctly

View File

@@ -0,0 +1,2 @@
Bug Fixes
- make markdown links clickable

View File

@@ -0,0 +1,4 @@
Features
- add setting to require checkbox tap to complete checklist items
Bug Fixes
- preserve subpath for Nextcloud instances hosted on sub-paths

View File

@@ -0,0 +1,5 @@
Features
- add setting to show spacing between categories in checklist items
- create new lists from the list selector
- share photos, links, and text to Pantry from other apps
- take photos directly from the photo board

View File

@@ -0,0 +1,5 @@
Features
- add trash view
- add undo action for checking items
- improve ui for larger devices
- switch between photos in photo view

View File

@@ -0,0 +1,2 @@
Build System
- re-sign with stripping

View File

@@ -0,0 +1,2 @@
Build System
- fix signing

View File

@@ -0,0 +1,4 @@
Features
- update notes view & edit ui
Bug Fixes
- bug where note grid would not clip correctly

View File

@@ -0,0 +1,2 @@
Bug Fixes
- make markdown links clickable

View File

@@ -0,0 +1,4 @@
Features
- add setting to require checkbox tap to complete checklist items
Bug Fixes
- preserve subpath for Nextcloud instances hosted on sub-paths

View File

@@ -0,0 +1,5 @@
Features
- add setting to show spacing between categories in checklist items
- create new lists from the list selector
- share photos, links, and text to Pantry from other apps
- take photos directly from the photo board

View File

@@ -0,0 +1,6 @@
Features
- add trash view
- add undo action for checking items
- improve ui for larger devices
- open full file picker when running on macOS
- switch between photos in photo view

View File

@@ -0,0 +1 @@
2026 Chen Asraf

View File

@@ -0,0 +1,5 @@
Features
- add setting to show spacing between categories in checklist items
- create new lists from the list selector
- share photos, links, and text to Pantry from other apps
- take photos directly from the photo board

View File

@@ -0,0 +1,6 @@
Features
- add trash view
- add undo action for checking items
- improve ui for larger devices
- go back by using Esc key
- switch between photos in photo view

View File

@@ -0,0 +1,25 @@
Pantry is a companion app for the Nextcloud Pantry server app. It lets you and your household members collaborate on shared checklists, a photo board, and a notes wall — all stored on your own Nextcloud server.
* Checklists
Create shopping lists and to-do lists with categories, quantities, recurring items, and images. Drag to reorder, sort by name or date, and check items off as you go.
* Photo Board
Upload and organize photos into folders. Add captions, drag to reorder, and browse everything in a clean grid view.
* Notes Wall
Keep shared notes with your household. Color-code them, write in markdown, and pin the important stuff where everyone can see it.
* Your data, your server
Pantry connects directly to your Nextcloud instance. No accounts to create, no cloud services in between. Your data never leaves your server.
* Features
- Shared checklists with categories, quantities, and recurrence
- Photo board with folders, captions, and multi-upload
- Color-coded notes wall
- Drag-and-drop reordering everywhere
- Multi-select for bulk actions
- Offline caching for fast loading
- Material Design 3 with dark mode support
- Secure login flow authentication
* Requires a Nextcloud server with the Pantry app installed. Visit the project page for setup instructions.

View File

@@ -0,0 +1 @@
nextcloud, checklist, todo, shopping list, notes, self-hosted, household

View File

@@ -0,0 +1 @@
https://github.com/chenasraf/pantry-flutter

View File

@@ -0,0 +1 @@
Pantry for Nextcloud

View File

@@ -0,0 +1 @@
https://casraf.dev/pantry-privacy-policy

View File

@@ -0,0 +1 @@
Manage your household on your Nextcloud — shared lists, photos & notes.

View File

@@ -0,0 +1,6 @@
Features
- add trash view
- add undo action for checking items
- improve ui for larger devices
- go back by using Esc key
- switch between photos in photo view

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1 @@
Home lists, photos & notes

View File

@@ -0,0 +1 @@
https://github.com/chenasraf/pantry-flutter

View File

@@ -0,0 +1 @@
PRODUCTIVITY

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
EQq38!t9uA!@RdAkn6umJHo@nDh3ZZwM

View File

@@ -0,0 +1 @@
store-test

View File

@@ -0,0 +1 @@
casraf@pm.me

View File

@@ -0,0 +1 @@
Chen

View File

@@ -0,0 +1 @@
Asraf

View File

@@ -0,0 +1,3 @@
1. In the login screen, for the server, use: spider.casraf.dev and click "Connect"
2. Use the given username/password to sign in to the Nextcloud website loaded in the default browser
3. Click "Grant Access" to grant access to the app inside the in-app browser

View File

@@ -0,0 +1 @@
+972549107970

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

10
ios/Gemfile Normal file
View File

@@ -0,0 +1,10 @@
source 'https://rubygems.org'
gem 'cocoapods'
# Pin xcodeproj to a commit on master that supports objectVersion 70
# (generated by Xcode 16+). Drop this override once a release > 1.27.0
# is published on rubygems.
gem 'xcodeproj',
git: 'https://github.com/CocoaPods/Xcodeproj.git',
ref: 'c12d2ae619ae42f947a6b07d865f69948c752df5'

194
ios/Gemfile.lock Normal file
View File

@@ -0,0 +1,194 @@
GIT
remote: https://github.com/CocoaPods/Xcodeproj.git
revision: c12d2ae619ae42f947a6b07d865f69948c752df5
ref: c12d2ae619ae42f947a6b07d865f69948c752df5
specs:
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.8)
activesupport (7.2.3.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1, < 6)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (4.1.2)
claide (1.1.0)
cocoapods (1.16.2)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.16.2)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.27.0, < 2.0)
cocoapods-core (1.16.2)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
cocoapods-downloader (2.1)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.3.6)
connection_pool (3.0.2)
drb (2.2.3)
escape (0.0.4)
ethon (0.18.0)
ffi (>= 1.15.0)
logger
ffi (1.17.4)
ffi (1.17.4-aarch64-linux-gnu)
ffi (1.17.4-aarch64-linux-musl)
ffi (1.17.4-arm-linux-gnu)
ffi (1.17.4-arm-linux-musl)
ffi (1.17.4-arm64-darwin)
ffi (1.17.4-x86-linux-gnu)
ffi (1.17.4-x86-linux-musl)
ffi (1.17.4-x86_64-darwin)
ffi (1.17.4-x86_64-linux-gnu)
ffi (1.17.4-x86_64-linux-musl)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.9.0)
mutex_m
i18n (1.14.8)
concurrent-ruby (~> 1.0)
json (2.19.5)
logger (1.7.0)
minitest (5.27.0)
molinillo (0.8.0)
mutex_m (0.3.0)
nanaimo (0.4.0)
nap (1.1.0)
netrc (0.11.0)
public_suffix (4.0.7)
rexml (3.4.4)
ruby-macho (2.5.1)
securerandom (0.4.1)
typhoeus (1.6.0)
ethon (>= 0.18.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
PLATFORMS
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
arm64-darwin
ruby
x86-linux-gnu
x86-linux-musl
x86_64-darwin
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES
cocoapods
xcodeproj!
CHECKSUMS
CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
activesupport (7.2.3.1) sha256=11ebed516a43a0bb47346227a35ebae4d9427465a7c9eb197a03d5c8d283cb34
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
algoliasearch (1.27.5) sha256=26c1cddf3c2ec4bd60c148389e42702c98fdac862881dc6b07a4c0b89ffec853
atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
cocoapods (1.16.2) sha256=0ff1c860f32df3db8b16df09b58da1a6bb2a12fe55f6d5e8be994a74fadd1e5e
cocoapods-core (1.16.2) sha256=4bb1b5c420691e60cf36fa227dec6bc48c096c34c97bb7aa512ea7f3246fc12b
cocoapods-deintegrate (1.0.5) sha256=517c2a448ef563afe99b6e7668704c27f5de9e02715a88ee9de6974dc1b3f6a2
cocoapods-downloader (2.1) sha256=bb6ebe1b3966dc4055de54f7a28b773485ac724fdf575d9bee2212d235e7b6d1
cocoapods-plugins (1.0.0) sha256=725d17ce90b52f862e73476623fd91441b4430b742d8a071000831efb440ca9a
cocoapods-search (1.0.1) sha256=1b133b0e6719ed439bd840e84a1828cca46425ab73a11eff5e096c3b2df05589
cocoapods-trunk (1.6.0) sha256=5f5bda8c172afead48fa2d43a718cf534b1313c367ba1194cebdeb9bfee9ed31
cocoapods-try (1.2.0) sha256=145b946c6e7747ed0301d975165157951153d27469e6b2763c83e25c84b9defe
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
escape (0.0.4) sha256=e49f44ae2b4f47c6a3abd544ae77fe4157802794e32f19b8e773cbc4dcec4169
ethon (0.18.0) sha256=b598afc9f30448cb068b850714b7d6948e941476095d04f90a4ac65b8d6efcb2
ffi (1.17.4) sha256=bcd1642e06f0d16fc9e09ac6d49c3a7298b9789bcb58127302f934e437d60acf
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564
ffi (1.17.4-arm-linux-musl) sha256=9d4838ded0465bef6e2426935f6bcc93134b6616785a84ffd2a3d82bc3cf6f95
ffi (1.17.4-arm64-darwin) sha256=19071aaf1419251b0a46852abf960e77330a3b334d13a4ab51d58b31a937001b
ffi (1.17.4-x86-linux-gnu) sha256=38e150df5f4ca555e25beca4090823ae09657bceded154e3c52f8631c1ed72cf
ffi (1.17.4-x86-linux-musl) sha256=fbeec0fc7c795bcf86f623bb18d31ea1820f7bd580e1703a3d3740d527437809
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
fourflusher (2.3.1) sha256=1b3de61c7c791b6a4e64f31e3719eb25203d151746bb519a0292bff1065ccaa9
fuzzy_match (2.0.4) sha256=b5de4f95816589c5b5c3ad13770c0af539b75131c158135b3f3bbba75d0cfca5
gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
molinillo (0.8.0) sha256=efbff2716324e2a30bccd3eba1ff3a735f4d5d53ffddbc6a2f32c0ca9433045d
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723
nap (1.1.0) sha256=949691660f9d041d75be611bb2a8d2fd559c467537deac241f4097d9b5eea576
netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f
public_suffix (4.0.7) sha256=8be161e2421f8d45b0098c042c06486789731ea93dc3a896d30554ee38b573b8
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
ruby-macho (2.5.1) sha256=9075e52e0f9270b552a90b24fcc6219ad149b0d15eae1bc364ecd0ac8984f5c9
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
typhoeus (1.6.0) sha256=bacc41c23e379547e29801dc235cd1699b70b955a1ba3d32b2b877aa844c331d
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
xcodeproj (1.27.0)
BUNDLED WITH
4.0.7

View File

@@ -1,5 +1,4 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
platform :ios, '14.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -36,6 +35,17 @@ target 'Runner' do
end
end
# Share Extension target has no pod dependencies — the
# `RSIShareViewController` base class is vendored directly into the
# extension's sources (ios/Share/RSIShareViewController.swift). This
# avoids dragging in Flutter.framework and `addApplicationDelegate`
# from the receive_sharing_intent plugin, which are banned in app
# extensions. `use_frameworks!` is declared so CocoaPods doesn't flag
# the host/embedded mismatch with Runner.
target 'Share' do
use_frameworks!
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)

View File

@@ -1,4 +1,40 @@
PODS:
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_local_notifications (0.0.1):
- Flutter
@@ -11,9 +47,15 @@ PODS:
- Flutter
- package_info_plus (0.4.5):
- Flutter
- receive_sharing_intent (1.8.1):
- Flutter
- SDWebImage (5.21.7):
- SDWebImage/Core (= 5.21.7)
- SDWebImage/Core (5.21.7)
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter
- wakelock_plus (0.0.1):
@@ -22,18 +64,32 @@ PODS:
- Flutter
DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_local_notifications:
@@ -46,6 +102,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_picker_ios/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
receive_sharing_intent:
:path: ".symlinks/plugins/receive_sharing_intent/ios"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
@@ -56,17 +114,24 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/workmanager_apple/ios"
SPEC CHECKSUMS:
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
PODFILE CHECKSUM: e8d97e41f073e724afe14e2390c9692116d292ff
COCOAPODS: 1.16.2

2
ios/Profile.xcconfig Normal file
View File

@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"
#include "Generated.xcconfig"

View File

@@ -11,8 +11,11 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4B2FECE151D6658169DC6E88 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E836629E52B02A5B6D0F862 /* Pods_RunnerTests.framework */; };
745F12F32FB5CF07007C0ADF /* Share.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 745F12E92FB5CF07007C0ADF /* Share.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
745F13182FB5D473007C0ADF /* Profile.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 745F13172FB5D473007C0ADF /* Profile.xcconfig */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
971DDFFA6B83AAC1FD474A95 /* Pods_Share.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B495CAE63B8B48AA6114881 /* Pods_Share.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -27,9 +30,27 @@
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
745F12F12FB5CF07007C0ADF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 745F12E82FB5CF07007C0ADF;
remoteInfo = Share;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
745F12F42FB5CF07007C0ADF /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
745F12F32FB5CF07007C0ADF /* Share.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -46,12 +67,17 @@
0D02B01150F8705A7BE3C6D1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1B495CAE63B8B48AA6114881 /* Pods_Share.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Share.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1E836629E52B02A5B6D0F862 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
31D13B974C1302A5F0CE86D2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
33DF0655613AFDEC743B1898 /* Pods-Share.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share.profile.xcconfig"; path = "Target Support Files/Pods-Share/Pods-Share.profile.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3D05507B181A60A566EB9904 /* Pods-Share.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share.debug.xcconfig"; path = "Target Support Files/Pods-Share/Pods-Share.debug.xcconfig"; sourceTree = "<group>"; };
736C2329657A6FBB9CE1D305 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
745F12E92FB5CF07007C0ADF /* Share.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Share.appex; sourceTree = BUILT_PRODUCTS_DIR; };
745F13172FB5D473007C0ADF /* Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Profile.xcconfig; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -65,10 +91,36 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9885F82953FBB27851D4D171 /* Pods-Share.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share.release.xcconfig"; path = "Target Support Files/Pods-Share/Pods-Share.release.xcconfig"; sourceTree = "<group>"; };
C28EA37C4C36CE14FE19E720 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
ECEC55E4DC05821BB0BEA50F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
745F13312FB5D55F007C0ADF /* Exceptions for "Share" folder in "Share" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 745F12E82FB5CF07007C0ADF /* Share */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
745F12EA2FB5CF07007C0ADF /* Share */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
745F13312FB5D55F007C0ADF /* Exceptions for "Share" folder in "Share" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = Share;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
55917F8B3405971C4327A3FF /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -78,6 +130,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
745F12E62FB5CF07007C0ADF /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
971DDFFA6B83AAC1FD474A95 /* Pods_Share.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -102,6 +162,7 @@
children = (
31D13B974C1302A5F0CE86D2 /* Pods_Runner.framework */,
1E836629E52B02A5B6D0F862 /* Pods_RunnerTests.framework */,
1B495CAE63B8B48AA6114881 /* Pods_Share.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -122,10 +183,12 @@
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
745F12EA2FB5CF07007C0ADF /* Share */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
ECEC5B714C9B60C200CF4964 /* Pods */,
43389EC5ADFFCB84CB05BC4D /* Frameworks */,
745F13172FB5D473007C0ADF /* Profile.xcconfig */,
);
sourceTree = "<group>";
};
@@ -134,6 +197,7 @@
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
745F12E92FB5CF07007C0ADF /* Share.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -163,8 +227,10 @@
0D02B01150F8705A7BE3C6D1 /* Pods-RunnerTests.debug.xcconfig */,
91DAEB15490ECD076B82EE80 /* Pods-RunnerTests.release.xcconfig */,
736C2329657A6FBB9CE1D305 /* Pods-RunnerTests.profile.xcconfig */,
3D05507B181A60A566EB9904 /* Pods-Share.debug.xcconfig */,
9885F82953FBB27851D4D171 /* Pods-Share.release.xcconfig */,
33DF0655613AFDEC743B1898 /* Pods-Share.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
@@ -190,6 +256,27 @@
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
745F12E82FB5CF07007C0ADF /* Share */ = {
isa = PBXNativeTarget;
buildConfigurationList = 745F12F92FB5CF07007C0ADF /* Build configuration list for PBXNativeTarget "Share" */;
buildPhases = (
1DFA5925966D69304295FDAF /* [CP] Check Pods Manifest.lock */,
745F12E52FB5CF07007C0ADF /* Sources */,
745F12E62FB5CF07007C0ADF /* Frameworks */,
745F12E72FB5CF07007C0ADF /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
745F12EA2FB5CF07007C0ADF /* Share */,
);
name = Share;
productName = Share;
productReference = 745F12E92FB5CF07007C0ADF /* Share.appex */;
productType = "com.apple.product-type.app-extension";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
@@ -200,12 +287,14 @@
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
745F12F42FB5CF07007C0ADF /* Embed Foundation Extensions */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
60CC7BE4F08CFECA259D64E9 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
745F12F22FB5CF07007C0ADF /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
@@ -219,6 +308,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 2630;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
@@ -226,6 +316,9 @@
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
745F12E82FB5CF07007C0ADF = {
CreatedOnToolsVersion = 26.3;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
@@ -247,6 +340,7 @@
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
745F12E82FB5CF07007C0ADF /* Share */,
);
};
/* End PBXProject section */
@@ -259,6 +353,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
745F12E72FB5CF07007C0ADF /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
745F13182FB5D473007C0ADF /* Profile.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -273,6 +375,28 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
1DFA5925966D69304295FDAF /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Share-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -376,6 +500,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
745F12E52FB5CF07007C0ADF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -394,6 +525,11 @@
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
745F12F22FB5CF07007C0ADF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 745F12E82FB5CF07007C0ADF /* Share */;
targetProxy = 745F12F12FB5CF07007C0ADF /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -470,10 +606,11 @@
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
baseConfigurationReference = 745F13172FB5D473007C0ADF /* Profile.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = Y893L6NQP2;
ENABLE_BITCODE = NO;
@@ -543,6 +680,134 @@
};
name = Profile;
};
745F12F52FB5CF07007C0ADF /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3D05507B181A60A566EB9904 /* Pods-Share.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = Share/ShareRelease.entitlements;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Y893L6NQP2;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Share/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Share;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.casraf.pantry.Share;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
745F12F62FB5CF07007C0ADF /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9885F82953FBB27851D4D171 /* Pods-Share.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = Share/ShareRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Y893L6NQP2;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Share/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Share;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.casraf.pantry.Share;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Pantry Share Distribution";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
745F12F72FB5CF07007C0ADF /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33DF0655613AFDEC743B1898 /* Pods-Share.profile.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = Share/ShareRelease.entitlements;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Y893L6NQP2;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Share/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Share;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.casraf.pantry.Share;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -660,6 +925,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = Y893L6NQP2;
ENABLE_BITCODE = NO;
@@ -686,6 +952,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -721,6 +988,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
745F12F92FB5CF07007C0ADF /* Build configuration list for PBXNativeTarget "Share" */ = {
isa = XCConfigurationList;
buildConfigurations = (
745F12F52FB5CF07007C0ADF /* Debug */,
745F12F62FB5CF07007C0ADF /* Release */,
745F12F72FB5CF07007C0ADF /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -84,5 +84,24 @@
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSCameraUsageDescription</key>
<string>Pantry needs access to the camera so you can take photos and upload them to your photo board.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Pantry needs access to your photo library so you can pick existing photos and upload them to your photo board.</string>
<key>AppGroupId</key>
<string>group.dev.casraf.pantry</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>dev.casraf.pantry.share</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMedia-dev.casraf.pantry</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.dev.casraf.pantry</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

29
ios/Share/Info.plist Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>20</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>10</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,346 @@
// Vendored from receive_sharing_intent 1.8.1 so the Share Extension does
// not need to import the pod (which transitively pulls in Flutter +
// UIApplication APIs that are banned in app extensions).
//
// Keep the constants and `SharedMediaFile` / `SharedMediaType` shapes in
// sync with `SwiftReceiveSharingIntentPlugin.swift` in the upstream pod,
// otherwise the host app side will not decode payloads correctly.
import UIKit
import Social
import MobileCoreServices
import Photos
import UniformTypeIdentifiers
let kSchemePrefix = "ShareMedia"
let kUserDefaultsKey = "ShareKey"
let kUserDefaultsMessageKey = "ShareMessageKey"
let kAppGroupIdKey = "AppGroupId"
class SharedMediaFile: Codable {
var path: String
var mimeType: String?
var thumbnail: String?
var duration: Double?
var message: String?
var type: SharedMediaType
init(
path: String,
mimeType: String? = nil,
thumbnail: String? = nil,
duration: Double? = nil,
message: String? = nil,
type: SharedMediaType
) {
self.path = path
self.mimeType = mimeType
self.thumbnail = thumbnail
self.duration = duration
self.message = message
self.type = type
}
}
enum SharedMediaType: String, Codable, CaseIterable {
case image
case video
case text
case file
case url
var toUTTypeIdentifier: String {
if #available(iOS 14.0, *) {
switch self {
case .image: return UTType.image.identifier
case .video: return UTType.movie.identifier
case .text: return UTType.text.identifier
case .file: return UTType.fileURL.identifier
case .url: return UTType.url.identifier
}
}
switch self {
case .image: return "public.image"
case .video: return "public.movie"
case .text: return "public.text"
case .file: return "public.file-url"
case .url: return "public.url"
}
}
}
@available(swift, introduced: 5.0)
open class RSIShareViewController: SLComposeServiceViewController {
var hostAppBundleIdentifier = ""
var appGroupId = ""
var sharedMedia: [SharedMediaFile] = []
open func shouldAutoRedirect() -> Bool { true }
open override func isContentValid() -> Bool { true }
open override func viewDidLoad() {
super.viewDidLoad()
loadIds()
}
open override func didSelectPost() {
saveAndRedirect(message: contentText)
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let content = extensionContext!.inputItems[0] as? NSExtensionItem,
let contents = content.attachments {
for (index, attachment) in contents.enumerated() {
for type in SharedMediaType.allCases {
if attachment.hasItemConformingToTypeIdentifier(type.toUTTypeIdentifier) {
attachment.loadItem(forTypeIdentifier: type.toUTTypeIdentifier) { [weak self] data, error in
guard let this = self, error == nil else {
self?.dismissWithError()
return
}
switch type {
case .text:
if let text = data as? String {
this.handleMedia(forLiteral: text, type: type, index: index, content: content)
}
case .url:
if let url = data as? URL {
this.handleMedia(forLiteral: url.absoluteString, type: type, index: index, content: content)
}
default:
if let url = data as? URL {
this.handleMedia(forFile: url, type: type, index: index, content: content)
} else if let image = data as? UIImage {
this.handleMedia(forUIImage: image, type: type, index: index, content: content)
}
}
}
break
}
}
}
}
}
open override func configurationItems() -> [Any]! { [] }
private func loadIds() {
let shareExtensionAppBundleIdentifier = Bundle.main.bundleIdentifier!
let lastIndexOfPoint = shareExtensionAppBundleIdentifier.lastIndex(of: ".")
hostAppBundleIdentifier = String(shareExtensionAppBundleIdentifier[..<lastIndexOfPoint!])
let defaultAppGroupId = "group.\(hostAppBundleIdentifier)"
let customAppGroupId = Bundle.main.object(forInfoDictionaryKey: kAppGroupIdKey) as? String
appGroupId = customAppGroupId ?? defaultAppGroupId
}
private func handleMedia(forLiteral item: String, type: SharedMediaType, index: Int, content: NSExtensionItem) {
sharedMedia.append(SharedMediaFile(
path: item,
mimeType: type == .text ? "text/plain" : nil,
type: type
))
if index == (content.attachments?.count ?? 0) - 1, shouldAutoRedirect() {
saveAndRedirect()
}
}
private func handleMedia(forUIImage image: UIImage, type: SharedMediaType, index: Int, content: NSExtensionItem) {
guard let containerURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
if index == (content.attachments?.count ?? 0) - 1, shouldAutoRedirect() {
saveAndRedirect()
}
return
}
let tempPath = containerURL.appendingPathComponent("TempImage.png")
if writeTempFile(image, to: tempPath) {
let newPathDecoded = tempPath.absoluteString.removingPercentEncoding!
sharedMedia.append(SharedMediaFile(
path: newPathDecoded,
mimeType: type == .image ? "image/png" : nil,
type: type
))
}
if index == (content.attachments?.count ?? 0) - 1, shouldAutoRedirect() {
saveAndRedirect()
}
}
private func handleMedia(forFile url: URL, type: SharedMediaType, index: Int, content: NSExtensionItem) {
let fileName = getFileName(from: url, type: type)
guard let containerURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
if index == (content.attachments?.count ?? 0) - 1, shouldAutoRedirect() {
saveAndRedirect()
}
return
}
let newPath = containerURL.appendingPathComponent(fileName)
if copyFile(at: url, to: newPath) {
let newPathDecoded = newPath.absoluteString.removingPercentEncoding!
if type == .video, let videoInfo = getVideoInfo(from: url) {
let thumbnailPathDecoded = videoInfo.thumbnail?.removingPercentEncoding
sharedMedia.append(SharedMediaFile(
path: newPathDecoded,
mimeType: url.mimeType(),
thumbnail: thumbnailPathDecoded,
duration: videoInfo.duration,
type: type
))
} else {
sharedMedia.append(SharedMediaFile(
path: newPathDecoded,
mimeType: url.mimeType(),
type: type
))
}
}
if index == (content.attachments?.count ?? 0) - 1, shouldAutoRedirect() {
saveAndRedirect()
}
}
private func saveAndRedirect(message: String? = nil) {
let userDefaults = UserDefaults(suiteName: appGroupId)
userDefaults?.set(toData(data: sharedMedia), forKey: kUserDefaultsKey)
userDefaults?.set(message, forKey: kUserDefaultsMessageKey)
userDefaults?.synchronize()
redirectToHostApp()
}
private func redirectToHostApp() {
loadIds()
let url = URL(string: "\(kSchemePrefix)-\(hostAppBundleIdentifier):share")!
var responder = self as UIResponder?
// iOS 18 removed the legacy `openURL:` selector, so we have to
// walk the responder chain and call `UIApplication.open(_:)`
// through the dynamic cast this path is what the upstream
// receive_sharing_intent package uses.
if #available(iOS 18.0, *) {
while responder != nil {
if let application = responder as? UIApplication {
application.open(url, options: [:], completionHandler: nil)
}
responder = responder?.next
}
} else {
let selectorOpenURL = sel_registerName("openURL:")
while responder != nil {
if responder?.responds(to: selectorOpenURL) == true {
_ = responder?.perform(selectorOpenURL, with: url)
}
responder = responder?.next
}
}
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
private func dismissWithError() {
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel) { _ in
self.dismiss(animated: true, completion: nil)
})
present(alert, animated: true, completion: nil)
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
private func getFileName(from url: URL, type: SharedMediaType) -> String {
var name = url.lastPathComponent
if name.isEmpty {
switch type {
case .image: name = UUID().uuidString + ".png"
case .video: name = UUID().uuidString + ".mp4"
case .text: name = UUID().uuidString + ".txt"
default: name = UUID().uuidString
}
}
return name
}
private func writeTempFile(_ image: UIImage, to dstURL: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: dstURL.path) {
try FileManager.default.removeItem(at: dstURL)
}
try image.pngData()?.write(to: dstURL)
return true
} catch {
return false
}
}
private func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: dstURL.path) {
try FileManager.default.removeItem(at: dstURL)
}
try FileManager.default.copyItem(at: srcURL, to: dstURL)
} catch {
return false
}
return true
}
private func getVideoInfo(from url: URL) -> (thumbnail: String?, duration: Double)? {
let asset = AVAsset(url: url)
let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
let thumbnailPath = getThumbnailPath(for: url)
if FileManager.default.fileExists(atPath: thumbnailPath.path) {
return (thumbnail: thumbnailPath.absoluteString, duration: duration)
}
var saved = false
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
assetImgGenerate.maximumSize = CGSize(width: 360, height: 360)
do {
let img = try assetImgGenerate.copyCGImage(
at: CMTimeMakeWithSeconds(600, preferredTimescale: 1),
actualTime: nil
)
try UIImage(cgImage: img).pngData()?.write(to: thumbnailPath)
saved = true
} catch {
saved = false
}
return saved ? (thumbnail: thumbnailPath.absoluteString, duration: duration) : nil
}
private func getThumbnailPath(for url: URL) -> URL {
let fileName = Data(url.lastPathComponent.utf8)
.base64EncodedString()
.replacingOccurrences(of: "==", with: "")
return FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: appGroupId)!
.appendingPathComponent("\(fileName).jpg")
}
private func toData(data: [SharedMediaFile]) -> Data {
try! JSONEncoder().encode(data)
}
}
extension URL {
func mimeType() -> String {
if #available(iOS 14.0, *) {
if let mimeType = UTType(filenameExtension: pathExtension)?.preferredMIMEType {
return mimeType
}
} else {
if let uti = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, pathExtension as NSString, nil
)?.takeRetainedValue(),
let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
return mimetype as String
}
}
return "application/octet-stream"
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.dev.casraf.pantry</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
import UIKit
class ShareViewController: RSIShareViewController {
// Inherits default behavior from `RSIShareViewController` (vendored in
// RSIShareViewController.swift): collect the shared payload (images,
// text, URLs), persist it to the App Group's UserDefaults, then open
// the host app via the registered URL scheme. Override
// `shouldAutoRedirect()` to return `false` if a custom share sheet UI
// is needed before posting.
}

View File

@@ -1,8 +1,11 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'i18n.dart';
@@ -16,6 +19,7 @@ import 'services/local_notifications_service.dart';
import 'services/note_service.dart';
import 'services/photo_service.dart';
import 'services/prefs_service.dart';
import 'services/share_intent_service.dart';
import 'services/theming_service.dart';
import 'views/home/home_view.dart';
import 'views/login/login_view.dart';
@@ -46,6 +50,7 @@ void main() async {
}
}
LocaleService.instance.apply();
unawaited(ShareIntentService.instance.init());
runApp(const PantryApp());
}
@@ -56,6 +61,39 @@ class PantryApp extends StatefulWidget {
State<PantryApp> createState() => PantryAppState();
}
class _EscapePopWrapper extends StatelessWidget {
final Widget child;
const _EscapePopWrapper({required this.child});
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.escape): _PopRouteIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{_PopRouteIntent: _PopRouteAction()},
child: child,
),
);
}
}
class _PopRouteIntent extends Intent {
const _PopRouteIntent();
}
class _PopRouteAction extends Action<_PopRouteIntent> {
@override
Object? invoke(covariant _PopRouteIntent intent) {
final nav = rootNavigatorKey.currentState;
if (nav?.canPop() == true) {
nav!.maybePop();
}
return null;
}
}
class PantryAppState extends State<PantryApp> {
bool _isLoggedIn = AuthService.instance.isLoggedIn;
@@ -113,74 +151,90 @@ class PantryAppState extends State<PantryApp> {
Widget build(BuildContext context) {
final color = ThemingService.instance.effectiveColor;
final locale = LocaleService.instance.effectiveLocale;
return Directionality(
textDirection: LocaleService.instance.textDirection,
child: MaterialApp(
key: ValueKey(locale),
// debugShowCheckedModeBanner: false,
navigatorKey: rootNavigatorKey,
locale: locale,
supportedLocales: supportedLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
title: m.common.appTitle,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: color,
).copyWith(primary: color),
useMaterial3: true,
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
final isMacOS = !kIsWeb && Platform.isMacOS;
final isDesktopHost =
kIsWeb ||
(!kIsWeb &&
(Platform.isMacOS || Platform.isWindows || Platform.isLinux));
final appBarTheme = isMacOS ? const AppBarTheme(toolbarHeight: 66) : null;
return ChangeNotifierProvider<PrefsService>.value(
value: PrefsService.instance,
child: Directionality(
textDirection: LocaleService.instance.textDirection,
child: MaterialApp(
key: ValueKey(locale),
debugShowCheckedModeBanner: false,
navigatorKey: rootNavigatorKey,
locale: locale,
supportedLocales: supportedLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
title: m.common.appTitle,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: color,
).copyWith(primary: color),
useMaterial3: true,
appBarTheme: appBarTheme,
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 8,
position: PopupMenuPosition.under,
),
elevation: 8,
position: PopupMenuPosition.under,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: color,
brightness: Brightness.dark,
).copyWith(primary: color),
useMaterial3: true,
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: color,
brightness: Brightness.dark,
).copyWith(primary: color),
useMaterial3: true,
appBarTheme: appBarTheme,
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 8,
position: PopupMenuPosition.under,
),
elevation: 8,
position: PopupMenuPosition.under,
),
themeMode: ThemingService.instance.themeMode,
builder: (context, child) {
if (child == null) return const SizedBox.shrink();
if (!isDesktopHost) return child;
return _EscapePopWrapper(child: child);
},
onGenerateInitialRoutes: (initialRoute) => [
MaterialPageRoute(
builder: (_) => _isLoggedIn
? (PrefsService.instance.notificationsIntroSeen
? HomeView(onLogout: _onLogout)
: NotificationsIntroView(onDone: _onIntroDone))
: LoginView(onLoginSuccess: _onLoginSuccess),
),
],
onGenerateRoute: (settings) {
switch (settings.name) {
case '/home':
return MaterialPageRoute(
builder: (_) => HomeView(onLogout: _onLogout),
);
case '/notifications-intro':
return MaterialPageRoute(
builder: (_) => NotificationsIntroView(onDone: _onIntroDone),
);
case '/login':
default:
return MaterialPageRoute(
builder: (_) => LoginView(onLoginSuccess: _onLoginSuccess),
);
}
},
),
themeMode: ThemingService.instance.themeMode,
onGenerateInitialRoutes: (initialRoute) => [
MaterialPageRoute(
builder: (_) => _isLoggedIn
? (PrefsService.instance.notificationsIntroSeen
? HomeView(onLogout: _onLogout)
: NotificationsIntroView(onDone: _onIntroDone))
: LoginView(onLoginSuccess: _onLoginSuccess),
),
],
onGenerateRoute: (settings) {
switch (settings.name) {
case '/home':
return MaterialPageRoute(
builder: (_) => HomeView(onLogout: _onLogout),
);
case '/notifications-intro':
return MaterialPageRoute(
builder: (_) => NotificationsIntroView(onDone: _onIntroDone),
);
case '/login':
default:
return MaterialPageRoute(
builder: (_) => LoginView(onLoginSuccess: _onLoginSuccess),
);
}
},
),
);
}

View File

@@ -75,6 +75,7 @@ class Messages {
ChecklistsMessages get checklists => ChecklistsMessages(this);
NotesWallMessages get notesWall => NotesWallMessages(this);
PhotoBoardMessages get photoBoard => PhotoBoardMessages(this);
ShareMessages get share => ShareMessages(this);
RecurrenceMessages get recurrence => RecurrenceMessages(this);
}
@@ -361,6 +362,35 @@ class SettingsMessages {
/// ```
String get generalSection => """General""";
/// ```dart
/// "Interface"
/// ```
String get interfaceSection => """Interface""";
/// ```dart
/// "Tap row to complete items"
/// ```
String get tapRowToComplete => """Tap row to complete items""";
/// ```dart
/// "When off, items are only marked complete by tapping the checkbox."
/// ```
String get tapRowToCompleteBody =>
"""When off, items are only marked complete by tapping the checkbox.""";
/// ```dart
/// "Show spacing between categories in list items"
/// ```
String get categorySpacing =>
"""Show spacing between categories in list items""";
/// ```dart
/// "Only visible when sorting by category"
/// ```
String get categorySpacingBody => """Only visible when sorting by category""";
CategorySpacingNamesSettingsMessages get categorySpacingNames =>
CategorySpacingNamesSettingsMessages(this);
/// ```dart
/// "Language"
/// ```
@@ -427,6 +457,26 @@ class SettingsMessages {
"""Notification permission was denied. Enable it in system settings.""";
}
class CategorySpacingNamesSettingsMessages {
final SettingsMessages _parent;
const CategorySpacingNamesSettingsMessages(this._parent);
/// ```dart
/// "Disabled"
/// ```
String get disabled => """Disabled""";
/// ```dart
/// "Space"
/// ```
String get space => """Space""";
/// ```dart
/// "Divider"
/// ```
String get divider => """Divider""";
}
class LanguageNamesSettingsMessages {
final SettingsMessages _parent;
const LanguageNamesSettingsMessages(this._parent);
@@ -657,6 +707,92 @@ class ChecklistsMessages {
/// ```
String get moveFailed => """Failed to move item.""";
/// ```dart
/// "Item marked as done"
/// ```
String get itemMarkedDone => """Item marked as done""";
/// ```dart
/// "Undo"
/// ```
String get undo => """Undo""";
/// ```dart
/// "View trash"
/// ```
String get viewTrash => """View trash""";
/// ```dart
/// "Exit trash"
/// ```
String get exitTrash => """Exit trash""";
/// ```dart
/// "Trash"
/// ```
String get trashTitle => """Trash""";
/// ```dart
/// "Trash is empty."
/// ```
String get noTrashedItems => """Trash is empty.""";
/// ```dart
/// "Empty trash"
/// ```
String get emptyTrash => """Empty trash""";
/// ```dart
/// "Empty the trash?"
/// ```
String get emptyTrashConfirm => """Empty the trash?""";
/// ```dart
/// "All items in the trash will be permanently deleted. This cannot be undone."
/// ```
String get emptyTrashConfirmBody =>
"""All items in the trash will be permanently deleted. This cannot be undone.""";
/// ```dart
/// "Failed to empty trash."
/// ```
String get emptyTrashFailed => """Failed to empty trash.""";
/// ```dart
/// "Restore"
/// ```
String get restoreItem => """Restore""";
/// ```dart
/// "Delete permanently"
/// ```
String get permanentlyDeleteItem => """Delete permanently""";
/// ```dart
/// "Permanently delete this item?"
/// ```
String get permanentlyDeleteConfirm => """Permanently delete this item?""";
/// ```dart
/// "This cannot be undone."
/// ```
String get permanentlyDeleteConfirmBody => """This cannot be undone.""";
/// ```dart
/// "Failed to restore item."
/// ```
String get restoreFailed => """Failed to restore item.""";
/// ```dart
/// "Failed to delete item."
/// ```
String get permanentlyDeleteFailed => """Failed to delete item.""";
/// ```dart
/// "Item restored"
/// ```
String get itemRestored => """Item restored""";
/// ```dart
/// "New list"
/// ```
@@ -1053,9 +1189,30 @@ class PhotoBoardMessages {
/// "$count"
/// ```
String photoCount(int count) => """$count""";
AddMenuPhotoBoardMessages get addMenu => AddMenuPhotoBoardMessages(this);
SortPhotoBoardMessages get sort => SortPhotoBoardMessages(this);
}
class AddMenuPhotoBoardMessages {
final PhotoBoardMessages _parent;
const AddMenuPhotoBoardMessages(this._parent);
/// ```dart
/// "Upload photos"
/// ```
String get upload => """Upload photos""";
/// ```dart
/// "Take photo"
/// ```
String get camera => """Take photo""";
/// ```dart
/// "New folder"
/// ```
String get newFolder => """New folder""";
}
class SortPhotoBoardMessages {
final PhotoBoardMessages _parent;
const SortPhotoBoardMessages(this._parent);
@@ -1091,6 +1248,56 @@ class SortPhotoBoardMessages {
String get custom => """Custom""";
}
class ShareMessages {
final Messages _parent;
const ShareMessages(this._parent);
/// ```dart
/// "Share to Pantry"
/// ```
String get title => """Share to Pantry""";
/// ```dart
/// "Choose house"
/// ```
String get chooseHouse => """Choose house""";
/// ```dart
/// "Upload to"
/// ```
String get choosePhotoDestination => """Upload to""";
/// ```dart
/// "Photo Board"
/// ```
String get photoBoardRoot => """Photo Board""";
/// ```dart
/// "New folder"
/// ```
String get newFolder => """New folder""";
/// ```dart
/// "Folder name"
/// ```
String get newFolderName => """Folder name""";
/// ```dart
/// "Failed to create folder."
/// ```
String get failedToCreateFolder => """Failed to create folder.""";
/// ```dart
/// "Could not open the shared content."
/// ```
String get failedToOpenShare => """Could not open the shared content.""";
/// ```dart
/// "No houses available. Create a house first."
/// ```
String get noHouses => """No houses available. Create a house first.""";
}
class RecurrenceMessages {
final Messages _parent;
const RecurrenceMessages(this._parent);
@@ -1390,6 +1597,17 @@ Please complete login in your browser.""",
"""about.feedback""": """Feedback & issues""",
"""settings.title""": """App Settings""",
"""settings.generalSection""": """General""",
"""settings.interfaceSection""": """Interface""",
"""settings.tapRowToComplete""": """Tap row to complete items""",
"""settings.tapRowToCompleteBody""":
"""When off, items are only marked complete by tapping the checkbox.""",
"""settings.categorySpacing""":
"""Show spacing between categories in list items""",
"""settings.categorySpacingBody""":
"""Only visible when sorting by category""",
"""settings.categorySpacingNames.disabled""": """Disabled""",
"""settings.categorySpacingNames.space""": """Space""",
"""settings.categorySpacingNames.divider""": """Divider""",
"""settings.language""": """Language""",
"""settings.languageNames.system""": """System default""",
"""settings.languageNames.english""": """English""",
@@ -1442,6 +1660,25 @@ Please complete login in your browser.""",
"""checklists.removeItem""": """Remove item""",
"""checklists.moveItem""": """Move to list""",
"""checklists.moveFailed""": """Failed to move item.""",
"""checklists.itemMarkedDone""": """Item marked as done""",
"""checklists.undo""": """Undo""",
"""checklists.viewTrash""": """View trash""",
"""checklists.exitTrash""": """Exit trash""",
"""checklists.trashTitle""": """Trash""",
"""checklists.noTrashedItems""": """Trash is empty.""",
"""checklists.emptyTrash""": """Empty trash""",
"""checklists.emptyTrashConfirm""": """Empty the trash?""",
"""checklists.emptyTrashConfirmBody""":
"""All items in the trash will be permanently deleted. This cannot be undone.""",
"""checklists.emptyTrashFailed""": """Failed to empty trash.""",
"""checklists.restoreItem""": """Restore""",
"""checklists.permanentlyDeleteItem""": """Delete permanently""",
"""checklists.permanentlyDeleteConfirm""":
"""Permanently delete this item?""",
"""checklists.permanentlyDeleteConfirmBody""": """This cannot be undone.""",
"""checklists.restoreFailed""": """Failed to restore item.""",
"""checklists.permanentlyDeleteFailed""": """Failed to delete item.""",
"""checklists.itemRestored""": """Item restored""",
"""checklists.createList""": """New list""",
"""checklists.listName""": """List name""",
"""checklists.listDescription""": """Description (optional)""",
@@ -1514,12 +1751,24 @@ Please complete login in your browser.""",
"""photoBoard.folderName""": """Folder name""",
"""photoBoard.renameFolder""": """Rename folder""",
"""photoBoard.caption""": """Caption""",
"""photoBoard.addMenu.upload""": """Upload photos""",
"""photoBoard.addMenu.camera""": """Take photo""",
"""photoBoard.addMenu.newFolder""": """New folder""",
"""photoBoard.sort.foldersFirst""": """Folders first""",
"""photoBoard.sort.newestFirst""": """Newest first""",
"""photoBoard.sort.oldestFirst""": """Oldest first""",
"""photoBoard.sort.captionAZ""": """Caption AZ""",
"""photoBoard.sort.captionZA""": """Caption ZA""",
"""photoBoard.sort.custom""": """Custom""",
"""share.title""": """Share to Pantry""",
"""share.chooseHouse""": """Choose house""",
"""share.choosePhotoDestination""": """Upload to""",
"""share.photoBoardRoot""": """Photo Board""",
"""share.newFolder""": """New folder""",
"""share.newFolderName""": """Folder name""",
"""share.failedToCreateFolder""": """Failed to create folder.""",
"""share.failedToOpenShare""": """Could not open the shared content.""",
"""share.noHouses""": """No houses available. Create a house first.""",
"""recurrence.title""": """Recurrence""",
"""recurrence.presets""": """Presets""",
"""recurrence.daily""": """Daily""",

View File

@@ -59,6 +59,15 @@ about:
settings:
title: App Settings
generalSection: General
interfaceSection: Interface
tapRowToComplete: Tap row to complete items
tapRowToCompleteBody: "When off, items are only marked complete by tapping the checkbox."
categorySpacing: Show spacing between categories in list items
categorySpacingBody: Only visible when sorting by category
categorySpacingNames:
disabled: Disabled
space: Space
divider: Divider
language: Language
languageNames:
system: "System default"
@@ -120,6 +129,23 @@ checklists:
removeItem: Remove item
moveItem: Move to list
moveFailed: Failed to move item.
itemMarkedDone: Item marked as done
undo: Undo
viewTrash: View trash
exitTrash: Exit trash
trashTitle: Trash
noTrashedItems: Trash is empty.
emptyTrash: Empty trash
emptyTrashConfirm: Empty the trash?
emptyTrashConfirmBody: All items in the trash will be permanently deleted. This cannot be undone.
emptyTrashFailed: Failed to empty trash.
restoreItem: Restore
permanentlyDeleteItem: Delete permanently
permanentlyDeleteConfirm: Permanently delete this item?
permanentlyDeleteConfirmBody: This cannot be undone.
restoreFailed: Failed to restore item.
permanentlyDeleteFailed: Failed to delete item.
itemRestored: Item restored
createList: New list
listName: List name
listDescription: Description (optional)
@@ -200,6 +226,10 @@ photoBoard:
renameFolder: Rename folder
caption: Caption
photoCount(int count): "$count"
addMenu:
upload: Upload photos
camera: Take photo
newFolder: New folder
sort:
foldersFirst: Folders first
newestFirst: Newest first
@@ -208,6 +238,17 @@ photoBoard:
captionZA: "Caption ZA"
custom: Custom
share:
title: Share to Pantry
chooseHouse: Choose house
choosePhotoDestination: Upload to
photoBoardRoot: Photo Board
newFolder: New folder
newFolderName: Folder name
failedToCreateFolder: Failed to create folder.
failedToOpenShare: Could not open the shared content.
noHouses: No houses available. Create a house first.
recurrence:
title: Recurrence
presets: Presets

View File

@@ -76,6 +76,7 @@ class MessagesDe extends Messages {
ChecklistsMessagesDe get checklists => ChecklistsMessagesDe(this);
NotesWallMessagesDe get notesWall => NotesWallMessagesDe(this);
PhotoBoardMessagesDe get photoBoard => PhotoBoardMessagesDe(this);
ShareMessagesDe get share => ShareMessagesDe(this);
RecurrenceMessagesDe get recurrence => RecurrenceMessagesDe(this);
}
@@ -363,6 +364,36 @@ class SettingsMessagesDe extends SettingsMessages {
/// ```
String get generalSection => """Allgemein""";
/// ```dart
/// "Oberfläche"
/// ```
String get interfaceSection => """Oberfläche""";
/// ```dart
/// "Eintrag durch Tippen der Zeile abhaken"
/// ```
String get tapRowToComplete => """Eintrag durch Tippen der Zeile abhaken""";
/// ```dart
/// "Wenn aus, werden Einträge nur durch Tippen auf das Kontrollkästchen abgehakt."
/// ```
String get tapRowToCompleteBody =>
"""Wenn aus, werden Einträge nur durch Tippen auf das Kontrollkästchen abgehakt.""";
/// ```dart
/// "Abstand zwischen Kategorien in Listeneinträgen anzeigen"
/// ```
String get categorySpacing =>
"""Abstand zwischen Kategorien in Listeneinträgen anzeigen""";
/// ```dart
/// "Nur sichtbar bei Sortierung nach Kategorie"
/// ```
String get categorySpacingBody =>
"""Nur sichtbar bei Sortierung nach Kategorie""";
CategorySpacingNamesSettingsMessagesDe get categorySpacingNames =>
CategorySpacingNamesSettingsMessagesDe(this);
/// ```dart
/// "Sprache"
/// ```
@@ -430,6 +461,27 @@ class SettingsMessagesDe extends SettingsMessages {
"""Benachrichtigungsberechtigung wurde verweigert. Aktiviere sie in den Systemeinstellungen.""";
}
class CategorySpacingNamesSettingsMessagesDe
extends CategorySpacingNamesSettingsMessages {
final SettingsMessagesDe _parent;
const CategorySpacingNamesSettingsMessagesDe(this._parent) : super(_parent);
/// ```dart
/// "Deaktiviert"
/// ```
String get disabled => """Deaktiviert""";
/// ```dart
/// "Abstand"
/// ```
String get space => """Abstand""";
/// ```dart
/// "Trennlinie"
/// ```
String get divider => """Trennlinie""";
}
class LanguageNamesSettingsMessagesDe extends LanguageNamesSettingsMessages {
final SettingsMessagesDe _parent;
const LanguageNamesSettingsMessagesDe(this._parent) : super(_parent);
@@ -661,6 +713,96 @@ class ChecklistsMessagesDe extends ChecklistsMessages {
/// ```
String get moveFailed => """Eintrag konnte nicht verschoben werden.""";
/// ```dart
/// "Eintrag als erledigt markiert"
/// ```
String get itemMarkedDone => """Eintrag als erledigt markiert""";
/// ```dart
/// "Rückgängig"
/// ```
String get undo => """Rückgängig""";
/// ```dart
/// "Papierkorb anzeigen"
/// ```
String get viewTrash => """Papierkorb anzeigen""";
/// ```dart
/// "Papierkorb verlassen"
/// ```
String get exitTrash => """Papierkorb verlassen""";
/// ```dart
/// "Papierkorb"
/// ```
String get trashTitle => """Papierkorb""";
/// ```dart
/// "Der Papierkorb ist leer."
/// ```
String get noTrashedItems => """Der Papierkorb ist leer.""";
/// ```dart
/// "Papierkorb leeren"
/// ```
String get emptyTrash => """Papierkorb leeren""";
/// ```dart
/// "Papierkorb leeren?"
/// ```
String get emptyTrashConfirm => """Papierkorb leeren?""";
/// ```dart
/// "Alle Einträge im Papierkorb werden endgültig gelöscht. Dies kann nicht rückgängig gemacht werden."
/// ```
String get emptyTrashConfirmBody =>
"""Alle Einträge im Papierkorb werden endgültig gelöscht. Dies kann nicht rückgängig gemacht werden.""";
/// ```dart
/// "Papierkorb konnte nicht geleert werden."
/// ```
String get emptyTrashFailed => """Papierkorb konnte nicht geleert werden.""";
/// ```dart
/// "Wiederherstellen"
/// ```
String get restoreItem => """Wiederherstellen""";
/// ```dart
/// "Endgültig löschen"
/// ```
String get permanentlyDeleteItem => """Endgültig löschen""";
/// ```dart
/// "Diesen Eintrag endgültig löschen?"
/// ```
String get permanentlyDeleteConfirm =>
"""Diesen Eintrag endgültig löschen?""";
/// ```dart
/// "Dies kann nicht rückgängig gemacht werden."
/// ```
String get permanentlyDeleteConfirmBody =>
"""Dies kann nicht rückgängig gemacht werden.""";
/// ```dart
/// "Eintrag konnte nicht wiederhergestellt werden."
/// ```
String get restoreFailed =>
"""Eintrag konnte nicht wiederhergestellt werden.""";
/// ```dart
/// "Eintrag konnte nicht gelöscht werden."
/// ```
String get permanentlyDeleteFailed =>
"""Eintrag konnte nicht gelöscht werden.""";
/// ```dart
/// "Eintrag wiederhergestellt"
/// ```
String get itemRestored => """Eintrag wiederhergestellt""";
/// ```dart
/// "Neue Liste"
/// ```
@@ -1062,9 +1204,30 @@ class PhotoBoardMessagesDe extends PhotoBoardMessages {
/// "$count"
/// ```
String photoCount(int count) => """$count""";
AddMenuPhotoBoardMessagesDe get addMenu => AddMenuPhotoBoardMessagesDe(this);
SortPhotoBoardMessagesDe get sort => SortPhotoBoardMessagesDe(this);
}
class AddMenuPhotoBoardMessagesDe extends AddMenuPhotoBoardMessages {
final PhotoBoardMessagesDe _parent;
const AddMenuPhotoBoardMessagesDe(this._parent) : super(_parent);
/// ```dart
/// "Fotos hochladen"
/// ```
String get upload => """Fotos hochladen""";
/// ```dart
/// "Foto aufnehmen"
/// ```
String get camera => """Foto aufnehmen""";
/// ```dart
/// "Neuer Ordner"
/// ```
String get newFolder => """Neuer Ordner""";
}
class SortPhotoBoardMessagesDe extends SortPhotoBoardMessages {
final PhotoBoardMessagesDe _parent;
const SortPhotoBoardMessagesDe(this._parent) : super(_parent);
@@ -1100,6 +1263,58 @@ class SortPhotoBoardMessagesDe extends SortPhotoBoardMessages {
String get custom => """Benutzerdefiniert""";
}
class ShareMessagesDe extends ShareMessages {
final MessagesDe _parent;
const ShareMessagesDe(this._parent) : super(_parent);
/// ```dart
/// "An Pantry senden"
/// ```
String get title => """An Pantry senden""";
/// ```dart
/// "Haus auswählen"
/// ```
String get chooseHouse => """Haus auswählen""";
/// ```dart
/// "Hochladen nach"
/// ```
String get choosePhotoDestination => """Hochladen nach""";
/// ```dart
/// "Fotowand"
/// ```
String get photoBoardRoot => """Fotowand""";
/// ```dart
/// "Neuer Ordner"
/// ```
String get newFolder => """Neuer Ordner""";
/// ```dart
/// "Ordnername"
/// ```
String get newFolderName => """Ordnername""";
/// ```dart
/// "Ordner konnte nicht erstellt werden."
/// ```
String get failedToCreateFolder => """Ordner konnte nicht erstellt werden.""";
/// ```dart
/// "Der geteilte Inhalt konnte nicht geöffnet werden."
/// ```
String get failedToOpenShare =>
"""Der geteilte Inhalt konnte nicht geöffnet werden.""";
/// ```dart
/// "Keine Häuser verfügbar. Erstelle zuerst ein Haus."
/// ```
String get noHouses =>
"""Keine Häuser verfügbar. Erstelle zuerst ein Haus.""";
}
class RecurrenceMessagesDe extends RecurrenceMessages {
final MessagesDe _parent;
const RecurrenceMessagesDe(this._parent) : super(_parent);
@@ -1403,6 +1618,17 @@ Bitte melde dich in deinem Browser an.""",
"""about.feedback""": """Feedback & Probleme""",
"""settings.title""": """App-Einstellungen""",
"""settings.generalSection""": """Allgemein""",
"""settings.interfaceSection""": """Oberfläche""",
"""settings.tapRowToComplete""": """Eintrag durch Tippen der Zeile abhaken""",
"""settings.tapRowToCompleteBody""":
"""Wenn aus, werden Einträge nur durch Tippen auf das Kontrollkästchen abgehakt.""",
"""settings.categorySpacing""":
"""Abstand zwischen Kategorien in Listeneinträgen anzeigen""",
"""settings.categorySpacingBody""":
"""Nur sichtbar bei Sortierung nach Kategorie""",
"""settings.categorySpacingNames.disabled""": """Deaktiviert""",
"""settings.categorySpacingNames.space""": """Abstand""",
"""settings.categorySpacingNames.divider""": """Trennlinie""",
"""settings.language""": """Sprache""",
"""settings.languageNames.system""": """Systemstandard""",
"""settings.languageNames.english""": """English""",
@@ -1459,6 +1685,29 @@ Bitte melde dich in deinem Browser an.""",
"""checklists.removeItem""": """Eintrag entfernen""",
"""checklists.moveItem""": """In Liste verschieben""",
"""checklists.moveFailed""": """Eintrag konnte nicht verschoben werden.""",
"""checklists.itemMarkedDone""": """Eintrag als erledigt markiert""",
"""checklists.undo""": """Rückgängig""",
"""checklists.viewTrash""": """Papierkorb anzeigen""",
"""checklists.exitTrash""": """Papierkorb verlassen""",
"""checklists.trashTitle""": """Papierkorb""",
"""checklists.noTrashedItems""": """Der Papierkorb ist leer.""",
"""checklists.emptyTrash""": """Papierkorb leeren""",
"""checklists.emptyTrashConfirm""": """Papierkorb leeren?""",
"""checklists.emptyTrashConfirmBody""":
"""Alle Einträge im Papierkorb werden endgültig gelöscht. Dies kann nicht rückgängig gemacht werden.""",
"""checklists.emptyTrashFailed""":
"""Papierkorb konnte nicht geleert werden.""",
"""checklists.restoreItem""": """Wiederherstellen""",
"""checklists.permanentlyDeleteItem""": """Endgültig löschen""",
"""checklists.permanentlyDeleteConfirm""":
"""Diesen Eintrag endgültig löschen?""",
"""checklists.permanentlyDeleteConfirmBody""":
"""Dies kann nicht rückgängig gemacht werden.""",
"""checklists.restoreFailed""":
"""Eintrag konnte nicht wiederhergestellt werden.""",
"""checklists.permanentlyDeleteFailed""":
"""Eintrag konnte nicht gelöscht werden.""",
"""checklists.itemRestored""": """Eintrag wiederhergestellt""",
"""checklists.createList""": """Neue Liste""",
"""checklists.listName""": """Listenname""",
"""checklists.listDescription""": """Beschreibung (optional)""",
@@ -1534,12 +1783,25 @@ Bitte melde dich in deinem Browser an.""",
"""photoBoard.folderName""": """Ordnername""",
"""photoBoard.renameFolder""": """Ordner umbenennen""",
"""photoBoard.caption""": """Beschriftung""",
"""photoBoard.addMenu.upload""": """Fotos hochladen""",
"""photoBoard.addMenu.camera""": """Foto aufnehmen""",
"""photoBoard.addMenu.newFolder""": """Neuer Ordner""",
"""photoBoard.sort.foldersFirst""": """Ordner zuerst""",
"""photoBoard.sort.newestFirst""": """Neueste zuerst""",
"""photoBoard.sort.oldestFirst""": """Älteste zuerst""",
"""photoBoard.sort.captionAZ""": """Beschriftung AZ""",
"""photoBoard.sort.captionZA""": """Beschriftung ZA""",
"""photoBoard.sort.custom""": """Benutzerdefiniert""",
"""share.title""": """An Pantry senden""",
"""share.chooseHouse""": """Haus auswählen""",
"""share.choosePhotoDestination""": """Hochladen nach""",
"""share.photoBoardRoot""": """Fotowand""",
"""share.newFolder""": """Neuer Ordner""",
"""share.newFolderName""": """Ordnername""",
"""share.failedToCreateFolder""": """Ordner konnte nicht erstellt werden.""",
"""share.failedToOpenShare""":
"""Der geteilte Inhalt konnte nicht geöffnet werden.""",
"""share.noHouses""": """Keine Häuser verfügbar. Erstelle zuerst ein Haus.""",
"""recurrence.title""": """Wiederholung""",
"""recurrence.presets""": """Voreinstellungen""",
"""recurrence.daily""": """Täglich""",

View File

@@ -59,6 +59,15 @@ about:
settings:
title: App-Einstellungen
generalSection: Allgemein
interfaceSection: Oberfläche
tapRowToComplete: Eintrag durch Tippen der Zeile abhaken
tapRowToCompleteBody: "Wenn aus, werden Einträge nur durch Tippen auf das Kontrollkästchen abgehakt."
categorySpacing: Abstand zwischen Kategorien in Listeneinträgen anzeigen
categorySpacingBody: Nur sichtbar bei Sortierung nach Kategorie
categorySpacingNames:
disabled: Deaktiviert
space: Abstand
divider: Trennlinie
language: Sprache
languageNames:
system: Systemstandard
@@ -120,6 +129,23 @@ checklists:
removeItem: Eintrag entfernen
moveItem: In Liste verschieben
moveFailed: Eintrag konnte nicht verschoben werden.
itemMarkedDone: Eintrag als erledigt markiert
undo: "Rückgängig"
viewTrash: Papierkorb anzeigen
exitTrash: Papierkorb verlassen
trashTitle: Papierkorb
noTrashedItems: Der Papierkorb ist leer.
emptyTrash: Papierkorb leeren
emptyTrashConfirm: Papierkorb leeren?
emptyTrashConfirmBody: "Alle Einträge im Papierkorb werden endgültig gelöscht. Dies kann nicht rückgängig gemacht werden."
emptyTrashFailed: Papierkorb konnte nicht geleert werden.
restoreItem: Wiederherstellen
permanentlyDeleteItem: Endgültig löschen
permanentlyDeleteConfirm: Diesen Eintrag endgültig löschen?
permanentlyDeleteConfirmBody: "Dies kann nicht rückgängig gemacht werden."
restoreFailed: Eintrag konnte nicht wiederhergestellt werden.
permanentlyDeleteFailed: Eintrag konnte nicht gelöscht werden.
itemRestored: Eintrag wiederhergestellt
createList: Neue Liste
listName: Listenname
listDescription: Beschreibung (optional)
@@ -200,6 +226,10 @@ photoBoard:
renameFolder: Ordner umbenennen
caption: Beschriftung
photoCount(int count): "$count"
addMenu:
upload: Fotos hochladen
camera: Foto aufnehmen
newFolder: Neuer Ordner
sort:
foldersFirst: Ordner zuerst
newestFirst: Neueste zuerst
@@ -208,6 +238,17 @@ photoBoard:
captionZA: "Beschriftung ZA"
custom: Benutzerdefiniert
share:
title: An Pantry senden
chooseHouse: Haus auswählen
choosePhotoDestination: Hochladen nach
photoBoardRoot: Fotowand
newFolder: Neuer Ordner
newFolderName: Ordnername
failedToCreateFolder: Ordner konnte nicht erstellt werden.
failedToOpenShare: Der geteilte Inhalt konnte nicht geöffnet werden.
noHouses: Keine Häuser verfügbar. Erstelle zuerst ein Haus.
recurrence:
title: Wiederholung
presets: Voreinstellungen

View File

@@ -76,6 +76,7 @@ class MessagesEs extends Messages {
ChecklistsMessagesEs get checklists => ChecklistsMessagesEs(this);
NotesWallMessagesEs get notesWall => NotesWallMessagesEs(this);
PhotoBoardMessagesEs get photoBoard => PhotoBoardMessagesEs(this);
ShareMessagesEs get share => ShareMessagesEs(this);
RecurrenceMessagesEs get recurrence => RecurrenceMessagesEs(this);
}
@@ -363,6 +364,35 @@ class SettingsMessagesEs extends SettingsMessages {
/// ```
String get generalSection => """General""";
/// ```dart
/// "Interfaz"
/// ```
String get interfaceSection => """Interfaz""";
/// ```dart
/// "Tocar la fila para completar elementos"
/// ```
String get tapRowToComplete => """Tocar la fila para completar elementos""";
/// ```dart
/// "Cuando está desactivado, los elementos solo se marcan como completados al tocar la casilla."
/// ```
String get tapRowToCompleteBody =>
"""Cuando está desactivado, los elementos solo se marcan como completados al tocar la casilla.""";
/// ```dart
/// "Mostrar espacio entre categorías en los elementos de la lista"
/// ```
String get categorySpacing =>
"""Mostrar espacio entre categorías en los elementos de la lista""";
/// ```dart
/// "Solo visible al ordenar por categoría"
/// ```
String get categorySpacingBody => """Solo visible al ordenar por categoría""";
CategorySpacingNamesSettingsMessagesEs get categorySpacingNames =>
CategorySpacingNamesSettingsMessagesEs(this);
/// ```dart
/// "Idioma"
/// ```
@@ -430,6 +460,27 @@ class SettingsMessagesEs extends SettingsMessages {
"""El permiso de notificaciones fue denegado. Actívalo en los ajustes del sistema.""";
}
class CategorySpacingNamesSettingsMessagesEs
extends CategorySpacingNamesSettingsMessages {
final SettingsMessagesEs _parent;
const CategorySpacingNamesSettingsMessagesEs(this._parent) : super(_parent);
/// ```dart
/// "Desactivado"
/// ```
String get disabled => """Desactivado""";
/// ```dart
/// "Espacio"
/// ```
String get space => """Espacio""";
/// ```dart
/// "Separador"
/// ```
String get divider => """Separador""";
}
class LanguageNamesSettingsMessagesEs extends LanguageNamesSettingsMessages {
final SettingsMessagesEs _parent;
const LanguageNamesSettingsMessagesEs(this._parent) : super(_parent);
@@ -660,6 +711,94 @@ class ChecklistsMessagesEs extends ChecklistsMessages {
/// ```
String get moveFailed => """No se pudo mover el artículo.""";
/// ```dart
/// "Artículo marcado como hecho"
/// ```
String get itemMarkedDone => """Artículo marcado como hecho""";
/// ```dart
/// "Deshacer"
/// ```
String get undo => """Deshacer""";
/// ```dart
/// "Ver papelera"
/// ```
String get viewTrash => """Ver papelera""";
/// ```dart
/// "Salir de la papelera"
/// ```
String get exitTrash => """Salir de la papelera""";
/// ```dart
/// "Papelera"
/// ```
String get trashTitle => """Papelera""";
/// ```dart
/// "La papelera está vacía."
/// ```
String get noTrashedItems => """La papelera está vacía.""";
/// ```dart
/// "Vaciar papelera"
/// ```
String get emptyTrash => """Vaciar papelera""";
/// ```dart
/// "¿Vaciar la papelera?"
/// ```
String get emptyTrashConfirm => """¿Vaciar la papelera?""";
/// ```dart
/// "Todos los artículos de la papelera se eliminarán permanentemente. Esta acción no se puede deshacer."
/// ```
String get emptyTrashConfirmBody =>
"""Todos los artículos de la papelera se eliminarán permanentemente. Esta acción no se puede deshacer.""";
/// ```dart
/// "No se pudo vaciar la papelera."
/// ```
String get emptyTrashFailed => """No se pudo vaciar la papelera.""";
/// ```dart
/// "Restaurar"
/// ```
String get restoreItem => """Restaurar""";
/// ```dart
/// "Eliminar permanentemente"
/// ```
String get permanentlyDeleteItem => """Eliminar permanentemente""";
/// ```dart
/// "¿Eliminar este artículo permanentemente?"
/// ```
String get permanentlyDeleteConfirm =>
"""¿Eliminar este artículo permanentemente?""";
/// ```dart
/// "Esta acción no se puede deshacer."
/// ```
String get permanentlyDeleteConfirmBody =>
"""Esta acción no se puede deshacer.""";
/// ```dart
/// "No se pudo restaurar el artículo."
/// ```
String get restoreFailed => """No se pudo restaurar el artículo.""";
/// ```dart
/// "No se pudo eliminar el artículo."
/// ```
String get permanentlyDeleteFailed => """No se pudo eliminar el artículo.""";
/// ```dart
/// "Artículo restaurado"
/// ```
String get itemRestored => """Artículo restaurado""";
/// ```dart
/// "Nueva lista"
/// ```
@@ -1059,9 +1198,30 @@ class PhotoBoardMessagesEs extends PhotoBoardMessages {
/// "$count"
/// ```
String photoCount(int count) => """$count""";
AddMenuPhotoBoardMessagesEs get addMenu => AddMenuPhotoBoardMessagesEs(this);
SortPhotoBoardMessagesEs get sort => SortPhotoBoardMessagesEs(this);
}
class AddMenuPhotoBoardMessagesEs extends AddMenuPhotoBoardMessages {
final PhotoBoardMessagesEs _parent;
const AddMenuPhotoBoardMessagesEs(this._parent) : super(_parent);
/// ```dart
/// "Subir fotos"
/// ```
String get upload => """Subir fotos""";
/// ```dart
/// "Tomar foto"
/// ```
String get camera => """Tomar foto""";
/// ```dart
/// "Nueva carpeta"
/// ```
String get newFolder => """Nueva carpeta""";
}
class SortPhotoBoardMessagesEs extends SortPhotoBoardMessages {
final PhotoBoardMessagesEs _parent;
const SortPhotoBoardMessagesEs(this._parent) : super(_parent);
@@ -1097,6 +1257,57 @@ class SortPhotoBoardMessagesEs extends SortPhotoBoardMessages {
String get custom => """Personalizado""";
}
class ShareMessagesEs extends ShareMessages {
final MessagesEs _parent;
const ShareMessagesEs(this._parent) : super(_parent);
/// ```dart
/// "Compartir con Pantry"
/// ```
String get title => """Compartir con Pantry""";
/// ```dart
/// "Elegir casa"
/// ```
String get chooseHouse => """Elegir casa""";
/// ```dart
/// "Subir a"
/// ```
String get choosePhotoDestination => """Subir a""";
/// ```dart
/// "Tablón de fotos"
/// ```
String get photoBoardRoot => """Tablón de fotos""";
/// ```dart
/// "Nueva carpeta"
/// ```
String get newFolder => """Nueva carpeta""";
/// ```dart
/// "Nombre de la carpeta"
/// ```
String get newFolderName => """Nombre de la carpeta""";
/// ```dart
/// "No se pudo crear la carpeta."
/// ```
String get failedToCreateFolder => """No se pudo crear la carpeta.""";
/// ```dart
/// "No se pudo abrir el contenido compartido."
/// ```
String get failedToOpenShare =>
"""No se pudo abrir el contenido compartido.""";
/// ```dart
/// "No hay casas disponibles. Crea una casa primero."
/// ```
String get noHouses => """No hay casas disponibles. Crea una casa primero.""";
}
class RecurrenceMessagesEs extends RecurrenceMessages {
final MessagesEs _parent;
const RecurrenceMessagesEs(this._parent) : super(_parent);
@@ -1398,6 +1609,17 @@ Por favor, completa el inicio de sesión en tu navegador.""",
"""about.feedback""": """Comentarios y problemas""",
"""settings.title""": """Ajustes de la app""",
"""settings.generalSection""": """General""",
"""settings.interfaceSection""": """Interfaz""",
"""settings.tapRowToComplete""": """Tocar la fila para completar elementos""",
"""settings.tapRowToCompleteBody""":
"""Cuando está desactivado, los elementos solo se marcan como completados al tocar la casilla.""",
"""settings.categorySpacing""":
"""Mostrar espacio entre categorías en los elementos de la lista""",
"""settings.categorySpacingBody""":
"""Solo visible al ordenar por categoría""",
"""settings.categorySpacingNames.disabled""": """Desactivado""",
"""settings.categorySpacingNames.space""": """Espacio""",
"""settings.categorySpacingNames.divider""": """Separador""",
"""settings.language""": """Idioma""",
"""settings.languageNames.system""": """Predeterminado del sistema""",
"""settings.languageNames.english""": """English""",
@@ -1453,6 +1675,27 @@ Por favor, completa el inicio de sesión en tu navegador.""",
"""checklists.removeItem""": """Eliminar artículo""",
"""checklists.moveItem""": """Mover a lista""",
"""checklists.moveFailed""": """No se pudo mover el artículo.""",
"""checklists.itemMarkedDone""": """Artículo marcado como hecho""",
"""checklists.undo""": """Deshacer""",
"""checklists.viewTrash""": """Ver papelera""",
"""checklists.exitTrash""": """Salir de la papelera""",
"""checklists.trashTitle""": """Papelera""",
"""checklists.noTrashedItems""": """La papelera está vacía.""",
"""checklists.emptyTrash""": """Vaciar papelera""",
"""checklists.emptyTrashConfirm""": """¿Vaciar la papelera?""",
"""checklists.emptyTrashConfirmBody""":
"""Todos los artículos de la papelera se eliminarán permanentemente. Esta acción no se puede deshacer.""",
"""checklists.emptyTrashFailed""": """No se pudo vaciar la papelera.""",
"""checklists.restoreItem""": """Restaurar""",
"""checklists.permanentlyDeleteItem""": """Eliminar permanentemente""",
"""checklists.permanentlyDeleteConfirm""":
"""¿Eliminar este artículo permanentemente?""",
"""checklists.permanentlyDeleteConfirmBody""":
"""Esta acción no se puede deshacer.""",
"""checklists.restoreFailed""": """No se pudo restaurar el artículo.""",
"""checklists.permanentlyDeleteFailed""":
"""No se pudo eliminar el artículo.""",
"""checklists.itemRestored""": """Artículo restaurado""",
"""checklists.createList""": """Nueva lista""",
"""checklists.listName""": """Nombre de la lista""",
"""checklists.listDescription""": """Descripción (opcional)""",
@@ -1526,12 +1769,25 @@ Por favor, completa el inicio de sesión en tu navegador.""",
"""photoBoard.folderName""": """Nombre de la carpeta""",
"""photoBoard.renameFolder""": """Renombrar carpeta""",
"""photoBoard.caption""": """Descripción""",
"""photoBoard.addMenu.upload""": """Subir fotos""",
"""photoBoard.addMenu.camera""": """Tomar foto""",
"""photoBoard.addMenu.newFolder""": """Nueva carpeta""",
"""photoBoard.sort.foldersFirst""": """Carpetas primero""",
"""photoBoard.sort.newestFirst""": """Más recientes""",
"""photoBoard.sort.oldestFirst""": """Más antiguos""",
"""photoBoard.sort.captionAZ""": """Descripción AZ""",
"""photoBoard.sort.captionZA""": """Descripción ZA""",
"""photoBoard.sort.custom""": """Personalizado""",
"""share.title""": """Compartir con Pantry""",
"""share.chooseHouse""": """Elegir casa""",
"""share.choosePhotoDestination""": """Subir a""",
"""share.photoBoardRoot""": """Tablón de fotos""",
"""share.newFolder""": """Nueva carpeta""",
"""share.newFolderName""": """Nombre de la carpeta""",
"""share.failedToCreateFolder""": """No se pudo crear la carpeta.""",
"""share.failedToOpenShare""":
"""No se pudo abrir el contenido compartido.""",
"""share.noHouses""": """No hay casas disponibles. Crea una casa primero.""",
"""recurrence.title""": """Recurrencia""",
"""recurrence.presets""": """Preajustes""",
"""recurrence.daily""": """Diario""",

View File

@@ -59,6 +59,15 @@ about:
settings:
title: Ajustes de la app
generalSection: General
interfaceSection: Interfaz
tapRowToComplete: Tocar la fila para completar elementos
tapRowToCompleteBody: "Cuando está desactivado, los elementos solo se marcan como completados al tocar la casilla."
categorySpacing: Mostrar espacio entre categorías en los elementos de la lista
categorySpacingBody: Solo visible al ordenar por categoría
categorySpacingNames:
disabled: Desactivado
space: Espacio
divider: Separador
language: Idioma
languageNames:
system: Predeterminado del sistema
@@ -120,6 +129,23 @@ checklists:
removeItem: "Eliminar artículo"
moveItem: Mover a lista
moveFailed: "No se pudo mover el artículo."
itemMarkedDone: "Artículo marcado como hecho"
undo: Deshacer
viewTrash: Ver papelera
exitTrash: Salir de la papelera
trashTitle: Papelera
noTrashedItems: La papelera está vacía.
emptyTrash: Vaciar papelera
emptyTrashConfirm: ¿Vaciar la papelera?
emptyTrashConfirmBody: "Todos los artículos de la papelera se eliminarán permanentemente. Esta acción no se puede deshacer."
emptyTrashFailed: No se pudo vaciar la papelera.
restoreItem: Restaurar
permanentlyDeleteItem: Eliminar permanentemente
permanentlyDeleteConfirm: "¿Eliminar este artículo permanentemente?"
permanentlyDeleteConfirmBody: Esta acción no se puede deshacer.
restoreFailed: No se pudo restaurar el artículo.
permanentlyDeleteFailed: No se pudo eliminar el artículo.
itemRestored: Artículo restaurado
createList: Nueva lista
listName: Nombre de la lista
listDescription: "Descripción (opcional)"
@@ -200,6 +226,10 @@ photoBoard:
renameFolder: Renombrar carpeta
caption: "Descripción"
photoCount(int count): "$count"
addMenu:
upload: Subir fotos
camera: Tomar foto
newFolder: Nueva carpeta
sort:
foldersFirst: Carpetas primero
newestFirst: "Más recientes"
@@ -208,6 +238,17 @@ photoBoard:
captionZA: "Descripción ZA"
custom: Personalizado
share:
title: Compartir con Pantry
chooseHouse: Elegir casa
choosePhotoDestination: Subir a
photoBoardRoot: Tablón de fotos
newFolder: Nueva carpeta
newFolderName: Nombre de la carpeta
failedToCreateFolder: No se pudo crear la carpeta.
failedToOpenShare: No se pudo abrir el contenido compartido.
noHouses: No hay casas disponibles. Crea una casa primero.
recurrence:
title: Recurrencia
presets: Preajustes

View File

@@ -76,6 +76,7 @@ class MessagesFr extends Messages {
ChecklistsMessagesFr get checklists => ChecklistsMessagesFr(this);
NotesWallMessagesFr get notesWall => NotesWallMessagesFr(this);
PhotoBoardMessagesFr get photoBoard => PhotoBoardMessagesFr(this);
ShareMessagesFr get share => ShareMessagesFr(this);
RecurrenceMessagesFr get recurrence => RecurrenceMessagesFr(this);
}
@@ -363,6 +364,37 @@ class SettingsMessagesFr extends SettingsMessages {
/// ```
String get generalSection => """Général""";
/// ```dart
/// "Interface"
/// ```
String get interfaceSection => """Interface""";
/// ```dart
/// "Toucher la ligne pour cocher les éléments"
/// ```
String get tapRowToComplete =>
"""Toucher la ligne pour cocher les éléments""";
/// ```dart
/// "Quand désactivé, les éléments ne sont cochés qu'en touchant la case."
/// ```
String get tapRowToCompleteBody =>
"""Quand désactivé, les éléments ne sont cochés qu'en touchant la case.""";
/// ```dart
/// "Afficher un espacement entre les catégories dans les éléments de la liste"
/// ```
String get categorySpacing =>
"""Afficher un espacement entre les catégories dans les éléments de la liste""";
/// ```dart
/// "Visible uniquement lors du tri par catégorie"
/// ```
String get categorySpacingBody =>
"""Visible uniquement lors du tri par catégorie""";
CategorySpacingNamesSettingsMessagesFr get categorySpacingNames =>
CategorySpacingNamesSettingsMessagesFr(this);
/// ```dart
/// "Langue"
/// ```
@@ -430,6 +462,27 @@ class SettingsMessagesFr extends SettingsMessages {
"""La permission de notification a été refusée. Activez-la dans les réglages système.""";
}
class CategorySpacingNamesSettingsMessagesFr
extends CategorySpacingNamesSettingsMessages {
final SettingsMessagesFr _parent;
const CategorySpacingNamesSettingsMessagesFr(this._parent) : super(_parent);
/// ```dart
/// "Désactivé"
/// ```
String get disabled => """Désactivé""";
/// ```dart
/// "Espace"
/// ```
String get space => """Espace""";
/// ```dart
/// "Séparateur"
/// ```
String get divider => """Séparateur""";
}
class LanguageNamesSettingsMessagesFr extends LanguageNamesSettingsMessages {
final SettingsMessagesFr _parent;
const LanguageNamesSettingsMessagesFr(this._parent) : super(_parent);
@@ -661,6 +714,95 @@ class ChecklistsMessagesFr extends ChecklistsMessages {
/// ```
String get moveFailed => """Impossible de déplacer l'article.""";
/// ```dart
/// "Article marqué comme fait"
/// ```
String get itemMarkedDone => """Article marqué comme fait""";
/// ```dart
/// "Annuler"
/// ```
String get undo => """Annuler""";
/// ```dart
/// "Afficher la corbeille"
/// ```
String get viewTrash => """Afficher la corbeille""";
/// ```dart
/// "Quitter la corbeille"
/// ```
String get exitTrash => """Quitter la corbeille""";
/// ```dart
/// "Corbeille"
/// ```
String get trashTitle => """Corbeille""";
/// ```dart
/// "La corbeille est vide."
/// ```
String get noTrashedItems => """La corbeille est vide.""";
/// ```dart
/// "Vider la corbeille"
/// ```
String get emptyTrash => """Vider la corbeille""";
/// ```dart
/// "Vider la corbeille ?"
/// ```
String get emptyTrashConfirm => """Vider la corbeille ?""";
/// ```dart
/// "Tous les articles de la corbeille seront supprimés définitivement. Cette action est irréversible."
/// ```
String get emptyTrashConfirmBody =>
"""Tous les articles de la corbeille seront supprimés définitivement. Cette action est irréversible.""";
/// ```dart
/// "Impossible de vider la corbeille."
/// ```
String get emptyTrashFailed => """Impossible de vider la corbeille.""";
/// ```dart
/// "Restaurer"
/// ```
String get restoreItem => """Restaurer""";
/// ```dart
/// "Supprimer définitivement"
/// ```
String get permanentlyDeleteItem => """Supprimer définitivement""";
/// ```dart
/// "Supprimer définitivement cet article ?"
/// ```
String get permanentlyDeleteConfirm =>
"""Supprimer définitivement cet article ?""";
/// ```dart
/// "Cette action est irréversible."
/// ```
String get permanentlyDeleteConfirmBody =>
"""Cette action est irréversible.""";
/// ```dart
/// "Impossible de restaurer l'article."
/// ```
String get restoreFailed => """Impossible de restaurer l'article.""";
/// ```dart
/// "Impossible de supprimer l'article."
/// ```
String get permanentlyDeleteFailed =>
"""Impossible de supprimer l'article.""";
/// ```dart
/// "Article restauré"
/// ```
String get itemRestored => """Article restauré""";
/// ```dart
/// "Nouvelle liste"
/// ```
@@ -1060,9 +1202,30 @@ class PhotoBoardMessagesFr extends PhotoBoardMessages {
/// "$count"
/// ```
String photoCount(int count) => """$count""";
AddMenuPhotoBoardMessagesFr get addMenu => AddMenuPhotoBoardMessagesFr(this);
SortPhotoBoardMessagesFr get sort => SortPhotoBoardMessagesFr(this);
}
class AddMenuPhotoBoardMessagesFr extends AddMenuPhotoBoardMessages {
final PhotoBoardMessagesFr _parent;
const AddMenuPhotoBoardMessagesFr(this._parent) : super(_parent);
/// ```dart
/// "Téléverser des photos"
/// ```
String get upload => """Téléverser des photos""";
/// ```dart
/// "Prendre une photo"
/// ```
String get camera => """Prendre une photo""";
/// ```dart
/// "Nouveau dossier"
/// ```
String get newFolder => """Nouveau dossier""";
}
class SortPhotoBoardMessagesFr extends SortPhotoBoardMessages {
final PhotoBoardMessagesFr _parent;
const SortPhotoBoardMessagesFr(this._parent) : super(_parent);
@@ -1098,6 +1261,57 @@ class SortPhotoBoardMessagesFr extends SortPhotoBoardMessages {
String get custom => """Personnalisé""";
}
class ShareMessagesFr extends ShareMessages {
final MessagesFr _parent;
const ShareMessagesFr(this._parent) : super(_parent);
/// ```dart
/// "Partager vers Pantry"
/// ```
String get title => """Partager vers Pantry""";
/// ```dart
/// "Choisir une maison"
/// ```
String get chooseHouse => """Choisir une maison""";
/// ```dart
/// "Téléverser vers"
/// ```
String get choosePhotoDestination => """Téléverser vers""";
/// ```dart
/// "Tableau photos"
/// ```
String get photoBoardRoot => """Tableau photos""";
/// ```dart
/// "Nouveau dossier"
/// ```
String get newFolder => """Nouveau dossier""";
/// ```dart
/// "Nom du dossier"
/// ```
String get newFolderName => """Nom du dossier""";
/// ```dart
/// "Impossible de créer le dossier."
/// ```
String get failedToCreateFolder => """Impossible de créer le dossier.""";
/// ```dart
/// "Impossible d'ouvrir le contenu partagé."
/// ```
String get failedToOpenShare => """Impossible d'ouvrir le contenu partagé.""";
/// ```dart
/// "Aucune maison disponible. Créez d'abord une maison."
/// ```
String get noHouses =>
"""Aucune maison disponible. Créez d'abord une maison.""";
}
class RecurrenceMessagesFr extends RecurrenceMessages {
final MessagesFr _parent;
const RecurrenceMessagesFr(this._parent) : super(_parent);
@@ -1401,6 +1615,18 @@ Veuillez terminer la connexion dans votre navigateur.""",
"""about.feedback""": """Commentaires & problèmes""",
"""settings.title""": """Réglages de l'app""",
"""settings.generalSection""": """Général""",
"""settings.interfaceSection""": """Interface""",
"""settings.tapRowToComplete""":
"""Toucher la ligne pour cocher les éléments""",
"""settings.tapRowToCompleteBody""":
"""Quand désactivé, les éléments ne sont cochés qu'en touchant la case.""",
"""settings.categorySpacing""":
"""Afficher un espacement entre les catégories dans les éléments de la liste""",
"""settings.categorySpacingBody""":
"""Visible uniquement lors du tri par catégorie""",
"""settings.categorySpacingNames.disabled""": """Désactivé""",
"""settings.categorySpacingNames.space""": """Espace""",
"""settings.categorySpacingNames.divider""": """Séparateur""",
"""settings.language""": """Langue""",
"""settings.languageNames.system""": """Par défaut du système""",
"""settings.languageNames.english""": """English""",
@@ -1455,6 +1681,27 @@ Veuillez terminer la connexion dans votre navigateur.""",
"""checklists.removeItem""": """Supprimer l'article""",
"""checklists.moveItem""": """Déplacer vers une liste""",
"""checklists.moveFailed""": """Impossible de déplacer l'article.""",
"""checklists.itemMarkedDone""": """Article marqué comme fait""",
"""checklists.undo""": """Annuler""",
"""checklists.viewTrash""": """Afficher la corbeille""",
"""checklists.exitTrash""": """Quitter la corbeille""",
"""checklists.trashTitle""": """Corbeille""",
"""checklists.noTrashedItems""": """La corbeille est vide.""",
"""checklists.emptyTrash""": """Vider la corbeille""",
"""checklists.emptyTrashConfirm""": """Vider la corbeille ?""",
"""checklists.emptyTrashConfirmBody""":
"""Tous les articles de la corbeille seront supprimés définitivement. Cette action est irréversible.""",
"""checklists.emptyTrashFailed""": """Impossible de vider la corbeille.""",
"""checklists.restoreItem""": """Restaurer""",
"""checklists.permanentlyDeleteItem""": """Supprimer définitivement""",
"""checklists.permanentlyDeleteConfirm""":
"""Supprimer définitivement cet article ?""",
"""checklists.permanentlyDeleteConfirmBody""":
"""Cette action est irréversible.""",
"""checklists.restoreFailed""": """Impossible de restaurer l'article.""",
"""checklists.permanentlyDeleteFailed""":
"""Impossible de supprimer l'article.""",
"""checklists.itemRestored""": """Article restauré""",
"""checklists.createList""": """Nouvelle liste""",
"""checklists.listName""": """Nom de la liste""",
"""checklists.listDescription""": """Description (facultatif)""",
@@ -1531,12 +1778,25 @@ Veuillez terminer la connexion dans votre navigateur.""",
"""photoBoard.folderName""": """Nom du dossier""",
"""photoBoard.renameFolder""": """Renommer le dossier""",
"""photoBoard.caption""": """Légende""",
"""photoBoard.addMenu.upload""": """Téléverser des photos""",
"""photoBoard.addMenu.camera""": """Prendre une photo""",
"""photoBoard.addMenu.newFolder""": """Nouveau dossier""",
"""photoBoard.sort.foldersFirst""": """Dossiers en premier""",
"""photoBoard.sort.newestFirst""": """Plus récents""",
"""photoBoard.sort.oldestFirst""": """Plus anciens""",
"""photoBoard.sort.captionAZ""": """Légende AZ""",
"""photoBoard.sort.captionZA""": """Légende ZA""",
"""photoBoard.sort.custom""": """Personnalisé""",
"""share.title""": """Partager vers Pantry""",
"""share.chooseHouse""": """Choisir une maison""",
"""share.choosePhotoDestination""": """Téléverser vers""",
"""share.photoBoardRoot""": """Tableau photos""",
"""share.newFolder""": """Nouveau dossier""",
"""share.newFolderName""": """Nom du dossier""",
"""share.failedToCreateFolder""": """Impossible de créer le dossier.""",
"""share.failedToOpenShare""": """Impossible d'ouvrir le contenu partagé.""",
"""share.noHouses""":
"""Aucune maison disponible. Créez d'abord une maison.""",
"""recurrence.title""": """Récurrence""",
"""recurrence.presets""": """Préréglages""",
"""recurrence.daily""": """Quotidien""",

View File

@@ -59,6 +59,15 @@ about:
settings:
title: "Réglages de l'app"
generalSection: "Général"
interfaceSection: Interface
tapRowToComplete: Toucher la ligne pour cocher les éléments
tapRowToCompleteBody: "Quand désactivé, les éléments ne sont cochés qu'en touchant la case."
categorySpacing: Afficher un espacement entre les catégories dans les éléments de la liste
categorySpacingBody: Visible uniquement lors du tri par catégorie
categorySpacingNames:
disabled: "Désactivé"
space: Espace
divider: "Séparateur"
language: Langue
languageNames:
system: "Par défaut du système"
@@ -120,6 +129,23 @@ checklists:
removeItem: Supprimer l'article
moveItem: "Déplacer vers une liste"
moveFailed: "Impossible de déplacer l'article."
itemMarkedDone: "Article marqué comme fait"
undo: Annuler
viewTrash: "Afficher la corbeille"
exitTrash: "Quitter la corbeille"
trashTitle: Corbeille
noTrashedItems: "La corbeille est vide."
emptyTrash: "Vider la corbeille"
emptyTrashConfirm: "Vider la corbeille ?"
emptyTrashConfirmBody: "Tous les articles de la corbeille seront supprimés définitivement. Cette action est irréversible."
emptyTrashFailed: "Impossible de vider la corbeille."
restoreItem: Restaurer
permanentlyDeleteItem: "Supprimer définitivement"
permanentlyDeleteConfirm: "Supprimer définitivement cet article ?"
permanentlyDeleteConfirmBody: "Cette action est irréversible."
restoreFailed: "Impossible de restaurer l'article."
permanentlyDeleteFailed: "Impossible de supprimer l'article."
itemRestored: "Article restauré"
createList: Nouvelle liste
listName: Nom de la liste
listDescription: Description (facultatif)
@@ -200,6 +226,10 @@ photoBoard:
renameFolder: Renommer le dossier
caption: "Légende"
photoCount(int count): "$count"
addMenu:
upload: Téléverser des photos
camera: Prendre une photo
newFolder: Nouveau dossier
sort:
foldersFirst: Dossiers en premier
newestFirst: "Plus récents"
@@ -208,6 +238,17 @@ photoBoard:
captionZA: "Légende ZA"
custom: "Personnalisé"
share:
title: Partager vers Pantry
chooseHouse: Choisir une maison
choosePhotoDestination: Téléverser vers
photoBoardRoot: Tableau photos
newFolder: Nouveau dossier
newFolderName: Nom du dossier
failedToCreateFolder: "Impossible de créer le dossier."
failedToOpenShare: "Impossible d'ouvrir le contenu partagé."
noHouses: "Aucune maison disponible. Créez d'abord une maison."
recurrence:
title: "Récurrence"
presets: "Préréglages"

View File

@@ -76,6 +76,7 @@ class MessagesHe extends Messages {
ChecklistsMessagesHe get checklists => ChecklistsMessagesHe(this);
NotesWallMessagesHe get notesWall => NotesWallMessagesHe(this);
PhotoBoardMessagesHe get photoBoard => PhotoBoardMessagesHe(this);
ShareMessagesHe get share => ShareMessagesHe(this);
RecurrenceMessagesHe get recurrence => RecurrenceMessagesHe(this);
}
@@ -361,6 +362,34 @@ class SettingsMessagesHe extends SettingsMessages {
/// ```
String get generalSection => """כללי""";
/// ```dart
/// "ממשק"
/// ```
String get interfaceSection => """ממשק""";
/// ```dart
/// "השלם פריטים בלחיצה על השורה"
/// ```
String get tapRowToComplete => """השלם פריטים בלחיצה על השורה""";
/// ```dart
/// "כאשר כבוי, פריטים מסומנים כהושלמו רק בלחיצה על תיבת הסימון."
/// ```
String get tapRowToCompleteBody =>
"""כאשר כבוי, פריטים מסומנים כהושלמו רק בלחיצה על תיבת הסימון.""";
/// ```dart
/// "הצג רווח בין קטגוריות בפריטי הרשימה"
/// ```
String get categorySpacing => """הצג רווח בין קטגוריות בפריטי הרשימה""";
/// ```dart
/// "מוצג רק בעת מיון לפי קטגוריה"
/// ```
String get categorySpacingBody => """מוצג רק בעת מיון לפי קטגוריה""";
CategorySpacingNamesSettingsMessagesHe get categorySpacingNames =>
CategorySpacingNamesSettingsMessagesHe(this);
/// ```dart
/// "שפה"
/// ```
@@ -428,6 +457,27 @@ class SettingsMessagesHe extends SettingsMessages {
"""הרשאת ההתראות נדחתה. הפעל אותה בהגדרות המערכת.""";
}
class CategorySpacingNamesSettingsMessagesHe
extends CategorySpacingNamesSettingsMessages {
final SettingsMessagesHe _parent;
const CategorySpacingNamesSettingsMessagesHe(this._parent) : super(_parent);
/// ```dart
/// "מושבת"
/// ```
String get disabled => """מושבת""";
/// ```dart
/// "רווח"
/// ```
String get space => """רווח""";
/// ```dart
/// "קו מפריד"
/// ```
String get divider => """קו מפריד""";
}
class LanguageNamesSettingsMessagesHe extends LanguageNamesSettingsMessages {
final SettingsMessagesHe _parent;
const LanguageNamesSettingsMessagesHe(this._parent) : super(_parent);
@@ -658,6 +708,92 @@ class ChecklistsMessagesHe extends ChecklistsMessages {
/// ```
String get moveFailed => """העברת הפריט נכשלה.""";
/// ```dart
/// "הפריט סומן כהושלם"
/// ```
String get itemMarkedDone => """הפריט סומן כהושלם""";
/// ```dart
/// "בטל"
/// ```
String get undo => """בטל""";
/// ```dart
/// "הצג אשפה"
/// ```
String get viewTrash => """הצג אשפה""";
/// ```dart
/// "צא מהאשפה"
/// ```
String get exitTrash => """צא מהאשפה""";
/// ```dart
/// "אשפה"
/// ```
String get trashTitle => """אשפה""";
/// ```dart
/// "האשפה ריקה."
/// ```
String get noTrashedItems => """האשפה ריקה.""";
/// ```dart
/// "רוקן אשפה"
/// ```
String get emptyTrash => """רוקן אשפה""";
/// ```dart
/// "לרוקן את האשפה?"
/// ```
String get emptyTrashConfirm => """לרוקן את האשפה?""";
/// ```dart
/// "כל הפריטים באשפה יימחקו לצמיתות. לא ניתן לבטל פעולה זו."
/// ```
String get emptyTrashConfirmBody =>
"""כל הפריטים באשפה יימחקו לצמיתות. לא ניתן לבטל פעולה זו.""";
/// ```dart
/// "ריקון האשפה נכשל."
/// ```
String get emptyTrashFailed => """ריקון האשפה נכשל.""";
/// ```dart
/// "שחזר"
/// ```
String get restoreItem => """שחזר""";
/// ```dart
/// "מחק לצמיתות"
/// ```
String get permanentlyDeleteItem => """מחק לצמיתות""";
/// ```dart
/// "למחוק את הפריט לצמיתות?"
/// ```
String get permanentlyDeleteConfirm => """למחוק את הפריט לצמיתות?""";
/// ```dart
/// "לא ניתן לבטל פעולה זו."
/// ```
String get permanentlyDeleteConfirmBody => """לא ניתן לבטל פעולה זו.""";
/// ```dart
/// "שחזור הפריט נכשל."
/// ```
String get restoreFailed => """שחזור הפריט נכשל.""";
/// ```dart
/// "מחיקת הפריט נכשלה."
/// ```
String get permanentlyDeleteFailed => """מחיקת הפריט נכשלה.""";
/// ```dart
/// "הפריט שוחזר"
/// ```
String get itemRestored => """הפריט שוחזר""";
/// ```dart
/// "רשימה חדשה"
/// ```
@@ -1055,9 +1191,30 @@ class PhotoBoardMessagesHe extends PhotoBoardMessages {
/// "$count"
/// ```
String photoCount(int count) => """$count""";
AddMenuPhotoBoardMessagesHe get addMenu => AddMenuPhotoBoardMessagesHe(this);
SortPhotoBoardMessagesHe get sort => SortPhotoBoardMessagesHe(this);
}
class AddMenuPhotoBoardMessagesHe extends AddMenuPhotoBoardMessages {
final PhotoBoardMessagesHe _parent;
const AddMenuPhotoBoardMessagesHe(this._parent) : super(_parent);
/// ```dart
/// "העלאת תמונות"
/// ```
String get upload => """העלאת תמונות""";
/// ```dart
/// "צילום תמונה"
/// ```
String get camera => """צילום תמונה""";
/// ```dart
/// "תיקייה חדשה"
/// ```
String get newFolder => """תיקייה חדשה""";
}
class SortPhotoBoardMessagesHe extends SortPhotoBoardMessages {
final PhotoBoardMessagesHe _parent;
const SortPhotoBoardMessagesHe(this._parent) : super(_parent);
@@ -1093,6 +1250,56 @@ class SortPhotoBoardMessagesHe extends SortPhotoBoardMessages {
String get custom => """מותאם אישית""";
}
class ShareMessagesHe extends ShareMessages {
final MessagesHe _parent;
const ShareMessagesHe(this._parent) : super(_parent);
/// ```dart
/// "שיתוף ל-Pantry"
/// ```
String get title => """שיתוף ל-Pantry""";
/// ```dart
/// "בחר בית"
/// ```
String get chooseHouse => """בחר בית""";
/// ```dart
/// "העלה אל"
/// ```
String get choosePhotoDestination => """העלה אל""";
/// ```dart
/// "לוח התמונות"
/// ```
String get photoBoardRoot => """לוח התמונות""";
/// ```dart
/// "תיקייה חדשה"
/// ```
String get newFolder => """תיקייה חדשה""";
/// ```dart
/// "שם התיקייה"
/// ```
String get newFolderName => """שם התיקייה""";
/// ```dart
/// "יצירת התיקייה נכשלה."
/// ```
String get failedToCreateFolder => """יצירת התיקייה נכשלה.""";
/// ```dart
/// "לא ניתן לפתוח את התוכן ששותף."
/// ```
String get failedToOpenShare => """לא ניתן לפתוח את התוכן ששותף.""";
/// ```dart
/// "אין בתים זמינים. צור תחילה בית."
/// ```
String get noHouses => """אין בתים זמינים. צור תחילה בית.""";
}
class RecurrenceMessagesHe extends RecurrenceMessages {
final MessagesHe _parent;
const RecurrenceMessagesHe(this._parent) : super(_parent);
@@ -1391,6 +1598,15 @@ Map<String, String> get messagesHeMap => {
"""about.feedback""": """משוב ובעיות""",
"""settings.title""": """הגדרות האפליקציה""",
"""settings.generalSection""": """כללי""",
"""settings.interfaceSection""": """ממשק""",
"""settings.tapRowToComplete""": """השלם פריטים בלחיצה על השורה""",
"""settings.tapRowToCompleteBody""":
"""כאשר כבוי, פריטים מסומנים כהושלמו רק בלחיצה על תיבת הסימון.""",
"""settings.categorySpacing""": """הצג רווח בין קטגוריות בפריטי הרשימה""",
"""settings.categorySpacingBody""": """מוצג רק בעת מיון לפי קטגוריה""",
"""settings.categorySpacingNames.disabled""": """מושבת""",
"""settings.categorySpacingNames.space""": """רווח""",
"""settings.categorySpacingNames.divider""": """קו מפריד""",
"""settings.language""": """שפה""",
"""settings.languageNames.system""": """ברירת מחדל (שפת המערכת)""",
"""settings.languageNames.english""": """English""",
@@ -1443,6 +1659,24 @@ Map<String, String> get messagesHeMap => {
"""checklists.removeItem""": """הסר פריט""",
"""checklists.moveItem""": """העבר לרשימה""",
"""checklists.moveFailed""": """העברת הפריט נכשלה.""",
"""checklists.itemMarkedDone""": """הפריט סומן כהושלם""",
"""checklists.undo""": """בטל""",
"""checklists.viewTrash""": """הצג אשפה""",
"""checklists.exitTrash""": """צא מהאשפה""",
"""checklists.trashTitle""": """אשפה""",
"""checklists.noTrashedItems""": """האשפה ריקה.""",
"""checklists.emptyTrash""": """רוקן אשפה""",
"""checklists.emptyTrashConfirm""": """לרוקן את האשפה?""",
"""checklists.emptyTrashConfirmBody""":
"""כל הפריטים באשפה יימחקו לצמיתות. לא ניתן לבטל פעולה זו.""",
"""checklists.emptyTrashFailed""": """ריקון האשפה נכשל.""",
"""checklists.restoreItem""": """שחזר""",
"""checklists.permanentlyDeleteItem""": """מחק לצמיתות""",
"""checklists.permanentlyDeleteConfirm""": """למחוק את הפריט לצמיתות?""",
"""checklists.permanentlyDeleteConfirmBody""": """לא ניתן לבטל פעולה זו.""",
"""checklists.restoreFailed""": """שחזור הפריט נכשל.""",
"""checklists.permanentlyDeleteFailed""": """מחיקת הפריט נכשלה.""",
"""checklists.itemRestored""": """הפריט שוחזר""",
"""checklists.createList""": """רשימה חדשה""",
"""checklists.listName""": """שם הרשימה""",
"""checklists.listDescription""": """תיאור (אופציונלי)""",
@@ -1513,12 +1747,24 @@ Map<String, String> get messagesHeMap => {
"""photoBoard.folderName""": """שם התיקייה""",
"""photoBoard.renameFolder""": """שנה שם תיקייה""",
"""photoBoard.caption""": """כיתוב""",
"""photoBoard.addMenu.upload""": """העלאת תמונות""",
"""photoBoard.addMenu.camera""": """צילום תמונה""",
"""photoBoard.addMenu.newFolder""": """תיקייה חדשה""",
"""photoBoard.sort.foldersFirst""": """תיקיות קודם""",
"""photoBoard.sort.newestFirst""": """החדש ביותר""",
"""photoBoard.sort.oldestFirst""": """הישן ביותר""",
"""photoBoard.sort.captionAZ""": """כיתוב א–ת""",
"""photoBoard.sort.captionZA""": """כיתוב ת–א""",
"""photoBoard.sort.custom""": """מותאם אישית""",
"""share.title""": """שיתוף ל-Pantry""",
"""share.chooseHouse""": """בחר בית""",
"""share.choosePhotoDestination""": """העלה אל""",
"""share.photoBoardRoot""": """לוח התמונות""",
"""share.newFolder""": """תיקייה חדשה""",
"""share.newFolderName""": """שם התיקייה""",
"""share.failedToCreateFolder""": """יצירת התיקייה נכשלה.""",
"""share.failedToOpenShare""": """לא ניתן לפתוח את התוכן ששותף.""",
"""share.noHouses""": """אין בתים זמינים. צור תחילה בית.""",
"""recurrence.title""": """חזרה""",
"""recurrence.presets""": """הגדרות מוכנות""",
"""recurrence.daily""": """יומי""",

View File

@@ -59,6 +59,15 @@ about:
settings:
title: הגדרות האפליקציה
generalSection: כללי
interfaceSection: ממשק
tapRowToComplete: השלם פריטים בלחיצה על השורה
tapRowToCompleteBody: כאשר כבוי, פריטים מסומנים כהושלמו רק בלחיצה על תיבת הסימון.
categorySpacing: הצג רווח בין קטגוריות בפריטי הרשימה
categorySpacingBody: מוצג רק בעת מיון לפי קטגוריה
categorySpacingNames:
disabled: מושבת
space: רווח
divider: קו מפריד
language: שפה
languageNames:
system: ברירת מחדל (שפת המערכת)
@@ -120,6 +129,23 @@ checklists:
removeItem: הסר פריט
moveItem: העבר לרשימה
moveFailed: העברת הפריט נכשלה.
itemMarkedDone: הפריט סומן כהושלם
undo: בטל
viewTrash: הצג אשפה
exitTrash: צא מהאשפה
trashTitle: אשפה
noTrashedItems: האשפה ריקה.
emptyTrash: רוקן אשפה
emptyTrashConfirm: לרוקן את האשפה?
emptyTrashConfirmBody: כל הפריטים באשפה יימחקו לצמיתות. לא ניתן לבטל פעולה זו.
emptyTrashFailed: ריקון האשפה נכשל.
restoreItem: שחזר
permanentlyDeleteItem: מחק לצמיתות
permanentlyDeleteConfirm: למחוק את הפריט לצמיתות?
permanentlyDeleteConfirmBody: לא ניתן לבטל פעולה זו.
restoreFailed: שחזור הפריט נכשל.
permanentlyDeleteFailed: מחיקת הפריט נכשלה.
itemRestored: הפריט שוחזר
createList: רשימה חדשה
listName: שם הרשימה
listDescription: תיאור (אופציונלי)
@@ -200,6 +226,10 @@ photoBoard:
renameFolder: שנה שם תיקייה
caption: כיתוב
photoCount(int count): "$count"
addMenu:
upload: העלאת תמונות
camera: צילום תמונה
newFolder: תיקייה חדשה
sort:
foldersFirst: תיקיות קודם
newestFirst: החדש ביותר
@@ -208,6 +238,17 @@ photoBoard:
captionZA: "כיתוב ת–א"
custom: מותאם אישית
share:
title: שיתוף ל-Pantry
chooseHouse: בחר בית
choosePhotoDestination: העלה אל
photoBoardRoot: לוח התמונות
newFolder: תיקייה חדשה
newFolderName: שם התיקייה
failedToCreateFolder: יצירת התיקייה נכשלה.
failedToOpenShare: לא ניתן לפתוח את התוכן ששותף.
noHouses: אין בתים זמינים. צור תחילה בית.
recurrence:
title: חזרה
presets: הגדרות מוכנות

View File

@@ -61,6 +61,7 @@ class ListItem {
final int sortOrder;
final int createdAt;
final int updatedAt;
final int? deletedAt;
const ListItem({
required this.id,
@@ -81,6 +82,7 @@ class ListItem {
required this.sortOrder,
required this.createdAt,
required this.updatedAt,
this.deletedAt,
});
factory ListItem.fromJson(Map<String, dynamic> json) => ListItem(
@@ -102,6 +104,7 @@ class ListItem {
sortOrder: json['sortOrder'] as int,
createdAt: json['createdAt'] as int,
updatedAt: json['updatedAt'] as int,
deletedAt: json['deletedAt'] as int?,
);
Map<String, dynamic> toJson() => {
@@ -123,6 +126,7 @@ class ListItem {
'sortOrder': sortOrder,
'createdAt': createdAt,
'updatedAt': updatedAt,
'deletedAt': deletedAt,
};
ListItem copyWith({bool? done, int? doneAt, String? doneBy}) => ListItem(
@@ -144,5 +148,6 @@ class ListItem {
sortOrder: sortOrder,
createdAt: createdAt,
updatedAt: updatedAt,
deletedAt: deletedAt,
);
}

View File

@@ -34,8 +34,11 @@ class ApiClient {
Uri _uri(String path, [Map<String, String>? queryParameters]) {
final base = Uri.parse(_credentials.serverUrl);
final prefix = base.path.endsWith('/')
? base.path.substring(0, base.path.length - 1)
: base.path;
return base.replace(
path: '$basePath$path',
path: '$prefix$basePath$path',
queryParameters: queryParameters,
);
}

View File

@@ -1,3 +1,5 @@
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:pantry/models/notification.dart';
@@ -80,9 +82,15 @@ Future<void> markCurrentNotificationsAsSeen(List<int> ids) async {
await storage.write(key: _seenIdsKey, value: ids.join(','));
}
/// workmanager only supports Android and iOS; other platforms throw
/// UnimplementedError. Gate every call so callers don't need to.
bool get _workmanagerSupported =>
!kIsWeb && (Platform.isAndroid || Platform.isIOS);
/// Schedule the periodic background poll using the user's configured
/// interval from [PrefsService] (minimum 15 minutes on Android).
Future<void> registerBackgroundNotificationPoll() async {
if (!_workmanagerSupported) return;
await Workmanager().initialize(backgroundCallbackDispatcher);
final minutes = PrefsService.instance.pollIntervalMinutes;
// Android enforces a 15-minute minimum for periodic tasks.
@@ -97,6 +105,7 @@ Future<void> registerBackgroundNotificationPoll() async {
}
Future<void> cancelBackgroundNotificationPoll() async {
if (!_workmanagerSupported) return;
await Workmanager().cancelByUniqueName(notificationPollTaskName);
}

View File

@@ -186,6 +186,44 @@ class ChecklistService {
);
}
Future<List<ListItem>> getDeletedItems(
int houseId,
int listId, {
int limit = 200,
int offset = 0,
}) async {
return ApiClient.instance.get<List, List<ListItem>>(
'/houses/$houseId/lists/$listId/items/trash',
query: {'limit': limit.toString(), 'offset': offset.toString()},
fromJson: (data) => data
.map((e) => ListItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
Future<ListItem> restoreItem(int houseId, int listId, int itemId) async {
return ApiClient.instance.post<Map<String, dynamic>, ListItem>(
'/houses/$houseId/lists/$listId/items/$itemId/restore',
fromJson: (data) => ListItem.fromJson(data),
);
}
Future<void> permanentlyDeleteItem(
int houseId,
int listId,
int itemId,
) async {
await ApiClient.instance.delete(
'/houses/$houseId/lists/$listId/items/$itemId/permanent',
);
}
Future<void> emptyTrash(int houseId, int listId) async {
await ApiClient.instance.delete(
'/houses/$houseId/lists/$listId/items/trash',
);
}
Future<ListItem> toggleItem(int houseId, int listId, int itemId) async {
return ApiClient.instance.post<Map<String, dynamic>, ListItem>(
'/houses/$houseId/lists/$listId/items/$itemId/toggle',

View File

@@ -19,14 +19,15 @@ class LocalNotificationsService {
const androidSettings = AndroidInitializationSettings(
'@mipmap/ic_launcher',
);
const iosSettings = DarwinInitializationSettings(
const darwinSettings = DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
);
const settings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
iOS: darwinSettings,
macOS: darwinSettings,
);
await _plugin.initialize(
@@ -79,7 +80,19 @@ class LocalNotificationsService {
await ios?.requestPermissions(alert: true, badge: true, sound: true) ??
true;
return androidGranted && iosGranted;
final macos = _plugin
.resolvePlatformSpecificImplementation<
MacOSFlutterLocalNotificationsPlugin
>();
final macosGranted =
await macos?.requestPermissions(
alert: true,
badge: true,
sound: true,
) ??
true;
return androidGranted && iosGranted && macosGranted;
}
Future<void> show({
@@ -102,6 +115,7 @@ class LocalNotificationsService {
priority: Priority.defaultPriority,
),
iOS: DarwinNotificationDetails(),
macOS: DarwinNotificationDetails(),
),
payload: payload,
);

View File

@@ -0,0 +1,34 @@
import 'package:flutter/foundation.dart';
/// A pending note to be created from an OS share intent. Held until the
/// notes wall picks it up and opens the new-note form prefilled.
class PendingNoteShare {
final int houseId;
final String content;
const PendingNoteShare({required this.houseId, required this.content});
}
/// Cross-screen signal carrying a pending note share. The share router
/// pushes here, then asks the home view to switch to the notes tab; the
/// [NotesWallView] for the same house listens and opens the form.
class PendingNoteShareService extends ChangeNotifier {
PendingNoteShareService._();
static final PendingNoteShareService instance = PendingNoteShareService._();
PendingNoteShare? _pending;
PendingNoteShare? get pending => _pending;
void push(PendingNoteShare share) {
_pending = share;
notifyListeners();
}
/// Returns the pending share if it matches [houseId] and clears it.
PendingNoteShare? takeForHouse(int houseId) {
if (_pending?.houseId != houseId) return null;
final taken = _pending;
_pending = null;
return taken;
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/foundation.dart';
/// A pending photo upload originating from an OS share intent. Held in a
/// queue until the [PhotoBoardController] for the same house picks it up.
class PendingPhotoShare {
final int houseId;
final int? folderId;
final List<String> paths;
const PendingPhotoShare({
required this.houseId,
required this.folderId,
required this.paths,
});
}
/// Cross-screen queue of pending photo uploads from share intents. The
/// share-router enqueues entries here, then asks the home view to switch
/// to the correct house + photo tab. The [PhotoBoardController] for that
/// house listens and consumes its matching entries.
class PendingPhotoShareService extends ChangeNotifier {
PendingPhotoShareService._();
static final PendingPhotoShareService instance = PendingPhotoShareService._();
final List<PendingPhotoShare> _queue = [];
void enqueue(PendingPhotoShare share) {
_queue.add(share);
notifyListeners();
}
/// Remove and return all pending uploads belonging to [houseId].
List<PendingPhotoShare> takeForHouse(int houseId) {
final taken = _queue.where((s) => s.houseId == houseId).toList();
if (taken.isNotEmpty) {
_queue.removeWhere((s) => s.houseId == houseId);
}
return taken;
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class PrefsService {
class PrefsService extends ChangeNotifier {
PrefsService._();
static final PrefsService instance = PrefsService._();
@@ -10,6 +11,8 @@ class PrefsService {
static const _notificationsIntroSeenKey = 'notifications_intro_seen';
static const _localeKey = 'locale';
static const _themeModeKey = 'theme_mode';
static const _checklistTapRowToToggleKey = 'checklist_tap_row_to_toggle';
static const _checklistCategorySpacingKey = 'checklist_category_spacing';
final _storage = const FlutterSecureStorage();
int? _lastHouseId;
@@ -32,6 +35,13 @@ class PrefsService {
String? _themeMode;
String? get themeMode => _themeMode;
bool _checklistTapRowToToggle = false;
bool get checklistTapRowToToggle => _checklistTapRowToToggle;
/// "disabled", "space", "divider"
String _checklistCategorySpacing = 'disabled';
String get checklistCategorySpacing => _checklistCategorySpacing;
Future<void> load() async {
final lastHouse = await _storage.read(key: _lastHouseKey);
if (lastHouse != null) _lastHouseId = int.tryParse(lastHouse);
@@ -50,11 +60,21 @@ class PrefsService {
_locale = await _storage.read(key: _localeKey);
_themeMode = await _storage.read(key: _themeModeKey);
final tapRow = await _storage.read(key: _checklistTapRowToToggleKey);
if (tapRow != null) _checklistTapRowToToggle = tapRow == 'true';
final spacing = await _storage.read(key: _checklistCategorySpacingKey);
if (spacing != null &&
(spacing == 'disabled' || spacing == 'space' || spacing == 'divider')) {
_checklistCategorySpacing = spacing;
}
}
Future<void> setLastHouseId(int id) async {
_lastHouseId = id;
await _storage.write(key: _lastHouseKey, value: id.toString());
notifyListeners();
}
Future<void> setNotificationsEnabled(bool value) async {
@@ -63,6 +83,7 @@ class PrefsService {
key: _notificationsEnabledKey,
value: value.toString(),
);
notifyListeners();
}
Future<void> setPollIntervalMinutes(int minutes) async {
@@ -71,6 +92,7 @@ class PrefsService {
key: _pollIntervalMinutesKey,
value: minutes.toString(),
);
notifyListeners();
}
Future<void> setLocale(String? locale) async {
@@ -80,6 +102,7 @@ class PrefsService {
} else {
await _storage.write(key: _localeKey, value: locale);
}
notifyListeners();
}
Future<void> setThemeMode(String? mode) async {
@@ -89,6 +112,22 @@ class PrefsService {
} else {
await _storage.write(key: _themeModeKey, value: mode);
}
notifyListeners();
}
Future<void> setChecklistTapRowToToggle(bool value) async {
_checklistTapRowToToggle = value;
await _storage.write(
key: _checklistTapRowToToggleKey,
value: value.toString(),
);
notifyListeners();
}
Future<void> setChecklistCategorySpacing(String value) async {
_checklistCategorySpacing = value;
await _storage.write(key: _checklistCategorySpacingKey, value: value);
notifyListeners();
}
Future<void> setNotificationsIntroSeen(bool value) async {
@@ -97,6 +136,7 @@ class PrefsService {
key: _notificationsIntroSeenKey,
value: value.toString(),
);
notifyListeners();
}
Future<void> clear() async {
@@ -106,11 +146,16 @@ class PrefsService {
_notificationsIntroSeen = false;
_locale = null;
_themeMode = null;
_checklistTapRowToToggle = false;
_checklistCategorySpacing = 'disabled';
await _storage.delete(key: _lastHouseKey);
await _storage.delete(key: _notificationsEnabledKey);
await _storage.delete(key: _pollIntervalMinutesKey);
await _storage.delete(key: _notificationsIntroSeenKey);
await _storage.delete(key: _localeKey);
await _storage.delete(key: _themeModeKey);
await _storage.delete(key: _checklistTapRowToToggleKey);
await _storage.delete(key: _checklistCategorySpacingKey);
notifyListeners();
}
}

View File

@@ -0,0 +1,53 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
/// Listens for incoming OS-level share intents (photos shared from Photos
/// app, plain text/URL shared from any app, etc.) and exposes the most
/// recent batch via a [ValueListenable]. Consumers should call [consume]
/// after navigating to the share handler so the same payload isn't
/// processed twice.
class ShareIntentService {
ShareIntentService._();
static final ShareIntentService instance = ShareIntentService._();
final ValueNotifier<List<SharedMediaFile>?> pending = ValueNotifier(null);
StreamSubscription<List<SharedMediaFile>>? _sub;
/// Begin listening for share intents. Idempotent.
Future<void> init() async {
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
_sub ??= ReceiveSharingIntent.instance.getMediaStream().listen(
(files) {
if (files.isNotEmpty) pending.value = files;
},
onError: (Object e) {
debugPrint('[ShareIntentService] stream error: $e');
},
);
final initial = await ReceiveSharingIntent.instance.getInitialMedia();
if (initial.isNotEmpty) {
pending.value = initial;
}
// Clear the native cache so the same intent isn't re-delivered on
// subsequent cold starts.
await ReceiveSharingIntent.instance.reset();
}
/// Take the most recent share payload and clear it.
List<SharedMediaFile>? consume() {
final v = pending.value;
pending.value = null;
return v;
}
Future<void> dispose() async {
await _sub?.cancel();
_sub = null;
}
}

View File

@@ -0,0 +1,12 @@
import 'dart:io' show Platform;
import 'package:device_info_plus/device_info_plus.dart';
bool? _isiOSAppOnMacCache;
Future<bool> isiOSAppOnMac() async {
if (_isiOSAppOnMacCache != null) return _isiOSAppOnMacCache!;
if (!Platform.isIOS) return _isiOSAppOnMacCache = false;
final info = await DeviceInfoPlugin().iosInfo;
return _isiOSAppOnMacCache = info.isiOSAppOnMac;
}

View File

@@ -4,6 +4,7 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:pantry/i18n.dart';
import 'package:pantry/widgets/app_bar_back_leading.dart';
class AboutView extends StatefulWidget {
const AboutView({super.key});
@@ -35,7 +36,7 @@ class _AboutViewState extends State<AboutView> {
final a = m.about;
return Scaffold(
appBar: AppBar(title: Text(a.title)),
appBar: AppBar(leading: appBarBackLeading(context), title: Text(a.title)),
body: ListView(
padding: const EdgeInsets.all(24),
children: [

View File

@@ -3,6 +3,7 @@ import 'package:pantry/i18n.dart';
import 'package:pantry/models/category.dart';
import 'package:pantry/services/category_service.dart';
import 'package:pantry/utils/category_icons.dart';
import 'package:pantry/widgets/app_bar_back_leading.dart';
import 'package:pantry/widgets/create_category_dialog.dart';
class CategoriesView extends StatefulWidget {
@@ -125,7 +126,10 @@ class _CategoriesViewState extends State<CategoriesView> {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(title: Text(m.categories.manageTitle)),
appBar: AppBar(
leading: appBarBackLeading(context),
title: Text(m.categories.manageTitle),
),
floatingActionButton: FloatingActionButton(
onPressed: _create,
child: const Icon(Icons.add),

View File

@@ -5,103 +5,141 @@ import 'package:pantry/models/category.dart' as models;
import 'package:pantry/models/checklist.dart';
import 'package:pantry/services/auth_service.dart';
import 'package:pantry/services/checklist_service.dart';
import 'package:pantry/services/prefs_service.dart';
import 'package:pantry/utils/category_icons.dart';
import 'package:pantry/utils/rrule.dart';
import 'package:pantry/widgets/context_menu_region.dart';
import 'package:pantry/widgets/image_preview.dart';
import 'package:provider/provider.dart';
class ChecklistItemTile extends StatelessWidget {
final ListItem item;
final models.Category? category;
final int houseId;
final bool trashMode;
final ValueChanged<ListItem> onToggle;
final ValueChanged<ListItem> onView;
final ValueChanged<ListItem> onEdit;
final ValueChanged<ListItem> onMove;
final ValueChanged<ListItem> onDelete;
final ValueChanged<ListItem>? onRestore;
final ValueChanged<ListItem>? onPermanentDelete;
const ChecklistItemTile({
super.key,
required this.item,
this.category,
required this.houseId,
this.trashMode = false,
required this.onToggle,
required this.onView,
required this.onEdit,
required this.onMove,
required this.onDelete,
this.onRestore,
this.onPermanentDelete,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final dimmed = item.done;
final tapRowToToggle = context
.watch<PrefsService>()
.checklistTapRowToToggle;
return Material(
type: MaterialType.transparency,
child: Opacity(
opacity: dimmed ? 0.5 : 1.0,
child: InkWell(
onTap: () => onToggle(item),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child: Row(
children: [
Checkbox(value: item.done, onChanged: (_) => onToggle(item)),
if (item.imageFileId != null) ...[
GestureDetector(
onTap: () => _showImagePreview(context),
child: Hero(
tag: 'item-image-${item.id}',
child: _ItemImage(
houseId: houseId,
fileId: item.imageFileId!,
owner: item.imageUploadedBy ?? '',
child: ContextMenuRegion<String>(
itemBuilder: _menuItems,
onSelected: (value) => _onMenuSelected(value),
child: InkWell(
onTap: trashMode
? () => onView(item)
: (tapRowToToggle ? () => onToggle(item) : null),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child: Row(
children: [
if (trashMode)
Padding(
padding: const EdgeInsetsDirectional.only(
start: 12,
end: 12,
),
child: Icon(
Icons.delete_outline,
color: theme.colorScheme.onSurfaceVariant,
),
)
else
Checkbox(
value: item.done,
onChanged: (_) => onToggle(item),
),
if (item.imageFileId != null) ...[
GestureDetector(
onTap: () => _showImagePreview(context),
child: Hero(
tag: 'item-image-${item.id}',
child: _ItemImage(
houseId: houseId,
fileId: item.imageFileId!,
owner: item.imageUploadedBy ?? '',
),
),
),
),
const SizedBox(width: 8),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: theme.textTheme.bodyLarge?.copyWith(
decoration: dimmed
? TextDecoration.lineThrough
: null,
),
),
if (_hasBadges)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Wrap(
spacing: 4,
runSpacing: 4,
children: _buildBadges(context),
const SizedBox(width: 8),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: theme.textTheme.bodyLarge?.copyWith(
decoration: dimmed
? TextDecoration.lineThrough
: null,
),
),
],
if (_hasBadges)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Wrap(
spacing: 4,
runSpacing: 4,
children: _buildBadges(context),
),
),
],
),
),
),
IconButton(
icon: Icon(
Icons.visibility_outlined,
size: 20,
color: theme.colorScheme.onSurfaceVariant,
IconButton(
icon: Icon(
Icons.visibility_outlined,
size: 20,
color: theme.colorScheme.onSurfaceVariant,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () => onView(item),
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () => onView(item),
),
_MoreMenuButton(
item: item,
onEdit: onEdit,
onMove: onMove,
onDelete: onDelete,
),
],
PopupMenuButton<String>(
icon: Icon(
Icons.more_horiz,
size: 20,
color: theme.colorScheme.onSurfaceVariant,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
itemBuilder: (_) => _menuItems(),
onSelected: _onMenuSelected,
),
],
),
),
),
),
@@ -109,6 +147,80 @@ class ChecklistItemTile extends StatelessWidget {
);
}
List<PopupMenuEntry<String>> _menuItems() {
if (trashMode) {
return [
PopupMenuItem(
value: 'restore',
child: Row(
children: [
const Icon(Icons.restore_from_trash, size: 18),
const SizedBox(width: 8),
Text(m.checklists.restoreItem),
],
),
),
PopupMenuItem(
value: 'permanent',
child: Row(
children: [
const Icon(Icons.delete_forever, size: 18),
const SizedBox(width: 8),
Text(m.checklists.permanentlyDeleteItem),
],
),
),
];
}
return [
PopupMenuItem(
value: 'edit',
child: Row(
children: [
const Icon(Icons.edit, size: 18),
const SizedBox(width: 8),
Text(m.checklists.editItem),
],
),
),
PopupMenuItem(
value: 'move',
child: Row(
children: [
const Icon(Icons.drive_file_move_outlined, size: 18),
const SizedBox(width: 8),
Text(m.checklists.moveItem),
],
),
),
PopupMenuItem(
value: 'remove',
child: Row(
children: [
const Icon(Icons.delete, size: 18),
const SizedBox(width: 8),
Text(m.checklists.removeItem),
],
),
),
];
}
void _onMenuSelected(String value) {
switch (value) {
case 'edit':
onEdit(item);
case 'move':
onMove(item);
case 'remove':
onDelete(item);
case 'restore':
onRestore?.call(item);
case 'permanent':
onPermanentDelete?.call(item);
}
}
void _showImagePreview(BuildContext context) {
final uri = ChecklistService.instance.itemImagePreviewUri(
houseId,
@@ -255,72 +367,3 @@ class _Badge extends StatelessWidget {
);
}
}
class _MoreMenuButton extends StatelessWidget {
final ListItem item;
final ValueChanged<ListItem> onEdit;
final ValueChanged<ListItem> onMove;
final ValueChanged<ListItem> onDelete;
const _MoreMenuButton({
required this.item,
required this.onEdit,
required this.onMove,
required this.onDelete,
});
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
icon: Icon(
Icons.more_horiz,
size: 20,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
children: [
const Icon(Icons.edit, size: 18),
const SizedBox(width: 8),
Text(m.checklists.editItem),
],
),
),
PopupMenuItem(
value: 'move',
child: Row(
children: [
const Icon(Icons.drive_file_move_outlined, size: 18),
const SizedBox(width: 8),
Text(m.checklists.moveItem),
],
),
),
PopupMenuItem(
value: 'remove',
child: Row(
children: [
const Icon(Icons.delete, size: 18),
const SizedBox(width: 8),
Text(m.checklists.removeItem),
],
),
),
],
onSelected: (value) {
switch (value) {
case 'edit':
onEdit(item);
case 'move':
onMove(item);
case 'remove':
onDelete(item);
}
},
);
}
}

View File

@@ -41,6 +41,9 @@ class ChecklistsController extends ChangeNotifier {
String _sortBy = 'custom';
String get sortBy => _sortBy;
bool _isTrashMode = false;
bool get isTrashMode => _isTrashMode;
bool _isLoading = true;
bool get isLoading => _isLoading;
@@ -142,6 +145,14 @@ class ChecklistsController extends ChangeNotifier {
_currentList = list;
_checklistService.selectedListId = list.id;
if (_isTrashMode) {
_items = [];
_isLoading = true;
notifyListeners();
await _loadTrashItems(list);
return;
}
// Show cached items immediately, or spinner if no cache for this list
final cached = _checklistService.getCachedItems(list.id);
if (cached != null) {
@@ -162,7 +173,7 @@ class ChecklistsController extends ChangeNotifier {
sortBy: _sortBy,
);
_checklistService.cacheItems(list.id, freshItems);
if (_currentList?.id == list.id) {
if (_currentList?.id == list.id && !_isTrashMode) {
_items = freshItems;
_isLoading = false;
notifyListeners();
@@ -177,6 +188,38 @@ class ChecklistsController extends ChangeNotifier {
}
}
Future<void> _loadTrashItems(ChecklistList list) async {
try {
final trashItems = await _checklistService.getDeletedItems(
houseId,
list.id,
);
if (_currentList?.id == list.id && _isTrashMode) {
_items = trashItems;
_error = null;
_isLoading = false;
notifyListeners();
}
} catch (e) {
debugPrint('[ChecklistsController] Failed to load trash: $e');
if (_currentList?.id == list.id && _isTrashMode) {
_error = m.checklists.failedToLoadItems;
_isLoading = false;
notifyListeners();
}
}
}
Future<void> setTrashMode(bool enabled) async {
if (_isTrashMode == enabled) return;
_isTrashMode = enabled;
if (_currentList != null) {
await selectList(_currentList!);
} else {
notifyListeners();
}
}
Future<void> setSortBy(String sort) async {
if (sort == _sortBy) return;
_sortBy = sort;
@@ -389,6 +432,43 @@ class ChecklistsController extends ChangeNotifier {
notifyListeners();
}
Future<ListItem> restoreItem(ListItem item) async {
final restored = await _checklistService.restoreItem(
houseId,
item.listId,
item.id,
);
_items.removeWhere((i) => i.id == item.id);
if (!_isTrashMode) {
_items.add(restored);
_checklistService.cacheItems(_currentList!.id, List.of(_items));
}
notifyListeners();
return restored;
}
Future<void> permanentlyDeleteItem(ListItem item) async {
await _checklistService.permanentlyDeleteItem(
houseId,
item.listId,
item.id,
);
_items.removeWhere((i) => i.id == item.id);
if (!_isTrashMode) {
_checklistService.cacheItems(_currentList!.id, List.of(_items));
}
notifyListeners();
}
Future<void> emptyTrash() async {
if (_currentList == null) return;
await _checklistService.emptyTrash(houseId, _currentList!.id);
if (_isTrashMode) {
_items = [];
notifyListeners();
}
}
Future<void> toggleItem(ListItem item) async {
final index = _items.indexWhere((i) => i.id == item.id);
if (index == -1) return;
@@ -404,12 +484,23 @@ class ChecklistsController extends ChangeNotifier {
item.listId,
item.id,
);
_items[index] = updated;
// If toggling caused a soft-delete (deleteOnDone), drop it from active list.
if (updated.deletedAt != null) {
_items.removeWhere((i) => i.id == item.id);
} else {
final i = _items.indexWhere((x) => x.id == item.id);
if (i != -1) _items[i] = updated;
}
_checklistService.cacheItems(item.listId, List.of(_items));
notifyListeners();
} catch (e) {
// Revert on failure
_items[index] = item;
final i = _items.indexWhere((x) => x.id == item.id);
if (i != -1) {
_items[i] = item;
} else {
_items.insert(index.clamp(0, _items.length), item);
}
_checklistService.cacheItems(item.listId, List.of(_items));
notifyListeners();
}

View File

@@ -4,6 +4,7 @@ import 'package:pantry/models/category.dart' as models;
import 'package:provider/provider.dart';
import 'package:pantry/models/checklist.dart';
import 'package:pantry/services/prefs_service.dart';
import 'package:pantry/utils/category_icons.dart';
import 'package:pantry/utils/checklist_icons.dart';
import 'package:pantry/widgets/checklist_selector.dart';
@@ -176,23 +177,39 @@ class _ChecklistsBodyState extends State<_ChecklistsBody> {
lists: controller.lists,
currentList: controller.currentList,
onSelected: controller.selectList,
onCreateNew: () => _createList(context, controller),
),
),
IconButton(
icon: Icon(_searchOpen ? Icons.search_off : Icons.search),
onPressed: _toggleSearch,
),
ChecklistSortButton(
currentSort: controller.sortBy,
onSelected: controller.setSortBy,
if (!controller.isTrashMode)
IconButton(
icon: Icon(_searchOpen ? Icons.search_off : Icons.search),
onPressed: _toggleSearch,
),
if (!controller.isTrashMode)
ChecklistSortButton(
currentSort: controller.sortBy,
onSelected: controller.setSortBy,
),
PopupMenuButton<String>(
icon: Icon(
controller.isTrashMode ? Icons.delete : Icons.more_vert,
),
tooltip: controller.isTrashMode
? m.checklists.trashTitle
: null,
onSelected: (value) =>
_onListMenuSelected(context, controller, value),
itemBuilder: (_) => _listMenuItems(controller),
),
],
),
if (controller.isTrashMode && controller.currentList != null)
_TrashBanner(onExit: () => controller.setTrashMode(false)),
AnimatedSize(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
alignment: Alignment.topCenter,
child: _searchOpen
child: (_searchOpen && !controller.isTrashMode)
? _SearchPanel(
searchController: _searchController,
selectedCategoryIds: _selectedCategoryIds,
@@ -205,11 +222,12 @@ class _ChecklistsBodyState extends State<_ChecklistsBody> {
Expanded(child: itemsArea),
],
),
if (controller.currentList != null)
if (controller.currentList != null && !controller.isTrashMode)
PositionedDirectional(
end: 16,
bottom: 16,
child: FloatingActionButton(
heroTag: 'checklists-fab',
onPressed: () async {
final added = await Navigator.of(context).push<bool>(
MaterialPageRoute(
@@ -226,6 +244,149 @@ class _ChecklistsBodyState extends State<_ChecklistsBody> {
],
);
}
Future<void> _createList(
BuildContext context,
ChecklistsController controller,
) async {
final created = await showCreateListDialog(context, controller);
if (created != null) {
await controller.selectList(created);
}
}
List<PopupMenuEntry<String>> _listMenuItems(ChecklistsController controller) {
if (controller.isTrashMode) {
return [
PopupMenuItem(
value: 'exit_trash',
child: Row(
children: [
const Icon(Icons.arrow_back, size: 18),
const SizedBox(width: 8),
Text(m.checklists.exitTrash),
],
),
),
PopupMenuItem(
value: 'empty_trash',
child: Row(
children: [
const Icon(Icons.delete_forever, size: 18),
const SizedBox(width: 8),
Text(m.checklists.emptyTrash),
],
),
),
];
}
return [
PopupMenuItem(
value: 'view_trash',
child: Row(
children: [
const Icon(Icons.delete_outline, size: 18),
const SizedBox(width: 8),
Text(m.checklists.viewTrash),
],
),
),
];
}
Future<void> _onListMenuSelected(
BuildContext context,
ChecklistsController controller,
String value,
) async {
switch (value) {
case 'view_trash':
if (_searchOpen) {
setState(() {
_searchOpen = false;
_searchController.clear();
_selectedCategoryIds.clear();
});
}
await controller.setTrashMode(true);
case 'exit_trash':
await controller.setTrashMode(false);
case 'empty_trash':
await _confirmEmptyTrash(context, controller);
}
}
Future<void> _confirmEmptyTrash(
BuildContext context,
ChecklistsController controller,
) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(m.checklists.emptyTrashConfirm),
content: Text(m.checklists.emptyTrashConfirmBody),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text(m.common.cancel),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: Text(m.checklists.emptyTrash),
),
],
),
);
if (confirmed != true) return;
try {
await controller.emptyTrash();
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(m.checklists.emptyTrashFailed)));
}
}
}
}
class _TrashBanner extends StatelessWidget {
final VoidCallback onExit;
const _TrashBanner({required this.onExit});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
width: double.infinity,
color: theme.colorScheme.surfaceContainerHighest,
padding: const EdgeInsetsDirectional.fromSTEB(16, 8, 8, 8),
child: Row(
children: [
Icon(
Icons.delete_outline,
size: 18,
color: theme.colorScheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Expanded(
child: Text(
m.checklists.trashTitle,
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
TextButton.icon(
onPressed: onExit,
icon: const Icon(Icons.close, size: 16),
label: Text(m.checklists.exitTrash),
),
],
),
);
}
}
class _SearchPanel extends StatelessWidget {
@@ -441,25 +602,52 @@ class _ItemList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final unchecked = items.where((i) => !i.done).toList();
final checked = items.where((i) => i.done).toList();
if (items.isEmpty) {
return ListView(
children: [
const SizedBox(height: 100),
Center(
child: Text(
isFiltering ? m.checklists.noSearchResults : m.checklists.noItems,
controller.isTrashMode
? m.checklists.noTrashedItems
: (isFiltering
? m.checklists.noSearchResults
: m.checklists.noItems),
),
),
],
);
}
if (controller.isTrashMode) {
return CustomScrollView(
slivers: [
_ReorderablePartition(
items: items,
controller: controller,
categorySpacing: 'disabled',
allowReorder: false,
),
const SliverToBoxAdapter(child: SizedBox(height: 88)),
],
);
}
final unchecked = items.where((i) => !i.done).toList();
final checked = items.where((i) => i.done).toList();
final spacingPref = context.watch<PrefsService>().checklistCategorySpacing;
final categorySpacing = controller.sortBy == 'category'
? spacingPref
: 'disabled';
return CustomScrollView(
slivers: [
_ReorderablePartition(items: unchecked, controller: controller),
_ReorderablePartition(
items: unchecked,
controller: controller,
categorySpacing: categorySpacing,
),
if (checked.isNotEmpty) ...[
SliverToBoxAdapter(
child: Column(
@@ -481,7 +669,11 @@ class _ItemList extends StatelessWidget {
],
),
),
_ReorderablePartition(items: checked, controller: controller),
_ReorderablePartition(
items: checked,
controller: controller,
categorySpacing: categorySpacing,
),
],
const SliverToBoxAdapter(child: SizedBox(height: 88)),
],
@@ -492,8 +684,113 @@ class _ItemList extends StatelessWidget {
class _ReorderablePartition extends StatelessWidget {
final List<ListItem> items;
final ChecklistsController controller;
final String categorySpacing;
final bool allowReorder;
const _ReorderablePartition({required this.items, required this.controller});
const _ReorderablePartition({
required this.items,
required this.controller,
this.categorySpacing = 'disabled',
this.allowReorder = true,
});
void _toggleItem(
BuildContext context,
ChecklistsController controller,
ListItem item,
) {
final wasDone = item.done;
final wasDeleteOnDone = item.deleteOnDone;
controller.toggleItem(item);
if (wasDone) return;
final messenger = ScaffoldMessenger.of(context);
messenger.clearSnackBars();
messenger.showSnackBar(
SnackBar(
content: Text(m.checklists.itemMarkedDone),
duration: const Duration(seconds: 6),
action: SnackBarAction(
label: m.checklists.undo,
onPressed: () async {
if (wasDeleteOnDone) {
try {
await controller.restoreItem(item);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(m.checklists.restoreFailed)),
);
}
}
return;
}
final current = controller.items.firstWhere(
(i) => i.id == item.id,
orElse: () => item.copyWith(done: true),
);
if (current.done) controller.toggleItem(current);
},
),
),
);
}
Future<void> _restoreItem(
BuildContext context,
ChecklistsController controller,
ListItem item,
) async {
try {
await controller.restoreItem(item);
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(m.checklists.itemRestored)));
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(m.checklists.restoreFailed)));
}
}
}
void _permanentlyDelete(
BuildContext context,
ChecklistsController controller,
ListItem item,
) {
showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(m.checklists.permanentlyDeleteConfirm),
content: Text(m.checklists.permanentlyDeleteConfirmBody),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text(m.common.cancel),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: Text(m.common.delete),
),
],
),
).then((confirmed) async {
if (confirmed != true) return;
try {
await controller.permanentlyDeleteItem(item);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(m.checklists.permanentlyDeleteFailed)),
);
}
}
});
}
void _viewItem(
BuildContext context,
@@ -634,8 +931,52 @@ class _ReorderablePartition extends StatelessWidget {
});
}
Widget _tileFor(BuildContext context, int index) {
final item = items[index];
final showSeparator =
categorySpacing != 'disabled' &&
index > 0 &&
items[index - 1].categoryId != item.categoryId;
final tile = ChecklistItemTile(
key: allowReorder ? null : ValueKey(item.id),
item: item,
category: item.categoryId != null
? controller.categories[item.categoryId]
: null,
houseId: controller.houseId,
trashMode: controller.isTrashMode,
onToggle: (item) => _toggleItem(context, controller, item),
onView: (item) => _viewItem(context, controller, item),
onEdit: (item) => _editItem(context, controller, item),
onMove: (item) => _moveItem(context, controller, item),
onDelete: (item) => _deleteItem(context, controller, item),
onRestore: (item) => _restoreItem(context, controller, item),
onPermanentDelete: (item) =>
_permanentlyDelete(context, controller, item),
);
return showSeparator
? Column(
children: [
if (categorySpacing == 'divider')
const Divider(height: 25)
else
const SizedBox(height: 20),
tile,
],
)
: tile;
}
@override
Widget build(BuildContext context) {
if (!allowReorder) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _tileFor(context, index),
childCount: items.length,
),
);
}
return SliverReorderableList(
itemCount: items.length,
onReorder: (oldIndex, newIndex) {
@@ -647,18 +988,7 @@ class _ReorderablePartition extends StatelessWidget {
return ReorderableDelayedDragStartListener(
key: ValueKey(item.id),
index: index,
child: ChecklistItemTile(
item: item,
category: item.categoryId != null
? controller.categories[item.categoryId]
: null,
houseId: controller.houseId,
onToggle: controller.toggleItem,
onView: (item) => _viewItem(context, controller, item),
onEdit: (item) => _editItem(context, controller, item),
onMove: (item) => _moveItem(context, controller, item),
onDelete: (item) => _deleteItem(context, controller, item),
),
child: _tileFor(context, index),
);
},
);

View File

@@ -1,6 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:pantry/i18n.dart';
import 'package:pantry/models/category.dart' as models;
import 'package:pantry/models/checklist.dart';
@@ -10,6 +11,7 @@ import 'package:pantry/utils/category_icons.dart';
import 'package:pantry/utils/date_format.dart';
import 'package:pantry/utils/rrule.dart';
import 'package:pantry/utils/text_direction.dart';
import 'package:pantry/widgets/app_bar_back_leading.dart';
import 'checklists_controller.dart';
import 'item_form_view.dart';
@@ -52,6 +54,8 @@ class ItemDetailView extends StatelessWidget {
SliverAppBar(
expandedHeight: hasImage ? 280 : 0,
pinned: true,
toolbarHeight: theme.appBarTheme.toolbarHeight ?? kToolbarHeight,
leading: appBarBackLeading(context),
title: Directionality(
textDirection: nameDir,
child: Text(item.name),
@@ -77,6 +81,11 @@ class ItemDetailView extends StatelessWidget {
child: MarkdownBody(
data: item.description!,
shrinkWrap: true,
onTapLink: (text, href, title) {
if (href != null) {
launchUrl(Uri.parse(href));
}
},
styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith(
p: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,

View File

@@ -10,6 +10,7 @@ import 'package:pantry/models/checklist.dart';
import 'package:pantry/services/auth_service.dart';
import 'package:pantry/services/checklist_service.dart';
import 'package:pantry/utils/text_direction.dart';
import 'package:pantry/widgets/app_bar_back_leading.dart';
import 'package:pantry/widgets/category_picker.dart';
import 'package:pantry/widgets/recurrence_dialog.dart';
import 'package:pantry/widgets/repeat_button.dart';
@@ -151,7 +152,10 @@ class _ItemFormViewState extends State<ItemFormView> {
final f = m.checklists.itemForm;
return Scaffold(
appBar: AppBar(title: Text(_isEditing ? f.editTitle : f.addTitle)),
appBar: AppBar(
leading: appBarBackLeading(context),
title: Text(_isEditing ? f.editTitle : f.addTitle),
),
floatingActionButton: FloatingActionButton(
onPressed: _saving ? null : _save,
child: _saving

View File

@@ -1,3 +1,6 @@
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -9,8 +12,10 @@ import 'package:pantry/models/house.dart';
import 'package:pantry/services/deep_link_service.dart';
import 'package:pantry/views/notifications/notifications_controller.dart';
import 'package:pantry/views/notifications/notifications_view.dart';
import 'package:pantry/services/share_intent_service.dart';
import 'package:pantry/views/photos/photo_board_view.dart';
import 'package:pantry/views/settings/settings_view.dart';
import 'package:pantry/views/share/share_router_view.dart';
import 'package:pantry/widgets/create_house_dialog.dart';
import 'package:pantry/widgets/no_houses_view.dart';
import 'package:pantry/widgets/notifications_bell.dart';
@@ -66,26 +71,31 @@ class _HomeViewBodyState extends State<_HomeViewBody>
final _pageController = PageController();
final _notificationsController = NotificationsController();
static bool get _isMacOS => !kIsWeb && Platform.isMacOS;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_notificationsController.load();
// Consume any deep link that arrived before we mounted (e.g. from a
// cold-start notification tap).
// Consume any deep link or share intent that arrived before we
// mounted (e.g. from a cold-start notification tap or share sheet).
WidgetsBinding.instance.addPostFrameCallback((_) {
_consumePendingDeepLink();
_consumePendingShare();
});
// Listen for deep links that arrive while the home view is mounted
// (notification tapped while app is in foreground or background).
// Listen for deep links and share intents that arrive while the home
// view is mounted.
DeepLinkService.instance.pending.addListener(_consumePendingDeepLink);
ShareIntentService.instance.pending.addListener(_consumePendingShare);
}
@override
void dispose() {
DeepLinkService.instance.pending.removeListener(_consumePendingDeepLink);
ShareIntentService.instance.pending.removeListener(_consumePendingShare);
WidgetsBinding.instance.removeObserver(this);
_pageController.dispose();
_notificationsController.dispose();
@@ -97,16 +107,32 @@ class _HomeViewBodyState extends State<_HomeViewBody>
if (state == AppLifecycleState.resumed) {
_notificationsController.refresh();
_consumePendingDeepLink();
_consumePendingShare();
}
}
void _consumePendingShare() {
final files = ShareIntentService.instance.consume();
if (files == null || files.isEmpty || !mounted) return;
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ShareRouterView(files: files),
fullscreenDialog: true,
),
);
}
void _goToTab(int index) {
if (index == _tabIndex) return;
_pageController.animateToPage(
index,
duration: const Duration(milliseconds: 280),
curve: Curves.easeInOut,
);
if (_pageController.hasClients) {
_pageController.animateToPage(
index,
duration: const Duration(milliseconds: 280),
curve: Curves.easeInOut,
);
} else {
setState(() => _tabIndex = index);
}
}
void _consumePendingDeepLink() {
@@ -144,65 +170,118 @@ class _HomeViewBodyState extends State<_HomeViewBody>
@override
Widget build(BuildContext context) {
final controller = context.watch<HomeController>();
final houseId = controller.currentHouse?.id;
final destinations = <_NavDestination>[
(icon: Icons.assignment_turned_in, label: m.nav.checklists),
(icon: Icons.photo, label: m.nav.photoBoard),
(icon: Icons.insert_drive_file, label: m.nav.notesWall),
];
return Scaffold(
appBar: AppBar(
title: Text(_tabTitle),
actions: [
if (_tabIndex == 0 && houseId != null)
IconButton(
icon: const Icon(Icons.sell_outlined),
tooltip: m.checklists.categories,
onPressed: () {
return LayoutBuilder(
builder: (context, constraints) {
final useRail = constraints.maxWidth >= 720;
final extendedRail = constraints.maxWidth >= 1100;
final body = _buildBody(controller, useRail: useRail);
final appBar = AppBar(
title: Text(_tabTitle),
actions: [
if (_tabIndex == 0 && houseId != null)
IconButton(
icon: const Icon(Icons.sell_outlined),
tooltip: m.checklists.categories,
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => CategoriesView(houseId: houseId),
),
);
},
),
NotificationsBell(
controller: _notificationsController,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => CategoriesView(houseId: houseId),
builder: (_) =>
NotificationsView(controller: _notificationsController),
),
);
},
),
NotificationsBell(
controller: _notificationsController,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) =>
NotificationsView(controller: _notificationsController),
),
);
},
UserMenuButton(
houses: controller.houses,
currentHouse: controller.currentHouse,
onHouseSelected: controller.selectHouse,
onCreateHouse: () => showCreateHouseDialog(context, controller),
onOpenSettings: () {
Navigator.of(
context,
).push(MaterialPageRoute(builder: (_) => const SettingsView()));
},
onLogout: widget.onLogout,
),
],
);
if (useRail) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
NavigationRail(
extended: extendedRail,
selectedIndex: _tabIndex,
onDestinationSelected: _goToTab,
labelType: extendedRail
? NavigationRailLabelType.none
: NavigationRailLabelType.all,
leading: _isMacOS ? const SizedBox(height: 24) : null,
destinations: [
for (final d in destinations)
NavigationRailDestination(
icon: Icon(d.icon),
label: Text(d.label),
),
],
),
const VerticalDivider(width: 1, thickness: 1),
Expanded(
child: Column(
children: [
appBar,
Expanded(
child: Padding(
padding: EdgeInsetsDirectional.only(
start: _tabIndex == 0 ? 0 : 16,
),
child: body,
),
),
],
),
),
],
),
),
);
}
return Scaffold(
appBar: appBar,
body: body,
bottomNavigationBar: _AnimatedBottomNav(
pageController: _pageController,
currentIndex: _tabIndex,
onTap: _goToTab,
destinations: destinations,
),
UserMenuButton(
houses: controller.houses,
currentHouse: controller.currentHouse,
onHouseSelected: controller.selectHouse,
onCreateHouse: () => showCreateHouseDialog(context, controller),
onOpenSettings: () {
Navigator.of(
context,
).push(MaterialPageRoute(builder: (_) => const SettingsView()));
},
onLogout: widget.onLogout,
),
],
),
body: _buildBody(controller),
bottomNavigationBar: _AnimatedBottomNav(
pageController: _pageController,
currentIndex: _tabIndex,
onTap: _goToTab,
destinations: [
(icon: Icons.assignment_turned_in, label: m.nav.checklists),
(icon: Icons.photo, label: m.nav.photoBoard),
(icon: Icons.insert_drive_file, label: m.nav.notesWall),
],
),
);
},
);
}
Widget _buildBody(HomeController controller) {
Widget _buildBody(HomeController controller, {required bool useRail}) {
if (controller.isLoading) {
return const Center(child: CircularProgressIndicator());
}
@@ -235,15 +314,19 @@ class _HomeViewBodyState extends State<_HomeViewBody>
}
final houseId = controller.currentHouse!.id;
final pages = [
ChecklistsView(key: ValueKey('checklists-$houseId'), houseId: houseId),
PhotoBoardView(key: ValueKey('photos-$houseId'), houseId: houseId),
NotesWallView(key: ValueKey('notes-$houseId'), houseId: houseId),
];
if (useRail) {
return IndexedStack(index: _tabIndex, children: pages);
}
return PageView(
controller: _pageController,
physics: const ClampingScrollPhysics(),
onPageChanged: (i) => setState(() => _tabIndex = i),
children: [
ChecklistsView(key: ValueKey('checklists-$houseId'), houseId: houseId),
PhotoBoardView(key: ValueKey('photos-$houseId'), houseId: houseId),
NotesWallView(key: ValueKey('notes-$houseId'), houseId: houseId),
],
children: pages,
);
}
}

View File

@@ -81,8 +81,8 @@ class LoginController extends ChangeNotifier {
notifyListeners();
_onLoginSuccess?.call();
}
} catch (_) {
// Expected — 404 until login completes, or transient network errors
} catch (e, st) {
debugPrint('[LoginController] Poll error: $e\n$st');
}
});
}

View File

@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:pantry/models/note.dart';
import 'package:pantry/utils/text_direction.dart';
import 'package:pantry/views/notes/note_form_view.dart';
import 'package:pantry/views/notes/notes_controller.dart';
import 'package:pantry/widgets/app_bar_back_leading.dart';
class NoteDetailView extends StatelessWidget {
final Note note;
@@ -31,17 +33,11 @@ class NoteDetailView extends StatelessWidget {
appBar: AppBar(
backgroundColor: bgColor,
foregroundColor: textColor,
title: Align(
alignment: titleDir == TextDirection.rtl
? Alignment.centerRight
: Alignment.centerLeft,
child: Directionality(
textDirection: titleDir,
child: Text(note.title),
),
),
leading: appBarBackLeading(context),
title: Directionality(textDirection: titleDir, child: Text(note.title)),
),
floatingActionButton: FloatingActionButton(
heroTag: null,
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
@@ -51,70 +47,84 @@ class NoteDetailView extends StatelessWidget {
},
child: const Icon(Icons.edit),
),
body: note.content != null && note.content!.isNotEmpty
? Directionality(
textDirection: contentDir,
child: Markdown(
data: note.content!,
padding: const EdgeInsets.all(16),
selectable: true,
styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith(
p: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
h1: theme.textTheme.headlineMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h2: theme.textTheme.headlineSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h3: theme.textTheme.titleLarge?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h4: theme.textTheme.titleMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
listBullet: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
code: TextStyle(
color: textColor,
backgroundColor: textColor.withAlpha(30),
fontFamily: 'monospace',
),
codeblockDecoration: BoxDecoration(
color: textColor.withAlpha(30),
borderRadius: BorderRadius.circular(6),
),
blockquote: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(180),
fontStyle: FontStyle.italic,
),
blockquoteDecoration: BoxDecoration(
border: Border(
left: BorderSide(
color: textColor.withAlpha(100),
width: 4,
body: Hero(
tag: 'note-${note.id}',
child: Material(
color: bgColor,
child: note.content != null && note.content!.isNotEmpty
? Directionality(
textDirection: contentDir,
child: Markdown(
data: note.content!,
padding: const EdgeInsets.all(16),
selectable: true,
onTapLink: (text, href, title) {
if (href != null) {
launchUrl(Uri.parse(href));
}
},
styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith(
p: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
h1: theme.textTheme.headlineMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h2: theme.textTheme.headlineSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h3: theme.textTheme.titleLarge?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h4: theme.textTheme.titleMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
listBullet: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
code: TextStyle(
color: textColor,
backgroundColor: textColor.withAlpha(30),
fontFamily: 'monospace',
),
codeblockDecoration: BoxDecoration(
color: textColor.withAlpha(30),
borderRadius: BorderRadius.circular(6),
),
blockquote: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(180),
fontStyle: FontStyle.italic,
),
blockquoteDecoration: BoxDecoration(
border: Border(
left: BorderSide(
color: textColor.withAlpha(100),
width: 4,
),
),
),
a: TextStyle(
color: textColor,
decoration: TextDecoration.underline,
),
strong: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
em: TextStyle(
color: textColor,
fontStyle: FontStyle.italic,
),
),
),
a: TextStyle(
color: textColor,
decoration: TextDecoration.underline,
),
strong: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
em: TextStyle(color: textColor, fontStyle: FontStyle.italic),
),
),
)
: null,
)
: null,
),
),
);
}
}

View File

@@ -4,6 +4,7 @@ import 'package:pantry/i18n.dart';
import 'package:pantry/models/note.dart';
import 'package:pantry/utils/text_direction.dart';
import 'package:pantry/views/notes/notes_controller.dart';
import 'package:pantry/widgets/app_bar_back_leading.dart';
const _colorOptions = <String?>[
null, // default / no color
@@ -29,7 +30,16 @@ class NoteFormView extends StatefulWidget {
final NotesController controller;
final Note? note;
const NoteFormView({super.key, required this.controller, this.note});
/// When opening a new note seeded from an OS share intent, this holds
/// the shared text/URL to prefill into the content field.
final String? prefillContent;
const NoteFormView({
super.key,
required this.controller,
this.note,
this.prefillContent,
});
@override
State<NoteFormView> createState() => _NoteFormViewState();
@@ -50,11 +60,13 @@ class _NoteFormViewState extends State<NoteFormView> {
super.initState();
_titleController = TextEditingController(text: widget.note?.title ?? '');
_contentController = TextEditingController(
text: widget.note?.content ?? '',
text: widget.note?.content ?? widget.prefillContent ?? '',
);
_selectedColor = widget.note?.color;
_titleDir = detectTextDirection(widget.note?.title);
_contentDir = detectTextDirection(widget.note?.content);
_contentDir = detectTextDirection(
widget.note?.content ?? widget.prefillContent,
);
_titleController.addListener(() {
final dir = detectTextDirection(_titleController.text);
if (dir != _titleDir) setState(() => _titleDir = dir);
@@ -104,10 +116,26 @@ class _NoteFormViewState extends State<NoteFormView> {
}
}
Color get _bgColor {
if (_selectedColor != null && _selectedColor!.isNotEmpty) {
final hex = _selectedColor!.replaceFirst('#', '');
final value = int.tryParse('FF$hex', radix: 16);
if (value != null) return Color(value);
}
return Theme.of(context).colorScheme.surfaceContainerHighest;
}
@override
Widget build(BuildContext context) {
final bgColor = _bgColor;
final textColor = _contrastColor(bgColor);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
backgroundColor: bgColor,
foregroundColor: textColor,
leading: appBarBackLeading(context),
title: Text(_isEditing ? m.notesWall.editNote : m.notesWall.newNote),
),
floatingActionButton: FloatingActionButton(
@@ -120,76 +148,92 @@ class _NoteFormViewState extends State<NoteFormView> {
)
: const Icon(Icons.check),
),
body: ListView(
padding: const EdgeInsets.all(16),
body: Column(
children: [
TextField(
controller: _titleController,
decoration: InputDecoration(
labelText: m.notesWall.title,
border: const OutlineInputBorder(),
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(16, 16, 16, 0),
child: TextField(
controller: _titleController,
decoration: InputDecoration(
hintText: m.notesWall.title,
hintStyle: TextStyle(color: textColor.withAlpha(100)),
border: InputBorder.none,
),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
autofocus: !_isEditing,
textCapitalization: TextCapitalization.sentences,
textInputAction: TextInputAction.next,
textDirection: _titleDir,
),
autofocus: !_isEditing,
textCapitalization: TextCapitalization.sentences,
textInputAction: TextInputAction.next,
textDirection: _titleDir,
),
const SizedBox(height: 16),
TextField(
controller: _contentController,
decoration: InputDecoration(
labelText: m.notesWall.content,
border: const OutlineInputBorder(),
alignLabelWithHint: true,
),
textCapitalization: TextCapitalization.sentences,
maxLines: 10,
minLines: 4,
textDirection: _contentDir,
),
const SizedBox(height: 16),
Text(
m.notesWall.color,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _colorOptions.map((hex) {
final color = hex != null
? Color(
int.parse('FF${hex.replaceFirst('#', '')}', radix: 16),
)
: Theme.of(context).colorScheme.surfaceContainerHighest;
final isSelected = _selectedColor == hex;
return GestureDetector(
onTap: () => setState(() => _selectedColor = hex),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(
color: Theme.of(context).colorScheme.primary,
width: 3,
)
: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: isSelected
? Icon(
Icons.check,
size: 18,
color: _contrastColor(color),
)
: null,
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(16, 0, 16, 0),
child: TextField(
controller: _contentController,
decoration: InputDecoration(
hintText: m.notesWall.content,
hintStyle: TextStyle(color: textColor.withAlpha(100)),
border: InputBorder.none,
),
);
}).toList(),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
textCapitalization: TextCapitalization.sentences,
maxLines: null,
expands: true,
textAlignVertical: TextAlignVertical.top,
textDirection: _contentDir,
),
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsetsDirectional.fromSTEB(16, 8, 80, 48),
child: Row(
children: _colorOptions.map((hex) {
final color = hex != null
? Color(
int.parse('FF${hex.replaceFirst('#', '')}', radix: 16),
)
: Theme.of(context).colorScheme.surfaceContainerHighest;
final isSelected = _selectedColor == hex;
return Padding(
padding: const EdgeInsetsDirectional.only(end: 8),
child: GestureDetector(
onTap: () => setState(() => _selectedColor = hex),
child: CustomPaint(
foregroundPainter: hex == null
? _DiagonalLinePainter(
color: textColor.withAlpha(120),
)
: null,
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(color: textColor, width: 3)
: Border.all(color: textColor.withAlpha(60)),
),
child: isSelected
? Icon(
Icons.check,
size: 18,
color: _contrastColor(color),
)
: null,
),
),
),
);
}).toList(),
),
),
],
),
@@ -200,3 +244,34 @@ class _NoteFormViewState extends State<NoteFormView> {
return bg.computeLuminance() > 0.5 ? Colors.black87 : Colors.white;
}
}
class _DiagonalLinePainter extends CustomPainter {
final Color color;
_DiagonalLinePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = 2
..style = PaintingStyle.stroke;
final center = size.center(Offset.zero);
final radius = size.width / 2;
canvas.clipRRect(
RRect.fromRectAndRadius(
Rect.fromCircle(center: center, radius: radius),
Radius.circular(radius),
),
);
canvas.drawLine(
Offset(size.width * 0.15, size.height * 0.85),
Offset(size.width * 0.85, size.height * 0.15),
paint,
);
}
@override
bool shouldRepaint(_DiagonalLinePainter oldDelegate) =>
color != oldDelegate.color;
}

Some files were not shown because too many files have changed in this diff Show More