mirror of
https://github.com/chenasraf/pantry-flutter.git
synced 2026-05-18 01:28:58 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e53481cdf | |||
| 116eb18693 | |||
| bbdc71c72f | |||
| cf2388db2b | |||
|
|
9fdee69cb9 | ||
| 4a0832a359 | |||
| 88e153b96f | |||
| cf9135e069 | |||
| db7cc6ffab | |||
| ec689d2940 | |||
| 00241b8ace | |||
| eb4d8d3c50 | |||
| ddf0c365a1 | |||
| eafc267e92 | |||
| 291d8c3bb5 | |||
|
|
903fd823d8 | ||
| bca375d701 | |||
| 28f8a269f8 | |||
| c944ec5140 | |||
| 550027e1bc | |||
| 41e8ac13a0 | |||
| d8802690c0 | |||
| 60b16aad30 | |||
| 689e4d6cad | |||
| db3bbc0f17 | |||
|
|
504da80c09 | ||
| 0c575eaa26 | |||
| 38b5d8b464 | |||
| ef2bc851de | |||
|
|
448d85834b | ||
| 7b5f9c1518 | |||
| 67581d04f0 | |||
| be83067fb7 | |||
| ea4590f0ed |
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@@ -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
|
||||
@@ -190,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
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,5 +1,46 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
75
Gemfile.lock
75
Gemfile.lock
@@ -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
|
||||
|
||||
98
Makefile
98
Makefile
@@ -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
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
BIN
assets/icon/icon_macos.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
39
assets/logo_icon_macos.svg
Normal file
39
assets/logo_icon_macos.svg
Normal 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 |
@@ -1,2 +1,2 @@
|
||||
metadata_path("./fastlane/metadata/ios")
|
||||
screenshots_path("./fastlane/metadata/ios/screenshots")
|
||||
screenshots_path("./fastlane/metadata/ios/en-US/screenshots")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
2
fastlane/metadata/android/en-US/changelogs/24.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/24.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Bug Fixes
|
||||
- make markdown links clickable
|
||||
4
fastlane/metadata/android/en-US/changelogs/25.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/25.txt
Normal 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
|
||||
5
fastlane/metadata/android/en-US/changelogs/26.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/26.txt
Normal 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
|
||||
5
fastlane/metadata/android/en-US/changelogs/27.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/27.txt
Normal 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
|
||||
2
fastlane/metadata/ios/en-US/changelogs/24.txt
Normal file
2
fastlane/metadata/ios/en-US/changelogs/24.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Bug Fixes
|
||||
- make markdown links clickable
|
||||
4
fastlane/metadata/ios/en-US/changelogs/25.txt
Normal file
4
fastlane/metadata/ios/en-US/changelogs/25.txt
Normal 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
|
||||
5
fastlane/metadata/ios/en-US/changelogs/26.txt
Normal file
5
fastlane/metadata/ios/en-US/changelogs/26.txt
Normal 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
|
||||
6
fastlane/metadata/ios/en-US/changelogs/27.txt
Normal file
6
fastlane/metadata/ios/en-US/changelogs/27.txt
Normal 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
|
||||
1
fastlane/metadata/macos/copyright.txt
Normal file
1
fastlane/metadata/macos/copyright.txt
Normal file
@@ -0,0 +1 @@
|
||||
2026 Chen Asraf
|
||||
5
fastlane/metadata/macos/en-US/changelogs/26.txt
Normal file
5
fastlane/metadata/macos/en-US/changelogs/26.txt
Normal 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
|
||||
6
fastlane/metadata/macos/en-US/changelogs/27.txt
Normal file
6
fastlane/metadata/macos/en-US/changelogs/27.txt
Normal 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
|
||||
25
fastlane/metadata/macos/en-US/description.txt
Normal file
25
fastlane/metadata/macos/en-US/description.txt
Normal 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.
|
||||
1
fastlane/metadata/macos/en-US/keywords.txt
Normal file
1
fastlane/metadata/macos/en-US/keywords.txt
Normal file
@@ -0,0 +1 @@
|
||||
nextcloud, checklist, todo, shopping list, notes, self-hosted, household
|
||||
1
fastlane/metadata/macos/en-US/marketing_url.txt
Normal file
1
fastlane/metadata/macos/en-US/marketing_url.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://github.com/chenasraf/pantry-flutter
|
||||
1
fastlane/metadata/macos/en-US/name.txt
Normal file
1
fastlane/metadata/macos/en-US/name.txt
Normal file
@@ -0,0 +1 @@
|
||||
Pantry for Nextcloud
|
||||
1
fastlane/metadata/macos/en-US/privacy_url.txt
Normal file
1
fastlane/metadata/macos/en-US/privacy_url.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://casraf.dev/pantry-privacy-policy
|
||||
1
fastlane/metadata/macos/en-US/promotional_text.txt
Normal file
1
fastlane/metadata/macos/en-US/promotional_text.txt
Normal file
@@ -0,0 +1 @@
|
||||
Manage your household on your Nextcloud — shared lists, photos & notes.
|
||||
6
fastlane/metadata/macos/en-US/release_notes.txt
Normal file
6
fastlane/metadata/macos/en-US/release_notes.txt
Normal 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
|
||||
BIN
fastlane/metadata/macos/en-US/screenshots/0_APP_DESKTOP_0.png
Normal file
BIN
fastlane/metadata/macos/en-US/screenshots/0_APP_DESKTOP_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
fastlane/metadata/macos/en-US/screenshots/1_APP_DESKTOP_1.png
Normal file
BIN
fastlane/metadata/macos/en-US/screenshots/1_APP_DESKTOP_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
BIN
fastlane/metadata/macos/en-US/screenshots/2_APP_DESKTOP_2.png
Normal file
BIN
fastlane/metadata/macos/en-US/screenshots/2_APP_DESKTOP_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
1
fastlane/metadata/macos/en-US/subtitle.txt
Normal file
1
fastlane/metadata/macos/en-US/subtitle.txt
Normal file
@@ -0,0 +1 @@
|
||||
Home lists, photos & notes
|
||||
1
fastlane/metadata/macos/en-US/support_url.txt
Normal file
1
fastlane/metadata/macos/en-US/support_url.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://github.com/chenasraf/pantry-flutter
|
||||
1
fastlane/metadata/macos/primary_category.txt
Normal file
1
fastlane/metadata/macos/primary_category.txt
Normal file
@@ -0,0 +1 @@
|
||||
PRODUCTIVITY
|
||||
1
fastlane/metadata/macos/primary_first_sub_category.txt
Normal file
1
fastlane/metadata/macos/primary_first_sub_category.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
fastlane/metadata/macos/primary_second_sub_category.txt
Normal file
1
fastlane/metadata/macos/primary_second_sub_category.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
EQq38!t9uA!@RdAkn6umJHo@nDh3ZZwM
|
||||
1
fastlane/metadata/macos/review_information/demo_user.txt
Normal file
1
fastlane/metadata/macos/review_information/demo_user.txt
Normal file
@@ -0,0 +1 @@
|
||||
store-test
|
||||
@@ -0,0 +1 @@
|
||||
casraf@pm.me
|
||||
@@ -0,0 +1 @@
|
||||
Chen
|
||||
1
fastlane/metadata/macos/review_information/last_name.txt
Normal file
1
fastlane/metadata/macos/review_information/last_name.txt
Normal file
@@ -0,0 +1 @@
|
||||
Asraf
|
||||
3
fastlane/metadata/macos/review_information/notes.txt
Normal file
3
fastlane/metadata/macos/review_information/notes.txt
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
+972549107970
|
||||
1
fastlane/metadata/macos/secondary_category.txt
Normal file
1
fastlane/metadata/macos/secondary_category.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
fastlane/metadata/macos/secondary_first_sub_category.txt
Normal file
1
fastlane/metadata/macos/secondary_first_sub_category.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
10
ios/Gemfile
Normal file
10
ios/Gemfile
Normal 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
194
ios/Gemfile.lock
Normal 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
|
||||
14
ios/Podfile
14
ios/Podfile
@@ -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)
|
||||
|
||||
@@ -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
2
ios/Profile.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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>
|
||||
|
||||
10
ios/Runner/Runner.entitlements
Normal file
10
ios/Runner/Runner.entitlements
Normal 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>
|
||||
24
ios/Share/Base.lproj/MainInterface.storyboard
Normal file
24
ios/Share/Base.lproj/MainInterface.storyboard
Normal 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
29
ios/Share/Info.plist
Normal 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>
|
||||
346
ios/Share/RSIShareViewController.swift
Normal file
346
ios/Share/RSIShareViewController.swift
Normal 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"
|
||||
}
|
||||
}
|
||||
10
ios/Share/ShareRelease.entitlements
Normal file
10
ios/Share/ShareRelease.entitlements
Normal 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>
|
||||
10
ios/Share/ShareViewController.swift
Normal file
10
ios/Share/ShareViewController.swift
Normal 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.
|
||||
}
|
||||
180
lib/main.dart
180
lib/main.dart
@@ -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),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 A–Z""",
|
||||
"""photoBoard.sort.captionZA""": """Caption Z–A""",
|
||||
"""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""",
|
||||
|
||||
@@ -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 Z–A"
|
||||
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
|
||||
|
||||
@@ -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 A–Z""",
|
||||
"""photoBoard.sort.captionZA""": """Beschriftung Z–A""",
|
||||
"""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""",
|
||||
|
||||
@@ -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 Z–A"
|
||||
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
|
||||
|
||||
@@ -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 A–Z""",
|
||||
"""photoBoard.sort.captionZA""": """Descripción Z–A""",
|
||||
"""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""",
|
||||
|
||||
@@ -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 Z–A"
|
||||
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
|
||||
|
||||
@@ -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 A–Z""",
|
||||
"""photoBoard.sort.captionZA""": """Légende Z–A""",
|
||||
"""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""",
|
||||
|
||||
@@ -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 Z–A"
|
||||
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"
|
||||
|
||||
@@ -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""": """יומי""",
|
||||
|
||||
@@ -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: הגדרות מוכנות
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
34
lib/services/pending_note_share_service.dart
Normal file
34
lib/services/pending_note_share_service.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
40
lib/services/pending_photo_share_service.dart
Normal file
40
lib/services/pending_photo_share_service.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
53
lib/services/share_intent_service.dart
Normal file
53
lib/services/share_intent_service.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
12
lib/utils/platform_info.dart
Normal file
12
lib/utils/platform_info.dart
Normal 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;
|
||||
}
|
||||
@@ -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: [
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,15 +33,8 @@ 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,
|
||||
@@ -63,6 +58,11 @@ class NoteDetailView extends StatelessWidget {
|
||||
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),
|
||||
|
||||
@@ -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);
|
||||
@@ -123,6 +135,7 @@ class _NoteFormViewState extends State<NoteFormView> {
|
||||
appBar: AppBar(
|
||||
backgroundColor: bgColor,
|
||||
foregroundColor: textColor,
|
||||
leading: appBarBackLeading(context),
|
||||
title: Text(_isEditing ? m.notesWall.editNote : m.notesWall.newNote),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pantry/i18n.dart';
|
||||
import 'package:pantry/services/pending_note_share_service.dart';
|
||||
import 'package:pantry/widgets/note_selection_actions.dart';
|
||||
import 'package:pantry/widgets/note_sort_button.dart';
|
||||
import 'package:pantry/widgets/note_tile.dart';
|
||||
@@ -23,14 +24,30 @@ class _NotesWallViewState extends State<NotesWallView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.load();
|
||||
PendingNoteShareService.instance.addListener(_handlePendingShare);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _handlePendingShare());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
PendingNoteShareService.instance.removeListener(_handlePendingShare);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handlePendingShare() {
|
||||
final share = PendingNoteShareService.instance.takeForHouse(widget.houseId);
|
||||
if (share == null || !mounted) return;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => NoteFormView(
|
||||
controller: _controller,
|
||||
prefillContent: share.content,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
@@ -108,6 +125,7 @@ class _NotesWallBody extends StatelessWidget {
|
||||
end: 16,
|
||||
bottom: 16,
|
||||
child: FloatingActionButton(
|
||||
heroTag: 'notes-fab',
|
||||
onPressed: () => _createNote(context, controller),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:pantry/i18n.dart';
|
||||
import 'package:pantry/models/notification.dart';
|
||||
import 'package:pantry/services/deep_link_service.dart';
|
||||
import 'package:pantry/widgets/app_bar_back_leading.dart';
|
||||
import 'notifications_controller.dart';
|
||||
|
||||
class NotificationsView extends StatefulWidget {
|
||||
@@ -53,6 +54,7 @@ class _NotificationsBody extends StatelessWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: appBarBackLeading(context),
|
||||
title: Text(m.notifications.title),
|
||||
actions: [
|
||||
if (controller.notifications.isNotEmpty)
|
||||
|
||||
@@ -2,12 +2,14 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:pantry/i18n.dart';
|
||||
import 'package:pantry/models/photo.dart';
|
||||
import 'package:pantry/services/pending_photo_share_service.dart';
|
||||
import 'package:pantry/services/photo_service.dart';
|
||||
|
||||
class UploadTask {
|
||||
final String fileName;
|
||||
final Uint8List? thumbnailBytes;
|
||||
final String mimeType;
|
||||
final int? folderId;
|
||||
double progress;
|
||||
bool done;
|
||||
String? error;
|
||||
@@ -17,6 +19,7 @@ class UploadTask {
|
||||
required this.fileName,
|
||||
this.thumbnailBytes,
|
||||
this.mimeType = 'image/jpeg',
|
||||
this.folderId,
|
||||
}) : progress = 0.0,
|
||||
done = false;
|
||||
|
||||
@@ -31,13 +34,18 @@ class UploadTask {
|
||||
class PhotoBoardController extends ChangeNotifier {
|
||||
final int houseId;
|
||||
|
||||
PhotoBoardController({required this.houseId});
|
||||
PhotoBoardController({required this.houseId}) {
|
||||
PendingPhotoShareService.instance.addListener(_consumePendingShares);
|
||||
// Consume any shares that arrived while this controller didn't exist.
|
||||
_consumePendingShares();
|
||||
}
|
||||
|
||||
bool _disposed = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
PendingPhotoShareService.instance.removeListener(_consumePendingShares);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -281,7 +289,8 @@ class PhotoBoardController extends ChangeNotifier {
|
||||
|
||||
// -- Upload --
|
||||
|
||||
Future<void> uploadPhotos(List<XFile> files) async {
|
||||
Future<void> uploadPhotos(List<XFile> files, {int? folderId}) async {
|
||||
final target = folderId ?? _currentFolderId;
|
||||
// Create all tasks up front with thumbnail bytes
|
||||
final tasks = <(UploadTask, XFile)>[];
|
||||
for (final file in files) {
|
||||
@@ -290,6 +299,7 @@ class PhotoBoardController extends ChangeNotifier {
|
||||
fileName: file.name,
|
||||
thumbnailBytes: bytes,
|
||||
mimeType: file.mimeType ?? 'image/jpeg',
|
||||
folderId: target,
|
||||
);
|
||||
_uploads.add(task);
|
||||
tasks.add((task, file));
|
||||
@@ -313,7 +323,7 @@ class PhotoBoardController extends ChangeNotifier {
|
||||
bytes: task.thumbnailBytes!,
|
||||
fileName: task.fileName,
|
||||
mimeType: task.mimeType,
|
||||
folderId: _currentFolderId,
|
||||
folderId: task.folderId,
|
||||
);
|
||||
_photos.insert(0, photo);
|
||||
_service.cachePhotos(houseId, _photos);
|
||||
@@ -336,6 +346,15 @@ class PhotoBoardController extends ChangeNotifier {
|
||||
_cleanUpDoneUploads();
|
||||
}
|
||||
|
||||
void _consumePendingShares() {
|
||||
final shares = PendingPhotoShareService.instance.takeForHouse(houseId);
|
||||
if (shares.isEmpty) return;
|
||||
for (final share in shares) {
|
||||
final files = share.paths.map((p) => XFile(p)).toList();
|
||||
uploadPhotos(files, folderId: share.folderId);
|
||||
}
|
||||
}
|
||||
|
||||
void dismissUpload(UploadTask task) {
|
||||
_uploads.remove(task);
|
||||
notifyListeners();
|
||||
|
||||
@@ -96,11 +96,7 @@ class _PhotoBoardBody extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
end: 16,
|
||||
bottom: 16,
|
||||
child: PhotoAddButton(controller: controller),
|
||||
),
|
||||
Positioned.fill(child: PhotoAddButton(controller: controller)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,35 +1,227 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:pantry/models/photo.dart';
|
||||
import 'package:pantry/services/photo_service.dart';
|
||||
import 'package:pantry/views/photos/photo_board_controller.dart';
|
||||
import 'package:pantry/widgets/app_bar_back_leading.dart';
|
||||
|
||||
class PhotoDetailView extends StatelessWidget {
|
||||
class PhotoDetailView extends StatefulWidget {
|
||||
final Photo photo;
|
||||
final Uri imageUri;
|
||||
final int houseId;
|
||||
final Map<String, String> headers;
|
||||
final PhotoBoardController controller;
|
||||
|
||||
const PhotoDetailView({
|
||||
super.key,
|
||||
required this.photo,
|
||||
required this.imageUri,
|
||||
required this.houseId,
|
||||
required this.headers,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PhotoDetailView> createState() => _PhotoDetailViewState();
|
||||
}
|
||||
|
||||
class _PhotoDetailViewState extends State<PhotoDetailView> {
|
||||
static bool get _isDesktop =>
|
||||
kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux;
|
||||
|
||||
late PageController _pageController;
|
||||
late final FocusNode _focusNode;
|
||||
final Map<int, TransformationController> _transformControllers = {};
|
||||
final Map<int, bool> _zoomedByIndex = {};
|
||||
|
||||
late List<Photo> _photos;
|
||||
late int _currentIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_photos = _photoList();
|
||||
_currentIndex = _photos.indexWhere((p) => p.id == widget.photo.id);
|
||||
if (_currentIndex < 0) _currentIndex = 0;
|
||||
_pageController = PageController(initialPage: _currentIndex);
|
||||
_focusNode = FocusNode();
|
||||
widget.controller.addListener(_onControllerChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_onControllerChanged);
|
||||
for (final c in _transformControllers.values) {
|
||||
c.dispose();
|
||||
}
|
||||
_pageController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<Photo> _photoList() => widget.controller.visiblePhotos;
|
||||
|
||||
void _onControllerChanged() {
|
||||
if (!mounted) return;
|
||||
final newList = _photoList();
|
||||
final currentId = _currentIndex < _photos.length
|
||||
? _photos[_currentIndex].id
|
||||
: null;
|
||||
final newIndex = currentId == null
|
||||
? -1
|
||||
: newList.indexWhere((p) => p.id == currentId);
|
||||
setState(() {
|
||||
_photos = newList;
|
||||
if (_photos.isEmpty) {
|
||||
Navigator.of(context).maybePop();
|
||||
return;
|
||||
}
|
||||
_currentIndex = newIndex >= 0
|
||||
? newIndex
|
||||
: _currentIndex.clamp(0, _photos.length - 1);
|
||||
});
|
||||
}
|
||||
|
||||
TransformationController _transformerFor(int index) {
|
||||
return _transformControllers.putIfAbsent(index, () {
|
||||
final c = TransformationController();
|
||||
c.addListener(() => _onTransformChanged(index, c));
|
||||
return c;
|
||||
});
|
||||
}
|
||||
|
||||
void _onTransformChanged(int index, TransformationController c) {
|
||||
final isZoomed = c.value.getMaxScaleOnAxis() > 1.01;
|
||||
if ((_zoomedByIndex[index] ?? false) != isZoomed) {
|
||||
setState(() => _zoomedByIndex[index] = isZoomed);
|
||||
}
|
||||
}
|
||||
|
||||
bool get _isCurrentZoomed => _zoomedByIndex[_currentIndex] ?? false;
|
||||
|
||||
void _goTo(int index) {
|
||||
if (index < 0 || index >= _photos.length) return;
|
||||
_pageController.animateToPage(
|
||||
index,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
|
||||
KeyEventResult _handleKey(FocusNode node, KeyEvent event) {
|
||||
if (event is! KeyDownEvent && event is! KeyRepeatEvent) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
if (_isCurrentZoomed) return KeyEventResult.ignored;
|
||||
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||
_goTo(_currentIndex - 1);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||
_goTo(_currentIndex + 1);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_photos.isEmpty) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(leading: appBarBackLeading(context)),
|
||||
body: const SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
final currentPhoto = _photos[_currentIndex];
|
||||
final canSwipe = !_isCurrentZoomed;
|
||||
final hasPrev = _currentIndex > 0;
|
||||
final hasNext = _currentIndex < _photos.length - 1;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(photo.caption ?? '')),
|
||||
body: InteractiveViewer(
|
||||
child: Center(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: imageUri.toString(),
|
||||
httpHeaders: headers,
|
||||
fit: BoxFit.contain,
|
||||
errorWidget: (_, _, _) =>
|
||||
const Icon(Icons.broken_image_outlined, size: 64),
|
||||
appBar: AppBar(
|
||||
leading: appBarBackLeading(context),
|
||||
title: Text(currentPhoto.caption ?? ''),
|
||||
),
|
||||
body: Focus(
|
||||
focusNode: _focusNode,
|
||||
autofocus: true,
|
||||
onKeyEvent: _handleKey,
|
||||
child: Stack(
|
||||
children: [
|
||||
PageView.builder(
|
||||
controller: _pageController,
|
||||
physics: canSwipe
|
||||
? const PageScrollPhysics()
|
||||
: const NeverScrollableScrollPhysics(),
|
||||
itemCount: _photos.length,
|
||||
onPageChanged: (i) => setState(() => _currentIndex = i),
|
||||
itemBuilder: (context, index) {
|
||||
final photo = _photos[index];
|
||||
final uri = PhotoService.instance.photoPreviewUri(
|
||||
widget.houseId,
|
||||
photo.id,
|
||||
size: 1024,
|
||||
);
|
||||
return InteractiveViewer(
|
||||
transformationController: _transformerFor(index),
|
||||
child: Center(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: uri.toString(),
|
||||
httpHeaders: widget.headers,
|
||||
fit: BoxFit.contain,
|
||||
errorWidget: (_, _, _) =>
|
||||
const Icon(Icons.broken_image_outlined, size: 64),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_isDesktop && canSwipe && hasPrev)
|
||||
_NavButton(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
icon: Icons.chevron_left,
|
||||
onPressed: () => _goTo(_currentIndex - 1),
|
||||
),
|
||||
if (_isDesktop && canSwipe && hasNext)
|
||||
_NavButton(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
icon: Icons.chevron_right,
|
||||
onPressed: () => _goTo(_currentIndex + 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NavButton extends StatelessWidget {
|
||||
final AlignmentGeometry alignment;
|
||||
final IconData icon;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const _NavButton({
|
||||
required this.alignment,
|
||||
required this.icon,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: alignment,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 16),
|
||||
child: Material(
|
||||
color: Colors.black.withAlpha(110),
|
||||
shape: const CircleBorder(),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: IconButton(
|
||||
icon: Icon(icon, color: Colors.white, size: 32),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pantry/i18n.dart';
|
||||
import 'package:pantry/services/background_notification_task.dart';
|
||||
@@ -6,6 +7,7 @@ import 'package:pantry/services/local_notifications_service.dart';
|
||||
import 'package:pantry/services/locale_service.dart';
|
||||
import 'package:pantry/services/prefs_service.dart';
|
||||
import 'package:pantry/services/theming_service.dart';
|
||||
import 'package:pantry/widgets/app_bar_back_leading.dart';
|
||||
|
||||
class SettingsView extends StatefulWidget {
|
||||
const SettingsView({super.key});
|
||||
@@ -15,22 +17,36 @@ class SettingsView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SettingsViewState extends State<SettingsView> {
|
||||
late bool _notificationsEnabled;
|
||||
late int _pollIntervalMinutes;
|
||||
late String? _selectedLocale;
|
||||
late String? _selectedTheme;
|
||||
|
||||
static const _pollOptions = [15, 30, 60, 120, 360];
|
||||
static const _categorySpacingOptions = ['disabled', 'space', 'divider'];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_notificationsEnabled = PrefsService.instance.notificationsEnabled;
|
||||
_pollIntervalMinutes = PrefsService.instance.pollIntervalMinutes;
|
||||
_selectedLocale = PrefsService.instance.locale;
|
||||
_selectedTheme = PrefsService.instance.themeMode;
|
||||
}
|
||||
|
||||
Future<void> _setTapRowToComplete(bool value) async {
|
||||
await context.read<PrefsService>().setChecklistTapRowToToggle(value);
|
||||
}
|
||||
|
||||
Future<void> _setCategorySpacing(String? value) async {
|
||||
if (value == null) return;
|
||||
final prefs = context.read<PrefsService>();
|
||||
if (value == prefs.checklistCategorySpacing) return;
|
||||
await prefs.setChecklistCategorySpacing(value);
|
||||
}
|
||||
|
||||
String _categorySpacingLabel(String value) => switch (value) {
|
||||
'space' => m.settings.categorySpacingNames.space,
|
||||
'divider' => m.settings.categorySpacingNames.divider,
|
||||
_ => m.settings.categorySpacingNames.disabled,
|
||||
};
|
||||
|
||||
// -- Language --
|
||||
|
||||
Future<void> _setLocale(String? value) async {
|
||||
@@ -78,8 +94,8 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
}
|
||||
}
|
||||
|
||||
await PrefsService.instance.setNotificationsEnabled(value);
|
||||
setState(() => _notificationsEnabled = value);
|
||||
if (!mounted) return;
|
||||
await context.read<PrefsService>().setNotificationsEnabled(value);
|
||||
|
||||
if (value) {
|
||||
await registerBackgroundNotificationPoll();
|
||||
@@ -90,10 +106,11 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
}
|
||||
|
||||
Future<void> _setPollInterval(int? minutes) async {
|
||||
if (minutes == null || minutes == _pollIntervalMinutes) return;
|
||||
await PrefsService.instance.setPollIntervalMinutes(minutes);
|
||||
setState(() => _pollIntervalMinutes = minutes);
|
||||
if (_notificationsEnabled) {
|
||||
if (minutes == null) return;
|
||||
final prefs = context.read<PrefsService>();
|
||||
if (minutes == prefs.pollIntervalMinutes) return;
|
||||
await prefs.setPollIntervalMinutes(minutes);
|
||||
if (prefs.notificationsEnabled) {
|
||||
await rescheduleBackgroundNotificationPoll();
|
||||
}
|
||||
}
|
||||
@@ -109,8 +126,17 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final prefs = context.watch<PrefsService>();
|
||||
final notificationsEnabled = prefs.notificationsEnabled;
|
||||
final pollIntervalMinutes = prefs.pollIntervalMinutes;
|
||||
final tapRowToComplete = prefs.checklistTapRowToToggle;
|
||||
final categorySpacing = prefs.checklistCategorySpacing;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(m.settings.title)),
|
||||
appBar: AppBar(
|
||||
leading: appBarBackLeading(context),
|
||||
title: Text(m.settings.title),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
// -- General --
|
||||
@@ -173,21 +199,45 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
),
|
||||
),
|
||||
|
||||
// -- Interface --
|
||||
_SectionHeader(m.settings.interfaceSection),
|
||||
SwitchListTile(
|
||||
title: Text(m.settings.tapRowToComplete),
|
||||
subtitle: Text(m.settings.tapRowToCompleteBody),
|
||||
value: tapRowToComplete,
|
||||
onChanged: _setTapRowToComplete,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(m.settings.categorySpacing),
|
||||
subtitle: Text(m.settings.categorySpacingBody),
|
||||
trailing: DropdownButton<String>(
|
||||
value: categorySpacing,
|
||||
onChanged: _setCategorySpacing,
|
||||
items: [
|
||||
for (final option in _categorySpacingOptions)
|
||||
DropdownMenuItem(
|
||||
value: option,
|
||||
child: Text(_categorySpacingLabel(option)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// -- Notifications --
|
||||
_SectionHeader(m.settings.notificationsSection),
|
||||
SwitchListTile(
|
||||
title: Text(m.settings.enableNotifications),
|
||||
subtitle: Text(m.settings.enableNotificationsBody),
|
||||
value: _notificationsEnabled,
|
||||
value: notificationsEnabled,
|
||||
onChanged: _toggleNotifications,
|
||||
),
|
||||
ListTile(
|
||||
enabled: _notificationsEnabled,
|
||||
enabled: notificationsEnabled,
|
||||
title: Text(m.settings.pollInterval),
|
||||
subtitle: Text(_pollIntervalLabel(_pollIntervalMinutes)),
|
||||
subtitle: Text(_pollIntervalLabel(pollIntervalMinutes)),
|
||||
trailing: DropdownButton<int>(
|
||||
value: _pollIntervalMinutes,
|
||||
onChanged: _notificationsEnabled ? _setPollInterval : null,
|
||||
value: pollIntervalMinutes,
|
||||
onChanged: notificationsEnabled ? _setPollInterval : null,
|
||||
items: [
|
||||
for (final minutes in _pollOptions)
|
||||
DropdownMenuItem(
|
||||
|
||||
307
lib/views/share/share_router_view.dart
Normal file
307
lib/views/share/share_router_view.dart
Normal file
@@ -0,0 +1,307 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
|
||||
import 'package:pantry/i18n.dart';
|
||||
import 'package:pantry/models/house.dart';
|
||||
import 'package:pantry/models/photo.dart';
|
||||
import 'package:pantry/services/deep_link_service.dart';
|
||||
import 'package:pantry/services/house_service.dart';
|
||||
import 'package:pantry/services/pending_note_share_service.dart';
|
||||
import 'package:pantry/services/pending_photo_share_service.dart';
|
||||
import 'package:pantry/services/photo_service.dart';
|
||||
import 'package:pantry/services/prefs_service.dart';
|
||||
import 'package:pantry/widgets/app_bar_back_leading.dart';
|
||||
|
||||
/// Entry screen for an incoming OS share intent. Classifies the payload,
|
||||
/// optionally asks the user to pick a house, and then routes:
|
||||
/// * photo flow → folder picker → enqueue uploads → switch to photo tab
|
||||
/// * text/URL flow → push the notes-form view prefilled with the content
|
||||
///
|
||||
/// On completion this view pops itself. On cancel/error it also pops.
|
||||
class ShareRouterView extends StatefulWidget {
|
||||
final List<SharedMediaFile> files;
|
||||
|
||||
const ShareRouterView({super.key, required this.files});
|
||||
|
||||
@override
|
||||
State<ShareRouterView> createState() => _ShareRouterViewState();
|
||||
}
|
||||
|
||||
class _ShareRouterViewState extends State<ShareRouterView> {
|
||||
bool _busy = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _run());
|
||||
}
|
||||
|
||||
Future<void> _run() async {
|
||||
try {
|
||||
final houses = await HouseService.instance.getHouses();
|
||||
if (!mounted) return;
|
||||
if (houses.isEmpty) {
|
||||
setState(() {
|
||||
_busy = false;
|
||||
_error = m.share.noHouses;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final house = houses.length == 1
|
||||
? houses.first
|
||||
: await _pickHouse(houses);
|
||||
if (!mounted) return;
|
||||
if (house == null) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
final hasImages = widget.files.any(
|
||||
(f) => f.type == SharedMediaType.image,
|
||||
);
|
||||
if (hasImages) {
|
||||
await _runPhotoFlow(house);
|
||||
} else {
|
||||
await _runNoteFlow(house);
|
||||
}
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_busy = false;
|
||||
_error = m.share.failedToOpenShare;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<House?> _pickHouse(List<House> houses) async {
|
||||
return showDialog<House>(
|
||||
context: context,
|
||||
builder: (ctx) => SimpleDialog(
|
||||
title: Text(m.share.chooseHouse),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 8),
|
||||
children: houses
|
||||
.map(
|
||||
(h) => ListTile(
|
||||
leading: const Icon(Icons.home_outlined),
|
||||
title: Text(h.name),
|
||||
onTap: () => Navigator.pop(ctx, h),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runPhotoFlow(House house) async {
|
||||
// Fetch folders for the selected house (used in the picker).
|
||||
List<PhotoFolder> folders;
|
||||
try {
|
||||
folders = await PhotoService.instance.getFolders(house.id);
|
||||
} catch (_) {
|
||||
folders = [];
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
||||
final dest = await showModalBottomSheet<_PhotoDestination>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (ctx) =>
|
||||
_PhotoDestinationPicker(folders: folders, houseId: house.id),
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (dest == null) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
int? folderId = dest.folderId;
|
||||
if (dest.newFolderName != null) {
|
||||
try {
|
||||
final folder = await PhotoService.instance.createFolder(
|
||||
house.id,
|
||||
name: dest.newFolderName!,
|
||||
);
|
||||
folderId = folder.id;
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(m.share.failedToCreateFolder)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final paths = widget.files
|
||||
.where((f) => f.type == SharedMediaType.image)
|
||||
.map((f) => f.path)
|
||||
.toList();
|
||||
|
||||
await PrefsService.instance.setLastHouseId(house.id);
|
||||
if (!mounted) return;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
DeepLinkService.instance.push(DeepLink(tabIndex: 1, houseId: house.id));
|
||||
PendingPhotoShareService.instance.enqueue(
|
||||
PendingPhotoShare(houseId: house.id, folderId: folderId, paths: paths),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runNoteFlow(House house) async {
|
||||
final text = widget.files
|
||||
.where(
|
||||
(f) =>
|
||||
f.type == SharedMediaType.text || f.type == SharedMediaType.url,
|
||||
)
|
||||
.map((f) => f.path)
|
||||
.where((s) => s.isNotEmpty)
|
||||
.join('\n\n');
|
||||
|
||||
await PrefsService.instance.setLastHouseId(house.id);
|
||||
if (!mounted) return;
|
||||
|
||||
// Pop the share router BEFORE notifying listeners. If the notes wall
|
||||
// is already mounted, its listener pushes the form synchronously, and
|
||||
// we don't want our own pop to remove that form by accident.
|
||||
Navigator.of(context).pop();
|
||||
DeepLinkService.instance.push(DeepLink(tabIndex: 2, houseId: house.id));
|
||||
PendingNoteShareService.instance.push(
|
||||
PendingNoteShare(houseId: house.id, content: text),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: appBarBackLeading(context),
|
||||
title: Text(m.share.title),
|
||||
),
|
||||
body: Center(
|
||||
child: _busy
|
||||
? const CircularProgressIndicator()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(_error ?? '', textAlign: TextAlign.center),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(m.common.cancel),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PhotoDestination {
|
||||
/// null = root, or a real folder id.
|
||||
final int? folderId;
|
||||
|
||||
/// non-null when the user picked "new folder" with this name.
|
||||
final String? newFolderName;
|
||||
|
||||
const _PhotoDestination.root() : folderId = null, newFolderName = null;
|
||||
const _PhotoDestination.folder(this.folderId) : newFolderName = null;
|
||||
const _PhotoDestination.newFolder(this.newFolderName) : folderId = null;
|
||||
}
|
||||
|
||||
class _PhotoDestinationPicker extends StatelessWidget {
|
||||
final List<PhotoFolder> folders;
|
||||
final int houseId;
|
||||
|
||||
const _PhotoDestinationPicker({required this.folders, required this.houseId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.fromSTEB(16, 8, 16, 8),
|
||||
child: Text(
|
||||
m.share.choosePhotoDestination,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library_outlined),
|
||||
title: Text(m.share.photoBoardRoot),
|
||||
onTap: () =>
|
||||
Navigator.pop(context, const _PhotoDestination.root()),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
for (final folder in folders)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.folder_outlined),
|
||||
title: Text(folder.name),
|
||||
onTap: () => Navigator.pop(
|
||||
context,
|
||||
_PhotoDestination.folder(folder.id),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.create_new_folder_outlined),
|
||||
title: Text(m.share.newFolder),
|
||||
onTap: () => _promptNewFolder(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _promptNewFolder(BuildContext context) async {
|
||||
final textController = TextEditingController();
|
||||
final result = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(m.share.newFolder),
|
||||
content: TextField(
|
||||
controller: textController,
|
||||
autofocus: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
labelText: m.share.newFolderName,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text(m.common.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
final name = textController.text.trim();
|
||||
if (name.isNotEmpty) Navigator.pop(ctx, name);
|
||||
},
|
||||
child: Text(m.common.save),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (result != null && context.mounted) {
|
||||
Navigator.pop(context, _PhotoDestination.newFolder(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
14
lib/widgets/app_bar_back_leading.dart
Normal file
14
lib/widgets/app_bar_back_leading.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget? appBarBackLeading(BuildContext context) {
|
||||
if (kIsWeb || !Platform.isMacOS) return null;
|
||||
final route = ModalRoute.of(context);
|
||||
if (route?.canPop != true) return null;
|
||||
return const Align(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
child: BackButton(),
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user