Compare commits

...

657 commits

Author SHA1 Message Date
9037f8e610 Aggiorna README.md
Some checks failed
Quality check / Flutter analysis (push) Has been cancelled
Quality check / CodeQL analysis (java-kotlin) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
2025-08-14 17:41:59 +08:00
05bb77a793 Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:51:39 +08:00
2f0f3da2fa Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:44:51 +08:00
31f85c3e01 Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:41:04 +08:00
84a822022a Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:07:15 +08:00
94c83914a4 Aggiorna README.md
Some checks failed
Quality check / Flutter analysis (push) Has been cancelled
Quality check / CodeQL analysis (java-kotlin) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
2025-06-11 16:40:56 +08:00
a461e2c55f Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-06-11 16:40:17 +08:00
99c9f85eaf Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-06-11 16:39:24 +08:00
848ad5220e Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-06-11 16:38:06 +08:00
Thibault Deckers
7577466978 rescale large thumbnails decoded as is;
check thumbnail bitmap size before getting raw bytes
2025-06-10 20:45:59 +02:00
Thibault Deckers
dfcaf4d35a upgrades 2025-06-09 18:47:11 +02:00
Thibault Deckers
171394056f #1608 query bar unfocus when navigating away 2025-06-07 20:12:54 +02:00
dependabot[bot]
60211545e1
Bump github/codeql-action from 3.28.18 to 3.28.19 (#1604)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.28.19.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ff0a06e83c...fca7ace96b)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-08 01:03:54 +09:00
Thibault Deckers
edbf9744f5 #1612 info: show matching dynamic albums 2025-06-07 18:03:32 +02:00
Thibault Deckers
d272c82454 version bump 2025-06-02 23:52:21 +02:00
Thibault Deckers
20b4f10b62 l10n 2025-06-02 23:48:57 +02:00
Weblate (bot)
3db0478be2
l10n by weblate (#1587)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jamil Farajov <jamilfarajov@gmail.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Miquel Martí <miquelmarti111@gmail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Murcielago <weblate.j9bmx@slmail.me>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thit Lwin <thitlwincoder@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: WMatheist <wmatheist@protonmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: vm <varga.m007@gmail.com>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-06-03 06:44:03 +09:00
Thibault Deckers
c9fd71056f removed obsolete plugin 2025-06-02 22:38:14 +02:00
Thibault Deckers
ca2d2c2026 format 2025-06-02 22:25:42 +02:00
Thibault Deckers
2e775b3906 prevent crash from security exception in media content listening 2025-06-02 22:19:51 +02:00
Thibault Deckers
ea3cb3c063 fixed crash when parsing some large media with trailing thumbnail 2025-06-02 19:45:11 +02:00
dependabot[bot]
340ed6a6d9
Bump ossf/scorecard-action from 2.4.1 to 2.4.2 (#1597)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.1 to 2.4.2.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](f49aabe0b5...05b42c6244)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 02:26:28 +09:00
Thibault Deckers
5e0f0b59d8 prevent display orientation flip when device rotation is locked 2025-06-02 19:26:13 +02:00
Thibault Deckers
a0163001bd CI: ensure l10n files are generated 2025-06-02 00:03:47 +02:00
Thibault Deckers
1222a711e0 downgraded Flutter to stable v3.27.4 2025-06-01 20:57:46 +02:00
Thibault Deckers
8c3d0f1b83 fixed file extension loss on move via tree doc 2025-05-31 20:20:07 +02:00
Thibault Deckers
43cb2cd101 android: dependency upgrades 2025-05-31 18:28:50 +02:00
Thibault Deckers
81a2b84c9f app bar layout padding review 2025-05-30 18:03:29 +02:00
Thibault Deckers
bae6d2b7c4 #268 fixed loading group custom covers 2025-05-27 22:00:02 +02:00
Thibault Deckers
9a377ed7bc #268 albums: fixed hiding groups/dynamics when they are explicitly hidden 2025-05-27 19:50:28 +02:00
Thibault Deckers
1119fa1407 #268 groups: listening to source/dynamics to remove groups with obsolete content 2025-05-27 00:28:41 +02:00
Thibault Deckers
7b0f72d6ee chip cover detail layout fixes 2025-05-26 23:40:37 +02:00
Thibault Deckers
6f9a581d99 minor 2025-05-26 23:39:43 +02:00
Thibault Deckers
b6faf36671 l10n 2025-05-26 19:20:32 +02:00
dependabot[bot]
17f3ec437c
Bump github/codeql-action from 3.28.17 to 3.28.18 (#1561)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.17 to 3.28.18.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](60168efe1c...ff0a06e83c)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 02:18:56 +09:00
Weblate (bot)
3ec5b96bc9
l10n by weblate (#1559)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Miquel Martí <miquelmarti111@gmail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Murcielago <weblate.j9bmx@slmail.me>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thit Lwin <thitlwincoder@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: WMatheist <wmatheist@protonmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: vm <varga.m007@gmail.com>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-27 02:18:12 +09:00
Thibault Deckers
540fbbc2b4 #268 clearer action name 2025-05-26 18:40:24 +02:00
Thibault Deckers
f355efefc1 fixed dependency 2025-05-23 19:40:34 +02:00
Thibault Deckers
3bcaab9a4b minor 2025-05-23 18:59:49 +02:00
Thibault Deckers
ef091b9932 #1576 fixed title sort wording 2025-05-20 18:54:26 +02:00
Thibault Deckers
33667e7e6e #1575 fixed home page when launching app as media picker 2025-05-19 22:53:22 +02:00
Thibault Deckers
2a3cce422b replaced flutter_markdown by flutter_markdown_plus 2025-05-14 23:01:11 +02:00
Thibault Deckers
e0af21f098 version bump 2025-05-14 22:18:09 +02:00
Weblate (bot)
8636e4b73e
l10n by weblate (#1556)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Miquel Martí <miquelmarti111@gmail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Murcielago <weblate.j9bmx@slmail.me>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-15 05:12:01 +09:00
Thibault Deckers
0ad4b2f16f albums: fixed positioning on group change 2025-05-14 22:10:51 +02:00
Thibault Deckers
df63f06897 albums: hide grouped albums containing hidden items only 2025-05-14 22:01:45 +02:00
Thibault Deckers
09df269ee0 albums: show groups to move/copy/export items 2025-05-14 21:52:21 +02:00
dependabot[bot]
39d7587ac9
Bump actions/dependency-review-action from 4.7.0 to 4.7.1 (#1557)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.7.0 to 4.7.1.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](38ecb5b593...da24556b54)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-version: 4.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-15 04:38:18 +09:00
Thibault Deckers
0f6a8230d8 version bump 2025-05-12 23:09:29 +02:00
Thibault Deckers
a6c1fd52a6 #1554 Collection: sort by storage path 2025-05-12 23:04:14 +02:00
Weblate (bot)
eaa4fe3317
l10n by weblate (#1555)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Miquel Martí <miquelmarti111@gmail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Murcielago <weblate.j9bmx@slmail.me>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-13 05:43:39 +09:00
Thibault Deckers
5358a38bd8 groups: scroll to top on group change 2025-05-12 21:22:29 +02:00
Thibault Deckers
244c1a293d groups: fixed default view selection when in group 2025-05-12 20:12:20 +02:00
Thibault Deckers
2ef03f1592 search: fixed query suggestions 2025-05-12 19:39:30 +02:00
Thibault Deckers
f2ef5c6f32 minor 2025-05-11 23:47:10 +02:00
Weblate (bot)
b582dbb3b2
l10n by weblate (#1551)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Miquel Martí <miquelmarti111@gmail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Murcielago <weblate.j9bmx@slmail.me>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-12 06:45:40 +09:00
dependabot[bot]
b864a4dae3
Bump actions/dependency-review-action from 4.6.0 to 4.7.0 (#1553)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.6.0 to 4.7.0.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](ce3cf9537a...38ecb5b593)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-version: 4.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 06:18:47 +09:00
Thibault Deckers
2b01fb41e2 #268 groups: import 2025-05-11 23:17:34 +02:00
Thibault Deckers
244217417b #268 groups: serialization 2025-05-11 23:10:31 +02:00
Thibault Deckers
651b5926dc #268 cover/pins/bookmarks sub to dynamics/groups; dynamics sub to groups;
container filter mixin;
debug: cover/dynamics dump;
2025-05-11 21:57:38 +02:00
Thibault Deckers
27879a900d group fab; prevent empty filter grid scroll 2025-05-06 22:45:03 +02:00
Thibault Deckers
4b87717cd2 #1549 week day filters 2025-05-05 23:12:23 +02:00
Thibault Deckers
91cfe01af3 l10n 2025-05-05 22:33:54 +02:00
Weblate (bot)
cb6ccab6ca
l10n by weblate (#1550)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Miquel Martí <miquelmarti111@gmail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-06 05:32:11 +09:00
Thibault Deckers
f04c55e901 ignore l10n gen files 2025-05-05 21:35:45 +02:00
Thibault Deckers
8e0c69cd66 removed l10n gen files from git 2025-05-05 21:28:20 +02:00
Thibault Deckers
c31b64535d #268 album grouping (WIP) 2025-05-05 19:14:40 +02:00
Thibault Deckers
89dee8d508 l10n 2025-05-05 19:10:20 +02:00
Weblate (bot)
93c2c7d34d
l10n by weblate (#1542)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Miquel Martí <miquelmarti111@gmail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-05-06 02:07:30 +09:00
dependabot[bot]
573b7c4593
Bump github/codeql-action from 3.28.16 to 3.28.17 (#1547)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.28.17.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](28deaeda66...60168efe1c)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-06 01:54:06 +09:00
Thibault Deckers
f9f21fbe76 l10n 2025-04-28 19:38:56 +02:00
dependabot[bot]
9cfb4436fa
Bump actions/attest-build-provenance from 2.2.3 to 2.3.0 (#1541)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.2.3 to 2.3.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](c074443f1a...db473fddc0)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: 2.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-29 02:32:26 +09:00
Weblate (bot)
925998b709
l10n by weblate (#1529)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ur/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Feliks-WR <aq.1428@tuta.io>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: slabs37 <p84haghi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-04-29 02:27:28 +09:00
Thibault Deckers
a8bb2eb69f #1539 locked: review, prevent rename / metadata export 2025-04-27 23:42:40 +02:00
Thibault Deckers
bb6c2c341b revert to Skia rendering engine 2025-04-24 21:41:19 +02:00
dependabot[bot]
cbaaf2fd87
Bump actions/download-artifact from 4.2.1 to 4.3.0 (#1537)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.2.1 to 4.3.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](95815c38cf...d3f86a106a)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-25 02:29:44 +09:00
dependabot[bot]
e1fad28411
Bump github/codeql-action from 3.28.15 to 3.28.16 (#1536)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.15 to 3.28.16.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](45775bd823...28deaeda66)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-25 02:29:34 +09:00
dependabot[bot]
bb26b18017
Bump step-security/harden-runner from 2.11.1 to 2.12.0 (#1534)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.11.1 to 2.12.0.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](c6295a65d1...0634a2670c)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-25 02:29:23 +09:00
Thibault Deckers
3a6ad33ea1 minor 2025-04-20 19:24:34 +02:00
Thibault Deckers
75421faf46 minor fix 2025-04-20 19:19:04 +02:00
Thibault Deckers
4df4738dd3 #1507 mime type normalization 2025-04-20 18:33:28 +02:00
Thibault Deckers
e8eae7e9db version bump 2025-04-16 23:37:06 +02:00
Thibault Deckers
f47cd57692 l10n 2025-04-16 23:34:30 +02:00
Weblate (bot)
bb332d5adf
l10n by weblate (#1523)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-04-17 06:30:17 +09:00
Thibault Deckers
7f54befb72 #1527 fixed concurrent region decoder pool access 2025-04-16 21:08:09 +02:00
Thibault Deckers
af4ca96da8 #1513 albums: sort by path 2025-04-15 21:55:55 +02:00
Thibault Deckers
90d0256bf7 upgraded Flutter to stable v3.29.3 2025-04-14 20:29:56 +02:00
Thibault Deckers
353cde0ee8 #1493 analysis service: ensure init sequence, safer notification update 2025-04-13 22:14:05 +02:00
Thibault Deckers
63130de577 #1507 search: add mime type filters 2025-04-13 19:31:16 +02:00
Thibault Deckers
ad5a9c848d #1507 syntax for extension filtering in title filter 2025-04-13 19:02:29 +02:00
Thibault Deckers
be75d5a284 static analysis fixes 2025-04-13 19:01:31 +02:00
Thibault Deckers
0142ed7f4f l10n 2025-04-13 18:50:34 +02:00
Weblate (bot)
6c2db18af2
l10n by weblate (#1517)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ne/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/he/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Over Barrow <rawixo6748@insfou.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-04-14 01:45:56 +09:00
dependabot[bot]
93e2b1d310
Bump github/codeql-action from 3.28.13 to 3.28.15 (#1519)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.13 to 3.28.15.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1b549b9259...45775bd823)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 01:43:05 +09:00
dependabot[bot]
9baa4a0441
Bump actions/setup-java from 4.7.0 to 4.7.1 (#1520)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.7.0 to 4.7.1.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](3a4f6e1af5...c5195efecf)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 4.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 01:42:47 +09:00
Thibault Deckers
f123faeee8 use material symbols over material icons 2025-04-13 18:42:17 +02:00
Thibault Deckers
e3ece7425f version bump 2025-04-06 23:13:12 +02:00
Thibault Deckers
ff4c49718e removed DB upgrade temporary code for v1.12.4 users 2025-04-06 23:10:16 +02:00
Thibault Deckers
5598b0a69d l10n: kn 2025-04-06 23:02:40 +02:00
Thibault Deckers
dac91a2d1d l10n: kn 2025-04-06 21:44:41 +02:00
Weblate (bot)
759f666085
l10n by weblate (#1488)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/or/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/kn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Prasannakumar T Bhat <pbhat99@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-04-07 02:17:09 +09:00
Thibault Deckers
c89441bb78 enable Impeller rendering engine 2025-04-06 17:56:52 +02:00
Thibault Deckers
969187444b viewer: improved video controller disposing 2025-04-03 22:59:48 +02:00
Thibault Deckers
1fd3d77bf9 fetch encoded bytes for video cover 2025-04-03 22:58:23 +02:00
Thibault Deckers
32202cc603 viewer: reduced tile area overflow 2025-04-02 23:31:16 +02:00
Thibault Deckers
bce7009ab0 upgrades 2025-04-02 23:28:51 +02:00
dependabot[bot]
b00b17a473
Bump step-security/harden-runner from 2.11.0 to 2.11.1 (#1508)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.11.0 to 2.11.1.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](4d991eb9b9...c6295a65d1)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 06:14:53 +09:00
dependabot[bot]
8be5b8d9c5
Bump actions/dependency-review-action from 4.5.0 to 4.6.0 (#1506)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](3b139cfc5f...ce3cf9537a)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 06:14:38 +09:00
Thibault Deckers
503757da0e svg decoder pool 2025-04-02 23:14:09 +02:00
Thibault Deckers
85a1b33e83 debug: glide sizing, memory cache clear 2025-04-02 22:30:40 +02:00
Thibault Deckers
022ad0334e debug: custom flutter image cache size 2025-04-01 23:39:11 +02:00
Thibault Deckers
e09b3e4440 region decoder pool 2025-04-01 23:38:41 +02:00
Thibault Deckers
409d80df4e terms txt version for play store 2025-03-27 23:18:00 +01:00
Thibault Deckers
a6ae2fd4cb terms html version for play store 2025-03-27 23:10:02 +01:00
Thibault Deckers
59aa75e46c upgrades 2025-03-26 18:17:49 +01:00
Thibault Deckers
1df2154f85 version bump 2025-03-25 23:37:43 +01:00
Thibault Deckers
326e74c04c l10n 2025-03-25 23:33:01 +01:00
Weblate (bot)
e1aee40f0c
l10n by weblate (#1457)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: hanyang cheng <cinxiafortis@tutanota.de>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-03-26 07:25:20 +09:00
Thibault Deckers
8193c48234 #1485 fixed magnifier edge detection 2025-03-25 23:23:08 +01:00
Thibault Deckers
9c641e0f49 debug: memory overlay 2025-03-25 22:58:51 +01:00
dependabot[bot]
988d9b2c8d
Bump actions/download-artifact from 4.1.9 to 4.2.1 (#1482)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.9 to 4.2.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](cc20338598...95815c38cf)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-26 04:11:15 +09:00
dependabot[bot]
305dcb4528
Bump actions/upload-artifact from 4.6.1 to 4.6.2 (#1484)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](4cec3d8aa0...ea165f8d65)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-26 04:10:49 +09:00
dependabot[bot]
a13da39af2
Bump github/codeql-action from 3.28.11 to 3.28.13 (#1487)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.11 to 3.28.13.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](6bb031afdd...1b549b9259)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-26 04:10:30 +09:00
Thibault Deckers
9d7e6bb9ff #1476 launch error handling reload;
DB: table existence check via sqlite_master
2025-03-25 19:49:33 +01:00
Thibault Deckers
509c402227 debug: leak tracking for GCed late and not GCed 2025-03-18 23:41:39 +01:00
Thibault Deckers
77443202aa vs code launch config 2025-03-18 21:56:17 +01:00
Thibault Deckers
508e5ae739 minor 2025-03-18 20:23:39 +01:00
Thibault Deckers
f38178ab7e deprecation prep 2025-03-18 20:18:40 +01:00
Thibault Deckers
a670b683e5 minor fix 2025-03-17 21:59:14 +01:00
Thibault Deckers
9a4c567c2b version bump 2025-03-16 19:44:08 +01:00
Thibault Deckers
672b6fd2dc minor fix 2025-03-16 18:29:09 +01:00
Thibault Deckers
b8e9786f4d minor fixes 2025-03-16 18:05:24 +01:00
Thibault Deckers
cb067aa1ac #1476 launch error handling;
DB: table existence check in v13+ upgrades
2025-03-16 17:17:45 +01:00
Thibault Deckers
cf74e75d58 mediakit upgrade 2025-03-14 18:53:36 +01:00
Thibault Deckers
9e33db5b4d upgraded Flutter to stable v3.29.2 2025-03-14 18:43:28 +01:00
Thibault Deckers
8e6a995c4e Update .gitignore 2025-03-11 23:59:53 +01:00
Thibault Deckers
62d666fb34 minor 2025-03-11 23:30:41 +01:00
Thibault Deckers
403ccac5c2 version bump 2025-03-11 00:30:57 +01:00
Thibault Deckers
dcd42b7048 #1471 check file length after metadata editing via PixyMeta 2025-03-11 00:24:03 +01:00
Thibault Deckers
f646639055 #1471 allow rescan of trashed items 2025-03-10 23:09:32 +01:00
Thibault Deckers
a32c0cf0f0 #1471 DB sanitizing to mitigate v1.12.4 upgrade issue 2025-03-10 23:08:23 +01:00
Thibault Deckers
3f6ef0cdaf version bump 2025-03-07 20:11:18 +01:00
Thibault Deckers
32bda0d9a7 #1463 fixed DB migration from v13; upgraded Flutter to stable v3.29.1 2025-03-07 20:07:34 +01:00
dependabot[bot]
4e10d882c9
Bump github/codeql-action from 3.28.10 to 3.28.11 (#1465)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.10 to 3.28.11.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b56ba49b26...6bb031afdd)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-08 02:03:43 +09:00
dependabot[bot]
9eadf61109
Bump actions/attest-build-provenance from 2.2.2 to 2.2.3 (#1460)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.2.2 to 2.2.3.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](bd77c07785...c074443f1a)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-08 02:03:08 +09:00
Thibault Deckers
7a84a80736 upgrades 2025-03-05 23:37:17 +01:00
Thibault Deckers
efc6ed2a01 version bump 2025-03-05 23:29:35 +01:00
Thibault Deckers
967e192b4c minor 2025-03-05 23:24:35 +01:00
Thibault Deckers
a3ce840d02 l10n 2025-03-05 23:21:41 +01:00
Weblate (bot)
ee748d8920
l10n by weblate (#1454)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-03-06 07:20:15 +09:00
Thibault Deckers
15fe378107 fallback for decoding large panorama 2025-03-05 23:17:22 +01:00
Thibault Deckers
2325501f3f decoding fixes 2025-03-04 00:58:00 +01:00
Thibault Deckers
f02108fbcd decoding: RGBA_F16 to ARGB_8888 conversion 2025-03-03 20:54:05 +01:00
Thibault Deckers
b224709c5d full decoding: use raw image descriptor in Flutter on decoded bytes from Android 2025-03-02 23:29:00 +01:00
Thibault Deckers
152b942f57 exif thumbnail decoding: use raw image descriptor in Flutter on decoded bytes from Android 2025-03-02 19:47:32 +01:00
Thibault Deckers
c1a99d9be5 app icon decoding: use raw image descriptor in Flutter on decoded bytes from Android 2025-03-02 18:48:48 +01:00
Thibault Deckers
d4791df333 thumbnail decoding: use raw image descriptor in Flutter on decoded bytes from Android 2025-03-02 18:05:44 +01:00
Thibault Deckers
1f95506abe color mode setting for wide gamut 2025-03-02 17:47:39 +01:00
Thibault Deckers
f850178afd region decoding: use raw image descriptor in Flutter on decoded bytes from Android 2025-03-02 13:06:58 +01:00
Thibault Deckers
5805bb2b5b fastlane: screenshot update 2025-03-01 11:29:37 +01:00
Thibault Deckers
731e82028c minor 2025-03-01 01:51:46 +01:00
Thibault Deckers
fe53d488b5 l10n 2025-03-01 00:55:56 +01:00
Weblate (bot)
eed280e840
l10n by weblate (#1452)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-03-01 08:49:20 +09:00
dependabot[bot]
f0580d8724
Bump actions/attest-build-provenance from 2.2.0 to 2.2.2 (#1453)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.2.0 to 2.2.2.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](520d128f16...bd77c07785)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 08:47:35 +09:00
Thibault Deckers
83f24374e5 l10n: gl 2025-02-27 20:31:20 +01:00
Thibault Deckers
6c055dd24c driver: screenshot fixes 2025-02-27 01:16:00 +01:00
Thibault Deckers
bf99f751bb l10n: gl 2025-02-26 20:14:38 +01:00
Weblate (bot)
8577eba448
l10n by weblate (#1450)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-02-27 04:08:08 +09:00
dependabot[bot]
12672984d7
Bump actions/download-artifact from 4.1.8 to 4.1.9 (#1451)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.8 to 4.1.9.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](fa0a91b85d...cc20338598)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-27 04:07:00 +09:00
Thibault Deckers
1d2aedf4c3 upgrades 2025-02-25 20:49:02 +01:00
Thibault Deckers
bb401e3410 l10n 2025-02-25 20:42:21 +01:00
Weblate (bot)
699c56e2f5
l10n by weblate (#1449)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-02-26 04:40:49 +09:00
Weblate (bot)
64cc59eae9
l10n by weblate (#1431)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: pitroig <ona@riseup.net>
Co-authored-by: rcasl <rcasl@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-02-26 04:21:30 +09:00
Thibault Deckers
76a9cf3cb3 #1448 display home tile in side drawer when customized 2025-02-25 19:59:28 +01:00
dependabot[bot]
5c94d2eece
Bump ncipollo/release-action from 1.15.0 to 1.16.0 (#1446)
Bumps [ncipollo/release-action](https://github.com/ncipollo/release-action) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/ncipollo/release-action/releases)
- [Commits](cdcc88a9ac...440c8c1cb0)

---
updated-dependencies:
- dependency-name: ncipollo/release-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 03:50:31 +09:00
dependabot[bot]
6c7dc7a0e3
Bump ossf/scorecard-action from 2.4.0 to 2.4.1 (#1445)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](62b2cac7ed...f49aabe0b5)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 03:50:19 +09:00
dependabot[bot]
b52d2242a4
Bump actions/upload-artifact from 4.6.0 to 4.6.1 (#1444)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65c4c4a1dd...4cec3d8aa0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 03:50:09 +09:00
Thibault Deckers
f02363592f #1441 fallback decoding of images packed in RGBA_1010102 config 2025-02-24 23:37:41 +01:00
dependabot[bot]
a3f6cd7a32
Bump github/codeql-action from 3.28.9 to 3.28.10 (#1440)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.9 to 3.28.10.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](9e8d0789d4...b56ba49b26)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 06:07:07 +09:00
dependabot[bot]
a608e122b1
Bump step-security/harden-runner from 2.10.4 to 2.11.0 (#1437)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.4 to 2.11.0.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](cb605e52c2...4d991eb9b9)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 06:06:55 +09:00
Thibault Deckers
3424631f5e editor: wip 2025-02-23 22:06:23 +01:00
Thibault Deckers
5c297c1daf editor: dynamic padding 2025-02-18 23:50:08 +01:00
Thibault Deckers
8fe267d345 downgrade dart to 3.6.0 because of incoherent dartfmt from 3.7.0 2025-02-18 23:44:41 +01:00
Thibault Deckers
f4108c244b #1436 search more broadly in Samsung SEFD box for motion photo video 2025-02-17 19:32:27 +01:00
Thibault Deckers
5769593799 minor 2025-02-16 22:40:40 +01:00
Thibault Deckers
ec9cb234a8 #1434 print: do not rotate image to fit page 2025-02-16 22:24:07 +01:00
Thibault Deckers
5f26cfbbf3 #1427 increased precision of file modified date to milliseconds 2025-02-16 20:32:34 +01:00
Thibault Deckers
9280b4a6a7 #1433 stack RAW with developed HEIC 2025-02-15 16:41:31 +01:00
Thibault Deckers
728b8018c4 #1428 switch parser when encountering !ATTLIST in SVG 2025-02-15 16:16:20 +01:00
Thibault Deckers
98537339bd backdrop filter grouping 2025-02-15 00:01:40 +01:00
Thibault Deckers
ae9e2977b4 shaders for flutter v3.29.0, disabled impeller 2025-02-15 00:01:03 +01:00
Thibault Deckers
61de0ffc4c flutter sdk version, removed android ndk version 2025-02-14 21:43:43 +01:00
Thibault Deckers
421e5d1522 gitignore updates, plugin ref fix 2025-02-14 20:13:34 +01:00
Thibault Deckers
5a505344ef upgraded Flutter to stable v3.29.0 2025-02-14 19:37:18 +01:00
Thibault Deckers
7b25d37616 l10n 2025-02-14 00:38:21 +01:00
dependabot[bot]
8641de4d5b
Bump github/codeql-action from 3.28.8 to 3.28.9 (#1422)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.8 to 3.28.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](dd746615b3...9e8d0789d4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-14 08:37:28 +09:00
Weblate (bot)
af0b79b07d
l10n by weblate (#1419)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <AJ07@users.noreply.hosted.weblate.org>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-02-14 08:37:13 +09:00
Thibault Deckers
983f50a814 minor 2025-02-14 00:01:46 +01:00
Thibault Deckers
79840b098f #1011 updated androidsvg to latest master 2025-02-13 22:03:55 +01:00
Thibault Deckers
95e0272afb editor: keep area on pan to edge 2025-02-12 19:25:44 +01:00
Thibault Deckers
b89db3bc9b editor: improved scale on resize 2025-02-10 23:41:08 +01:00
Thibault Deckers
8353064945 identify video location from Apple QuickTime metadata, and 3GPP loci atom 2025-02-09 19:47:35 +01:00
Thibault Deckers
de0cfb1431 #1424 fixed opening embedded video when video track is not the first one 2025-02-09 12:42:26 +01:00
Thibault Deckers
026cfebd49 #1423 support for Samsung HEIC motion photos embedding video in sefd box 2025-02-09 11:32:27 +01:00
Thibault Deckers
e2e0ee706f magnifier: init fix 2025-02-08 19:47:01 +01:00
Thibault Deckers
d11bd21d89 magnifier: scale boundaries padding;
editor: pan fixes
2025-02-08 19:32:35 +01:00
Thibault Deckers
62952de907
Update README.md 2025-02-06 23:03:43 +01:00
Thibault Deckers
881882a117 version bump 2025-02-06 17:18:19 +01:00
Weblate (bot)
578abe3d5a
l10n by weblate (#1415)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-02-07 01:13:37 +09:00
Thibault Deckers
fd9a118bb0 upgrades 2025-02-06 16:54:33 +01:00
Thibault Deckers
b7b57ca15b minor fix 2025-02-06 16:02:42 +01:00
Thibault Deckers
670b4b1830 minor fix 2025-02-06 14:56:10 +01:00
Thibault Deckers
f143f86732 minor fix 2025-02-06 14:50:31 +01:00
Thibault Deckers
71b37a9d77 upgraded Flutter to stable v3.27.4 2025-02-06 14:20:21 +01:00
Thibault Deckers
22f984bb72 improved handling of motion photo with incorrect video offset 2025-02-05 20:30:06 +01:00
Thibault Deckers
bad11564c6 #1417 do not trust video size and orientation reported by media store 2025-02-04 21:02:12 +01:00
Thibault Deckers
bbd819df19 minor 2025-02-04 20:35:46 +01:00
Thibault Deckers
34e22cd486 minor 2025-02-04 19:48:38 +01:00
Thibault Deckers
4b4d444884 minor 2025-02-04 19:30:06 +01:00
Thibault Deckers
cdfd7808dd minor 2025-02-04 18:56:12 +01:00
Thibault Deckers
ffa23f445d minor 2025-02-04 18:09:34 +01:00
Thibault Deckers
b2e4c291e8 l10n 2025-02-03 19:47:04 +01:00
Weblate (bot)
377601422e
l10n by weblate (#1404)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-02-04 03:43:55 +09:00
dependabot[bot]
eeb2f912b5
Bump actions/setup-java from 4.6.0 to 4.7.0 (#1407)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.6.0 to 4.7.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](7a6d8a8234...3a4f6e1af5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 03:42:38 +09:00
dependabot[bot]
e266dc3c28
Bump github/codeql-action from 3.28.6 to 3.28.8 (#1409)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.6 to 3.28.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](17a820bf2e...dd746615b3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 03:42:14 +09:00
Thibault Deckers
e14a1a0bee #1075 metadata: toggle for all types in removal dialog 2025-02-03 19:29:43 +01:00
Thibault Deckers
598b705b36 #1323 preserve favourite status when converting items 2025-02-03 19:12:46 +01:00
Thibault Deckers
0a3a792a7e #1201 keep selection when action on several items is interrupted before processing 2025-02-02 22:49:07 +01:00
Thibault Deckers
16da0ec3f5 #1331 ignore attempts to move file to its current folder 2025-02-02 20:59:17 +01:00
Thibault Deckers
892e64ef28 #1384 improved subsampling and filter quality strategy 2025-02-01 00:08:36 +01:00
Weblate (bot)
b54ed21c93
l10n by weblate (#1395)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-01-29 03:59:17 +09:00
dependabot[bot]
8ef2c09856
Bump github/codeql-action from 3.28.2 to 3.28.6 (#1403)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.2 to 3.28.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](d68b2d4edb...17a820bf2e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-29 03:58:07 +09:00
Thibault Deckers
0301269171 #1397 edit location via GPX 2025-01-27 22:22:12 +01:00
Thibault Deckers
a99f4877ce minor 2025-01-23 00:41:03 +01:00
dependabot[bot]
405794467b
Bump actions/attest-build-provenance from 2.1.0 to 2.2.0 (#1399)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](7668571508...520d128f16)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 06:14:17 +09:00
dependabot[bot]
2610de09ae
Bump github/codeql-action from 3.28.1 to 3.28.2 (#1398)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.1 to 3.28.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b6a472f63d...d68b2d4edb)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 06:13:54 +09:00
Thibault Deckers
2be1b8b538 l10n 2025-01-21 22:26:45 +01:00
Weblate (bot)
aea76c6c1a
l10n by weblate (#1381)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2025-01-22 06:25:39 +09:00
Thibault Deckers
dbbef98ac3 upgraded Flutter to stable v3.27.3 2025-01-21 22:21:09 +01:00
Thibault Deckers
43b832680c #1388 info: generate palette in another isolate 2025-01-21 20:27:28 +01:00
Thibault Deckers
b282d0a250 debug: viewer tile display flag 2025-01-21 18:14:25 +01:00
Thibault Deckers
0575a6cce6 #1391 region decoding fallback to jpeg export 2025-01-21 18:01:04 +01:00
Thibault Deckers
8e5d971a6f ExifInterface upgrade 2025-01-21 14:31:33 +01:00
Thibault Deckers
a0f7af96e0 upgraded pixy meta 2025-01-21 00:07:10 +01:00
dependabot[bot]
2d65efdcb4
Bump step-security/harden-runner from 2.10.3 to 2.10.4 (#1392)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.3 to 2.10.4.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](c95a14d0e5...cb605e52c2)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 18:13:37 +01:00
Thibault Deckers
456486b846 upgraded tiff lib 2025-01-20 00:31:50 +01:00
Thibault Deckers
1b404e0ee8 gradle upgrades 2025-01-16 23:18:03 +01:00
Thibault Deckers
aad89c2255 about: added display details to system info 2025-01-14 22:52:42 +01:00
Thibault Deckers
9faa9b7c26 Merge branch 'develop' of https://github.com/deckerst/aves.git 2025-01-14 22:38:54 +01:00
Thibault Deckers
cfe87422aa upgraded Flutter to stable v3.27.2 2025-01-14 22:38:37 +01:00
Thibault Deckers
8b7a14d916 about: added display details to system info 2025-01-14 22:26:25 +01:00
dependabot[bot]
e1934510b5
Bump github/codeql-action from 3.28.0 to 3.28.1 (#1386)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.0 to 3.28.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](48ab28a6f5...b6a472f63d)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 03:11:34 +09:00
dependabot[bot]
4067d89075
Bump ncipollo/release-action from 1.14.0 to 1.15.0 (#1387)
Bumps [ncipollo/release-action](https://github.com/ncipollo/release-action) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/ncipollo/release-action/releases)
- [Commits](2c591bcc8e...cdcc88a9ac)

---
updated-dependencies:
- dependency-name: ncipollo/release-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 03:11:22 +09:00
Thibault Deckers
41ab5d8f90 version bump 2025-01-13 10:43:41 +01:00
Thibault Deckers
6a5b0770e0 #1382 DDM coordinate format 2025-01-13 10:40:23 +01:00
Weblate (bot)
f108103a4b
l10n by weblate (#1372)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Short description

Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
2025-01-13 07:16:02 +09:00
Thibault Deckers
4d50f258e4 PiP: fixed trigger state, aspect ratio clamp 2025-01-12 23:13:36 +01:00
Thibault Deckers
bb5bbcc069 #1378 accessibility: apply system "touch and hold delay" setting 2025-01-12 19:09:50 +01:00
dependabot[bot]
8c11a7bbd4
Bump actions/upload-artifact from 4.5.0 to 4.6.0 (#1377)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](6f51ac03b9...65c4c4a1dd)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-11 01:57:07 +09:00
dependabot[bot]
dc01b46fd0
Bump step-security/harden-runner from 2.10.2 to 2.10.3 (#1376)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.2 to 2.10.3.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](0080882f6c...c95a14d0e5)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-11 01:56:47 +09:00
Thibault Deckers
43521e6268 removed ffmpeg-kit dependency 2025-01-10 17:56:19 +01:00
Thibault Deckers
550c72e994 #1368 crashfix, using media-kit instead of ffmpeg-kit for video metadata fetch;
info: show video chapters
2025-01-10 00:35:08 +01:00
Thibault Deckers
07f253d587 version bump 2025-01-05 16:35:12 +01:00
Thibault Deckers
162900091e #1365 fixed displaying neighbour items when the initial item of a view intent is a new one 2025-01-05 16:30:57 +01:00
Thibault Deckers
ecbbd3b459 l10n: da 2025-01-05 15:08:32 +01:00
Thibault Deckers
0685bc2a95 l10n 2025-01-05 14:42:14 +01:00
Weblate (bot)
d1ab33e64d
l10n by weblate (#1369)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/sat/
Translation: Aves/App - Main
Translation: Aves/Store - Short description

Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Saúl Palacios <palacios22c@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
2025-01-05 22:40:58 +09:00
Thibault Deckers
b87dff0fb0 upgrades 2025-01-05 14:29:28 +01:00
Thibault Deckers
2d1fbbd4d3 search: fixed dynamic album name filtering 2025-01-03 12:08:26 +01:00
Thibault Deckers
9bdb3171d9 #1370 dynamic albums: decompose action 2025-01-01 19:11:27 +01:00
Thibault Deckers
43a42ad1dc reporting fix 2024-12-31 10:59:40 +01:00
Thibault Deckers
799c263202 l10n test 2024-12-30 17:10:49 +01:00
Thibault Deckers
54b5d5d377 l10n test 2024-12-30 17:08:43 +01:00
Thibault Deckers
68a285b7fa fixed app lifecycle state init 2024-12-30 16:18:57 +01:00
Thibault Deckers
cfb546f056 l10n 2024-12-30 10:35:02 +01:00
Weblate (bot)
89f177539e
l10n by weblate (#1353)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/be/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/el/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/es/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/et/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/id/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/is/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/it/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/my/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sat/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ta/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: Dr Hieu <v7pvas7m@anonaddy.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <32201227+glemco@users.noreply.github.com>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: wickdj <wickdj@gmail.com>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-30 18:22:47 +09:00
dependabot[bot]
73b3c96fe2
Bump github/codeql-action from 3.27.9 to 3.28.0 (#1356)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.9 to 3.28.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](df409f7d92...48ab28a6f5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 18:21:18 +09:00
Thibault Deckers
487b364c09 upgrades 2024-12-19 21:58:01 +01:00
Thibault Deckers
dd36ecb7e1 version bump 2024-12-19 21:55:05 +01:00
Weblate (bot)
ef21280721
l10n by weblate (#1350)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/be/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/el/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/es/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/et/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/id/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/is/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/it/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/my/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sat/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ta/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <32201227+glemco@users.noreply.github.com>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-20 05:51:13 +09:00
Thibault Deckers
e80f6645f1 l10n: et 2024-12-19 21:46:07 +01:00
Thibault Deckers
67bdc50758 countries: show states for Mexico 2024-12-19 21:31:00 +01:00
Thibault Deckers
4685708d49 l10n 2024-12-18 23:26:39 +01:00
Weblate (bot)
784ee82b35
l10n by weblate (#1340)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/be/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bn/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/da/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/el/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/es/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/et/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/id/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/is/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/it/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/my/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sat/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ta/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: bovirus <roberto.boriotti@canon.it>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <32201227+glemco@users.noreply.github.com>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-19 07:24:10 +09:00
dependabot[bot]
17c2c9c044
Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#1349)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.3 to 4.5.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](b4b15b8c7c...6f51ac03b9)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 06:55:48 +09:00
dependabot[bot]
4bd8ebed79
Bump actions/setup-java from 4.5.0 to 4.6.0 (#1348)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](8df1039502...7a6d8a8234)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 06:55:33 +09:00
Thibault Deckers
e30e65a2cc upgraded Flutter to stable v3.27.1 2024-12-17 23:17:19 +01:00
dependabot[bot]
bb37664df0
Bump github/codeql-action from 3.27.7 to 3.27.9 (#1341)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.7 to 3.27.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](babb554ede...df409f7d92)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-17 06:01:27 +09:00
Thibault Deckers
939895599d #1342 slice entries when inserting in DB, to prevent OOM 2024-12-16 22:00:16 +01:00
Thibault Deckers
b027e9a9a8 upgraded Flutter to stable v3.27.0 2024-12-16 21:59:00 +01:00
Thibault Deckers
96d43a4ab0 version bump 2024-12-11 21:50:47 +01:00
Weblate (bot)
14e8f702ea
Translations update from Hosted Weblate (#1338)
* l10n by weblate

Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/et/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ta/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

* Translated using Weblate (Italian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/it/

* Translated using Weblate (Russian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ru/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/

* Translated using Weblate (French)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fr/

* Translated using Weblate (Korean)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ko/

* Translated using Weblate (Persian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fa/

* Translated using Weblate (Portuguese)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pt/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/es/

* Translated using Weblate (Galician)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/gl/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ar/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hant/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/uk/

* Translated using Weblate (Czech)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/cs/

* Translated using Weblate (Basque)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/eu/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sk/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hu/

* Translated using Weblate (Belarusian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/be/

* Translated using Weblate (Burmese)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/my/

* Translated using Weblate (Bengali)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bn/

* Translated using Weblate (Vietnamese)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/vi/

* Translated using Weblate (Icelandic)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/is/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/

* Translated using Weblate (Santali)

Currently translated at 0.0% (0 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sat/

* Translated using Weblate (Danish)

Currently translated at 0.0% (0 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/da/

* Translated using Weblate (Catalan)

Currently translated at 0.0% (0 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ca/

* Translated using Weblate (English (Shavian))

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/en_Shaw/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/et/

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/

* Translated using Weblate (German)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/de/

* Translated using Weblate (Greek)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/el/

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/id/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ja/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/tr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/zh_Hans/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/pl/

* Translated using Weblate (Romanian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ro/

* Translated using Weblate (Lithuanian)

Currently translated at 100.0% (3 of 3 strings)

Translation: Aves/Store - Full description
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/lt/

---------

Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-12 05:39:57 +09:00
Thibault Deckers
9b71593743 Update full_description.txt 2024-12-11 19:48:19 +01:00
Thibault Deckers
c0a821a44f l10n: bg 2024-12-10 23:42:43 +01:00
Thibault Deckers
29d5fab00d l10n: ta 2024-12-10 22:47:47 +01:00
Thibault Deckers
202273b669 upgrades 2024-12-10 21:56:45 +01:00
Weblate (bot)
7b375bb6a8
l10n by weblate (#1333)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/he/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/et/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ta/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-11 05:56:00 +09:00
dependabot[bot]
966275923c
Bump actions/attest-build-provenance from 2.0.1 to 2.1.0 (#1336)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](c4fbc64884...7668571508)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 02:55:09 +09:00
dependabot[bot]
4e0677512f
Bump github/codeql-action from 3.27.6 to 3.27.7 (#1337)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.6 to 3.27.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](aa57810251...babb554ede)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 02:55:02 +09:00
Thibault Deckers
2f5831cf4a l10n 2024-12-09 23:07:45 +01:00
dependabot[bot]
c15595887a
Bump actions/attest-build-provenance from 2.0.0 to 2.0.1 (#1328)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](619dbb2e03...c4fbc64884)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-10 07:01:22 +09:00
Weblate (bot)
2d70dde7cd
l10n by weblate (#1327)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/et/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-10 07:01:00 +09:00
Thibault Deckers
ebb00923bf l10n 2024-12-06 00:06:31 +01:00
Weblate (bot)
d2a27e979b
l10n by weblate (#1326)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ta/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-06 07:47:10 +09:00
Weblate (bot)
07f6419bb5
l10n by weblate (#1318)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/bg/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Petrov <i_v_c@mail.ru>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-12-05 06:42:16 +09:00
dependabot[bot]
bbfbc509c6
Bump github/codeql-action from 3.27.5 to 3.27.6 (#1324)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.5 to 3.27.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f09c1c0a94...aa57810251)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-05 02:51:36 +09:00
dependabot[bot]
4d823acded
Bump actions/attest-build-provenance from 1.4.4 to 2.0.0 (#1325)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.4.4 to 2.0.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](ef244123eb...619dbb2e03)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-05 02:50:46 +09:00
Thibault Deckers
303425e699 #1107 dynamic albums 2024-12-03 00:25:12 +01:00
Thibault Deckers
76f0764d27 #1319 guard against uninitialized system brightness in some environments 2024-11-30 21:54:22 +01:00
Thibault Deckers
d02e6cc693 l10n 2024-11-28 00:13:56 +01:00
Weblate (bot)
cdd7aca33c
l10n by weblate (#1314)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/bg/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/bg/
Translation: Aves/App - Android
Translation: Aves/App - Main

Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Petrov <i_v_c@mail.ru>
2024-11-28 06:30:19 +09:00
Thibault Deckers
066bff3faf log fix 2024-11-27 22:04:48 +01:00
Thibault Deckers
801cdf2897 upgrades 2024-11-24 23:30:32 +01:00
Thibault Deckers
cc58f919f7 version bump 2024-11-24 23:16:18 +01:00
Weblate (bot)
8ff44186f6
l10n by weblate (#1304)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/et/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Short description

Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Cleverson Cândido <optimuspraimu@gmail.com>
Co-authored-by: Hasan Kara <hasanbeytullahkara@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: wanzh <wanzh@users.noreply.hosted.weblate.org>
2024-11-25 07:11:34 +09:00
Thibault Deckers
fc9c2dfcc5 minor fix 2024-11-24 23:09:47 +01:00
Thibault Deckers
1a78fdfd0b #1292 remember title filter visibility by page 2024-11-24 01:15:36 +01:00
Thibault Deckers
a3024fdf4e minor 2024-11-24 00:52:29 +01:00
Thibault Deckers
5b6eb44c05 trim new tags 2024-11-24 00:08:26 +01:00
Thibault Deckers
ffbf0bd8f2 improved error reporting 2024-11-23 23:24:41 +01:00
Thibault Deckers
ec7e4ac2f2 simplified time zone offset check 2024-11-23 22:33:01 +01:00
dependabot[bot]
66f0e2b1e9
Bump actions/dependency-review-action from 4.4.0 to 4.5.0 (#1310)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](4081bf99e2...3b139cfc5f)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 07:48:17 +09:00
dependabot[bot]
b50c305204
Bump github/codeql-action from 3.27.4 to 3.27.5 (#1308)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.4 to 3.27.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ea9e4e3799...f09c1c0a94)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 07:48:09 +09:00
dependabot[bot]
1f679ec11c
Bump step-security/harden-runner from 2.10.1 to 2.10.2 (#1306)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.1 to 2.10.2.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](91182cccc0...0080882f6c)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 07:47:57 +09:00
Thibault Deckers
2f5e959fb0 integrate with OS app language settings on Android >=14 2024-11-21 23:47:39 +01:00
Thibault Deckers
0dee00dbca version bump 2024-11-18 23:46:12 +01:00
Weblate (bot)
0c55c7eab0
l10n by weblate (#1302)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/et/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Short description

Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Cleverson Cândido <optimuspraimu@gmail.com>
Co-authored-by: Hasan Kara <hasanbeytullahkara@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: wanzh <wanzh@users.noreply.hosted.weblate.org>
2024-11-19 07:41:45 +09:00
Thibault Deckers
46374dfcc1 do not try to start analysis service when app goes to background 2024-11-18 23:41:13 +01:00
Thibault Deckers
58e3912f86 #1269 fixed cataloguing images with wrong MPF offsets 2024-11-18 21:51:20 +01:00
Thibault Deckers
89c4fdd854 info: show avif metadata as video 2024-11-17 18:27:20 +01:00
Thibault Deckers
039de32faa avif: fixed decoding of rotated still, fixed sizing during cataloguing
info: show avif metadata via exifinterface
2024-11-17 18:18:40 +01:00
Thibault Deckers
ad214b3f56 ExifInterface upgrade 2024-11-17 18:15:53 +01:00
Thibault Deckers
a25f117e1b l10n 2024-11-17 15:23:00 +01:00
Weblate (bot)
b16bb12749
l10n by weblate (#1288)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/et/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/et/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Short description

Co-authored-by: Cleverson Cândido <optimuspraimu@gmail.com>
Co-authored-by: Hasan Kara <hasanbeytullahkara@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: wanzh <wanzh@users.noreply.hosted.weblate.org>
2024-11-17 23:20:25 +09:00
dependabot[bot]
925845a130
Bump github/codeql-action from 3.27.2 to 3.27.4 (#1298)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.2 to 3.27.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](9278e42166...ea9e4e3799)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-17 22:42:58 +09:00
Thibault Deckers
d7f5eabc34 upgraded Flutter to stable v3.24.5 2024-11-17 14:41:31 +01:00
dependabot[bot]
d5fef14e9a
Bump github/codeql-action from 3.27.1 to 3.27.2 (#1290)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.1 to 3.27.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](4f3212b617...9278e42166)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-13 02:58:57 +09:00
Thibault Deckers
e7ec41b8d5 #1280 print: ignore undecodable pages 2024-11-12 18:58:41 +01:00
Thibault Deckers
d83537f774 #1253 fastlane: changed Shavian tag 2024-11-11 22:07:46 +01:00
Weblate (bot)
e3eda2d330
l10n by weblate (#1282)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translation: Aves/App - Android
Translation: Aves/App - Main

Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: rehork <cooky@e.email>
2024-11-12 06:04:56 +09:00
Thibault Deckers
88df125f5e #1271 upgrade media-kit 2024-11-11 13:56:14 +01:00
Thibault Deckers
339a039dd6 #1272 albums: improved album creation feedback 2024-11-08 23:29:51 +01:00
Thibault Deckers
e071ff299a #1253 changed fastlane tag for Shavian as workaround for f-droid broken resolution 2024-11-08 19:38:17 +01:00
Thibault Deckers
a65c0fe3b1 #1279 changed Shavian resource tag to prevent bug on Android <7 2024-11-08 19:30:50 +01:00
Weblate (bot)
1c61aa7d14
l10n by weblate (#1270)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/en_SHAW/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/az/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/en_Shaw/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: 5FeetUnder <15950507+5FeetUnder@users.noreply.github.com>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Noah Kenzie Rodriguez-Beus <noahbeus@protonmail.com>
Co-authored-by: Olli <ollinen@ollit.dev>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Your Average Code <neumeiersi91358@th-nuernberg.de>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: elea11 <p.manuel.warnecke@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
2024-11-09 03:20:37 +09:00
dependabot[bot]
b5e6ce9c94
Bump github/codeql-action from 3.27.0 to 3.27.1 (#1281)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.0 to 3.27.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](662472033e...4f3212b617)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-09 02:51:59 +09:00
dependabot[bot]
4384d4dcf3
Bump actions/attest-build-provenance from 1.4.3 to 1.4.4 (#1278)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](1c608d11d6...ef244123eb)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-09 02:51:50 +09:00
Thibault Deckers
be1e96270b version bump 2024-10-30 23:13:44 +01:00
Thibault Deckers
63b9b1015a l10n 2024-10-30 21:48:54 +01:00
Weblate (bot)
382c7e5b1f
l10n by weblate (#1265)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/en_SHAW/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/en_Shaw/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Noah Kenzie Rodriguez-Beus <noahbeus@protonmail.com>
Co-authored-by: Olli <ollinen@ollit.dev>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Your Average Code <neumeiersi91358@th-nuernberg.de>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
2024-10-31 05:47:45 +09:00
Thibault Deckers
71efe696c1 settings: video controls preview 2024-10-30 21:37:14 +01:00
Thibault Deckers
da959874fc media-kit update 2024-10-30 20:33:58 +01:00
Thibault Deckers
e65f23d248 #1086 video: control buttons 2024-10-30 20:06:29 +01:00
Thibault Deckers
ee6b34ad21 logs 2024-10-30 20:05:27 +01:00
Thibault Deckers
e0b3f92b65 video: custom playback buttons 2024-10-30 11:40:07 +01:00
Thibault Deckers
9e64da8940 minor 2024-10-30 10:50:07 +01:00
Thibault Deckers
dbb19e1f31 video controls prep 2024-10-30 10:22:20 +01:00
Thibault Deckers
b97000e8e4 #1172 viewer: hero pop/push diversion animation issue workaround 2024-10-30 01:43:30 +01:00
Thibault Deckers
c9663fea19 lint 2024-10-30 01:33:03 +01:00
Thibault Deckers
520499d33e stats: reversed histogram gradient 2024-10-29 23:11:57 +01:00
Thibault Deckers
b5fea82fce loader/completer review 2024-10-29 23:11:35 +01:00
Thibault Deckers
f0e048e340 lint 2024-10-29 19:44:01 +01:00
Thibault Deckers
104403948c #1238 tagging while vaults are unlocked does not yield recent tags visible when vaults are locked 2024-10-29 19:29:06 +01:00
dependabot[bot]
00a4890fb1
Bump actions/dependency-review-action from 4.3.5 to 4.4.0 (#1268)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.3.5 to 4.4.0.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](a6993e2c61...4081bf99e2)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-30 02:38:06 +09:00
Thibault Deckers
d4bb360902 fixed shortcut thumbnail for content URI 2024-10-29 18:37:50 +01:00
Thibault Deckers
ae22a25a13 #1248 fixed new album name resolution to allow same name as existing empty directory 2024-10-29 02:02:42 +01:00
Thibault Deckers
c6ec5afba1 #1249 fixed copying content URI items 2024-10-29 01:06:46 +01:00
Thibault Deckers
ccbca7c506 minor fix 2024-10-29 00:05:33 +01:00
Thibault Deckers
ea3619d7f5 disable impeller, minor fix 2024-10-29 00:05:24 +01:00
Thibault Deckers
33ffb1cd1a source init scope review 2024-10-29 00:04:53 +01:00
Thibault Deckers
687ca5eb41 l10n: en-Shaw 2024-10-28 00:13:20 +01:00
Thibault Deckers
1ab663ba70 flutter vNext prep 2024-10-27 23:39:39 +01:00
Thibault Deckers
cce5b1fced l10n: en-Shaw 2024-10-27 23:22:17 +01:00
Thibault Deckers
1c828c3cf6 media-kit package ref fix 2024-10-27 21:14:53 +01:00
Thibault Deckers
5985a89f85 leaks: overlay entry review, change notifier mixin creation event 2024-10-26 01:27:02 +02:00
Thibault Deckers
3a63533dd9 leak tracking setup 2024-10-26 00:58:52 +02:00
Thibault Deckers
8de8d26756 deprecation: whereNotNull 2024-10-26 00:19:43 +02:00
Thibault Deckers
884baae602 deprecation: opacity 2024-10-26 00:10:40 +02:00
Thibault Deckers
e039798463 l10n: en-Shaw 2024-10-25 21:10:30 +02:00
Weblate (bot)
6c8fb28e09
l10n by weblate (#1261)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/en_SHAW/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/en_Shaw/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: Your Average Code <neumeiersi91358@th-nuernberg.de>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
2024-10-26 03:22:27 +09:00
Thibault Deckers
0af94eee1a enable impeller 2024-10-25 19:34:15 +02:00
Thibault Deckers
f1b143ac95 upgraded Flutter to stable v3.24.4 2024-10-25 19:09:26 +02:00
Thibault Deckers
0b614e8ce5 use MPV commands directly 2024-10-25 18:50:37 +02:00
Thibault Deckers
86b06bff7a #1086 frame stepping with latest media-kit 2024-10-25 01:16:19 +02:00
StepSecurity Bot
0ec2875736
[StepSecurity] ci: Harden GitHub Actions (#1264)
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
2024-10-25 04:52:59 +09:00
dependabot[bot]
fc30db2f82
Bump actions/setup-java from 4.4.0 to 4.5.0 (#1263)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](b36c23c0d9...8df1039502)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 04:48:32 +09:00
Thibault Deckers
d96f9768f9 map: hero fixes 2024-10-24 20:53:29 +02:00
Thibault Deckers
9aeb0a1fc3 fixed silent crash when exiting after cancelled large transaction from item picking 2024-10-24 00:30:59 +02:00
Thibault Deckers
3448a060db l10n 2024-10-23 20:26:59 +02:00
Thibault Deckers
50d151ccc0 Revert "Translations update from Hosted Weblate (#1260)"
This reverts commit d93022591a.
2024-10-23 20:04:47 +02:00
Weblate (bot)
d93022591a
Translations update from Hosted Weblate (#1260)
* l10n by weblate

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/en_SHAW/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translation: Aves/App - Android
Translation: Aves/App - Main

* Deleted translation using Weblate (English (en_SHAW))

* Deleted translation using Weblate (English (en_SHAW))

---------

Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
2024-10-24 03:02:59 +09:00
Thibault Deckers
1522e5c5c4 Revert "Translations update from Hosted Weblate (#1259)"
This reverts commit 1eecd224e5.
2024-10-23 19:41:27 +02:00
Weblate (bot)
1eecd224e5
Translations update from Hosted Weblate (#1259)
* l10n by weblate

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/en_SHAW/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translation: Aves/App - Android
Translation: Aves/App - Main

* Deleted translation using Weblate (English (en_SHAW))

---------

Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
2024-10-24 02:38:09 +09:00
Thibault Deckers
ee116c49f4 Update app_en_SHAW.arb 2024-10-23 19:32:31 +02:00
Weblate (bot)
25d9349cdc
l10n by weblate (#1257)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/en_SHAW/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/en_Shaw/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translation: Aves/App - Android
Translation: Aves/App - Main

Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Paranoid Android <f.cherdzhiev@innopolis.university>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
2024-10-24 02:27:54 +09:00
dependabot[bot]
9983f3800c
Bump actions/checkout from 4.2.1 to 4.2.2 (#1258)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.1 to 4.2.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](eef61447b9...11bd71901b)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 02:25:06 +09:00
Thibault Deckers
df9d9733fa l10n: added files for English (Shavian) locale 2024-10-23 01:22:00 +02:00
Thibault Deckers
b9327db44b map: create shortcut to custom region and filters 2024-10-23 01:09:17 +02:00
Weblate (bot)
5ce8bef9cc
Translations update from Hosted Weblate (#1246)
* l10n by weblate

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translation: Aves/App - Android
Translation: Aves/App - Main

* Deleted translation using Weblate (English (en_SHAW))

* Deleted translation using Weblate (English (en_SHAW))

* Deleted translation using Weblate (English (Shavian) (en-Shaw))

* Deleted translation using Weblate (English (Shavian) (en-Shaw))

* Deleted translation using Weblate (English (Shavian))

* Delete android/app/src/main/res/values-b+en+Shaw/strings.xml

---------

Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Whoever4976 <wolffjonas47@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
2024-10-23 03:28:55 +09:00
dependabot[bot]
51868287d4
Bump github/codeql-action from 3.26.13 to 3.27.0 (#1256)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.13 to 3.27.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f779452ac5...662472033e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 02:47:56 +09:00
dependabot[bot]
67616ad077
Bump actions/dependency-review-action from 4.3.4 to 4.3.5 (#1255)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.3.4 to 4.3.5.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](5a2ce3f5b9...a6993e2c61)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 02:47:43 +09:00
Thibault Deckers
a3b613662a map: vector tile cache on external storage 2024-10-20 20:04:42 +02:00
Thibault Deckers
5f80fab460 minor 2024-10-20 11:21:59 +02:00
Thibault Deckers
fdf9aa7ef4 Update CHANGELOG.md 2024-10-18 22:15:14 +02:00
Thibault Deckers
76c3244074 db: use cursor 2024-10-18 20:16:24 +02:00
Thibault Deckers
7982c536e4 map: icon, density, transition fixes 2024-10-18 00:35:29 +02:00
Thibault Deckers
95c279088a minor 2024-10-14 20:59:53 +02:00
Thibault Deckers
deb9f4af87 upgrades 2024-10-14 20:55:25 +02:00
Thibault Deckers
342894c360 l10n 2024-10-14 20:39:54 +02:00
Weblate (bot)
a7c117a942
l10n by weblate (#1235)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translation: Aves/App - Android
Translation: Aves/App - Main

Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: João Palmeiro <joaommpalmeiro@gmail.com>
Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
2024-10-15 03:31:35 +09:00
dependabot[bot]
b47e5bf3f4
Bump github/codeql-action from 3.26.12 to 3.26.13 (#1245)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.12 to 3.26.13.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](c36620d31a...f779452ac5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 03:29:26 +09:00
Thibault Deckers
0a99dc24b6 fixed kotlin jvm and source compatibility versions 2024-10-14 19:28:33 +02:00
Thibault Deckers
78a28e6762 minor 2024-10-14 00:00:00 +02:00
Thibault Deckers
ee4dd38bbe check whether app is in foreground before starting service 2024-10-13 23:33:46 +02:00
Thibault Deckers
bb89756815 minor 2024-10-13 23:03:25 +02:00
Thibault Deckers
1058aba262 picker: prevent security exception 2024-10-13 22:04:01 +02:00
Thibault Deckers
2932e0b80f release: generate artifact attestation 2024-10-10 20:41:00 +02:00
Thibault Deckers
a8ad8b9ee6 version bump 2024-10-10 19:10:02 +02:00
Thibault Deckers
148a120e4c minor fixes 2024-10-10 19:07:50 +02:00
Thibault Deckers
fb0a9436db l10n 2024-10-10 18:43:19 +02:00
Weblate (bot)
5d1e59ca0f
l10n by weblate (#1229)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translation: Aves/App - Android
Translation: Aves/App - Main

Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: splice11 <trenchedgrandpa@protonmail.com>
2024-10-11 01:42:40 +09:00
dependabot[bot]
ef33cd7902
Bump actions/upload-artifact from 4.4.2 to 4.4.3 (#1234)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.2 to 4.4.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](84480863f2...b4b15b8c7c)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-11 01:38:57 +09:00
dependabot[bot]
1c51767de4
Bump actions/upload-artifact from 4.4.1 to 4.4.2 (#1232)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](604373da63...84480863f2)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-11 01:16:31 +09:00
Thibault Deckers
9067d75959 fixed case-insensitive access to restricted directories 2024-10-10 18:14:26 +02:00
Thibault Deckers
dde21abbac version bump 2024-10-09 01:04:02 +02:00
Thibault Deckers
9401dd2874 version bump 2024-10-09 00:09:43 +02:00
Thibault Deckers
7da501ad2e upgrades 2024-10-08 23:58:00 +02:00
Thibault Deckers
01e2bcc1b4 fixed download directory access when not using reference case 2024-10-08 23:48:39 +02:00
Weblate (bot)
83f273f76e
l10n by weblate (#1225)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translation: Aves/App - Main

Co-authored-by: LE NHUT BINH <125377511+lenhutbinh@users.noreply.github.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: rehork <cooky@e.email>
2024-10-09 06:07:56 +09:00
dependabot[bot]
802516a82d
Bump github/codeql-action from 3.26.11 to 3.26.12 (#1228)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.11 to 3.26.12.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](6db8d6351f...c36620d31a)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 05:18:52 +09:00
dependabot[bot]
44ea51e1f2
Bump actions/checkout from 4.2.0 to 4.2.1 (#1227)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](d632683dd7...eef61447b9)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 05:18:41 +09:00
Thibault Deckers
763eebac7d viewer: fixed video progress bar layout 2024-10-07 23:56:48 +02:00
Thibault Deckers
eb68b8eba6 fixed section header widget span layout 2024-10-07 23:36:21 +02:00
Thibault Deckers
77cea1b7cb info: hide empty XMP 2024-10-07 23:24:03 +02:00
Thibault Deckers
211f803afe source: disable analysis for widget, screen saver; disabling analysis also disables entry discovery 2024-10-07 22:24:58 +02:00
Thibault Deckers
618b63bfc0 source: dismiss changes if only loaded for specific directory 2024-10-07 19:28:20 +02:00
dependabot[bot]
ada3c2908b
Bump actions/upload-artifact from 4.4.0 to 4.4.1 (#1226)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.0 to 4.4.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](50769540e7...604373da63)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 01:58:35 +09:00
Thibault Deckers
f833689961 prevent ANR by moving Binder call off main thread 2024-10-07 18:58:17 +02:00
Thibault Deckers
0f5d0a42a3 #1203 keep full file to fetch metadata from large PNG 2024-10-07 17:09:12 +02:00
Thibault Deckers
8f431a5426 explorer: hide path action 2024-10-07 16:06:53 +02:00
Thibault Deckers
eb1efe10e5 #1185 relaxed Apple HDR image check conditions 2024-10-07 15:50:13 +02:00
Thibault Deckers
306456967e #1222 cataloguing: fixed MPF offset lookup 2024-10-07 15:19:41 +02:00
Weblate (bot)
2f4d1f5689
l10n by weblate (#1217)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translation: Aves/App - Main

Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
2024-10-07 07:30:24 +09:00
dependabot[bot]
c6265940ab
Bump github/codeql-action from 3.26.10 to 3.26.11 (#1220)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.10 to 3.26.11.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e2b3eafc8d...6db8d6351f)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 07:24:37 +09:00
Thibault Deckers
c2a8aa7919 Merge remote-tracking branch 'refs/remotes/weblate/develop'
Conflicts:
	lib/l10n/app_es.arb
	lib/l10n/app_eu.arb
	lib/l10n/app_fr.arb
	lib/l10n/app_hu.arb
	lib/l10n/app_ko.arb
	lib/l10n/app_nl.arb
	lib/l10n/app_pl.arb
	lib/l10n/app_sv.arb
	lib/l10n/app_tr.arb
2024-10-06 23:31:04 +02:00
Shift18
c7eb9d3c91
Translated using Weblate (Swedish)
Currently translated at 100.0% (694 of 694 strings)

Translation: Aves/App - Main
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
2024-10-05 20:15:41 +02:00
Shift18
a7c2a2bb41
Translated using Weblate (Swedish)
Currently translated at 100.0% (9 of 9 strings)

Translation: Aves/App - Android
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
2024-10-05 20:15:40 +02:00
rehork
c2de228a5d
Translated using Weblate (Polish)
Currently translated at 100.0% (694 of 694 strings)

Translation: Aves/App - Main
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
2024-10-05 20:15:39 +02:00
rehork
05dd44e3ce
Translated using Weblate (Polish)
Currently translated at 100.0% (9 of 9 strings)

Translation: Aves/App - Android
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/pl/
2024-10-05 20:15:39 +02:00
Oğuz Ersen
b800ed97da
Translated using Weblate (Turkish)
Currently translated at 100.0% (694 of 694 strings)

Translation: Aves/App - Main
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
2024-10-05 20:15:38 +02:00
Thibault Deckers
a2260f995f l10n: map attribution key reuse 2024-10-04 15:38:09 +02:00
Hosted Weblate
e6a2d65168
l10n by weblate
Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Aitor Salaberria <amento@ni.eus>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Anurag Samota <anuragsamotasamota@gmail.com>
Co-authored-by: Enenra <nnra2210@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Leo Aaua Felix <g00g7el@gmail.com>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Taufan <taufanxxx@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Victor M <victormorita@tuta.io>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
2024-10-04 12:44:31 +00:00
Thibault Deckers
7fabb059b7 map: fixed collection refresh when source initializes 2024-10-04 14:44:09 +02:00
Thibault Deckers
1864f77866 upgrades, lints 2024-10-03 23:50:03 +02:00
Thibault Deckers
fcdbbc9854 l10n 2024-10-03 23:17:56 +02:00
Weblate (bot)
a1ddcf37d8
l10n by weblate (#1194)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Anurag Samota <anuragsamotasamota@gmail.com>
Co-authored-by: Enenra <nnra2210@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Leo Aaua Felix <g00g7el@gmail.com>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Taufan <taufanxxx@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Victor M <victormorita@tuta.io>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
2024-10-04 06:13:04 +09:00
Thibault Deckers
a4edacc2d4 Update CHANGELOG.md 2024-10-03 23:02:09 +02:00
Thibault Deckers
c07dc36d26 #1216 settings: hidden path filters are merged with others and can be toggled 2024-10-03 23:00:19 +02:00
Thibault Deckers
d859887319 #1211 receiving geo: uri while editing location fills in coordinates 2024-10-03 00:36:40 +02:00
Thibault Deckers
ec59e348c5 map shortcut, geo: uri handling 2024-10-02 01:11:02 +02:00
Thibault Deckers
fb0b4dcab2 Merge branch 'develop' 2024-09-30 23:54:51 +02:00
Thibault Deckers
34ff91ccea fix 2024-09-30 23:54:18 +02:00
Thibault Deckers
7a63164891 Merge branch 'develop' 2024-09-30 23:41:43 +02:00
dependabot[bot]
c982811748
Bump github/codeql-action from 3.26.9 to 3.26.10 (#1214)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.9 to 3.26.10.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](461ef6c76d...e2b3eafc8d)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 06:35:10 +09:00
Thibault Deckers
1e58ae94b9 enterprise: user/work profile switch from drawer 2024-09-30 23:34:46 +02:00
Thibault Deckers
c7e5b1ab7b android: app build cleanup 2024-09-26 23:11:49 +02:00
Thibault Deckers
00d928fa47 Merge branch 'develop' 2024-09-26 20:28:52 +02:00
Thibault Deckers
da8f6cdf7f agp upgrade 2024-09-26 20:28:34 +02:00
dependabot[bot]
9163059a16
Bump actions/checkout from 4.1.7 to 4.2.0 (#1210)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](692973e3d9...d632683dd7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 03:28:12 +09:00
Thibault Deckers
615af106d8 improved overhead 2024-09-26 00:09:12 +02:00
Thibault Deckers
d0d9783b78 americana / osm liberty merge 2024-09-25 23:23:05 +02:00
dependabot[bot]
c85d97b598
Bump github/codeql-action from 3.26.8 to 3.26.9 (#1206)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.8 to 3.26.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](294a9d9291...461ef6c76d)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 02:19:02 +09:00
dependabot[bot]
178229b283
Bump actions/setup-java from 4.3.0 to 4.4.0 (#1204)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](2dfa2011c5...b36c23c0d9)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 05:04:58 +09:00
dependabot[bot]
c2353de3b4
Bump github/codeql-action from 3.26.7 to 3.26.8 (#1199)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.7 to 3.26.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](8214744c54...294a9d9291)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 05:04:19 +09:00
Thibault Deckers
c48687b77c added OSM Americana vector tile provider 2024-09-24 00:13:46 +02:00
Thibault Deckers
33bdc41e7b map: added OpenTopoMap layer 2024-09-22 21:19:33 +02:00
Thibault Deckers
1793da1262 version bump 2024-09-17 20:22:13 +02:00
Thibault Deckers
ebdbf0324e source init context logs 2024-09-17 20:19:21 +02:00
Thibault Deckers
9093cfab69 source init context logs 2024-09-17 20:03:38 +02:00
Thibault Deckers
832a683607 minor fixes 2024-09-17 19:41:41 +02:00
Thibault Deckers
9f7fd2b049 fixed crash on view intent on Android API <27 2024-09-17 18:32:32 +02:00
Thibault Deckers
f2275d5785 version bump 2024-09-16 20:02:28 +02:00
Thibault Deckers
4851b997cf #1196 fix access to app from lock screen 2024-09-16 19:52:51 +02:00
Thibault Deckers
fc04a1cf89
Update README.md 2024-09-16 07:50:43 +09:00
Thibault Deckers
2a70cc0fb8
Update release.yml 2024-09-16 07:16:12 +09:00
Thibault Deckers
e1e6f353e0 version bump 2024-09-16 00:12:44 +02:00
Weblate (bot)
6ed79bf5a5
l10n by weblate (#1190)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Anurag Samota <anuragsamotasamota@gmail.com>
Co-authored-by: Enenra <nnra2210@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Leo Aaua Felix <g00g7el@gmail.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Taufan <taufanxxx@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Victor M <victormorita@tuta.io>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
2024-09-16 07:10:37 +09:00
Thibault Deckers
f4e5018b78 #1174 custom placeholder handling for collection-viewer hero 2024-09-16 00:07:14 +02:00
Thibault Deckers
cc3b4f661b route transition duration renaming 2024-09-15 23:50:55 +02:00
Thibault Deckers
748f8b9270 #1192 fixed path filter 2024-09-14 23:02:57 +02:00
StepSecurity Bot
652215fc13
[StepSecurity] ci: Harden GitHub Actions (#1191)
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
2024-09-15 02:34:20 +09:00
Thibault Deckers
3c460c5aa6 workflows: job/step names 2024-09-14 19:33:18 +02:00
Thibault Deckers
f6eb97434c Update quality-check.yml 2024-09-14 18:49:00 +02:00
Thibault Deckers
fad079b718 Update check.yml, codeql.yml, and 2 more files... 2024-09-14 18:32:47 +02:00
Thibault Deckers
759b719f8a
Create codeql.yml 2024-09-14 09:32:41 +09:00
Thibault Deckers
6b8790c4a3 fixed gitignore android executables in plugins 2024-09-14 01:29:41 +02:00
Thibault Deckers
5d598bb421
Update release.yml 2024-09-14 08:22:14 +09:00
Thibault Deckers
ba0d91a1ff entry hero review 2024-09-14 00:47:15 +02:00
Thibault Deckers
535d4c0d00 #1177 slideshow: fixed bottom overlay layout on inset transition 2024-09-13 21:30:35 +02:00
Thibault Deckers
013b2631aa l10n 2024-09-13 20:33:02 +02:00
Weblate (bot)
07357c29cc
l10n by weblate (#1159)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/da/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Anurag Samota <anuragsamotasamota@gmail.com>
Co-authored-by: Enenra <nnra2210@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Leo Aaua Felix <g00g7el@gmail.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Taufan <taufanxxx@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Victor M <victormorita@tuta.io>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
2024-09-14 03:18:55 +09:00
dependabot[bot]
4ec0893687
Bump github/codeql-action from 3.26.6 to 3.26.7 (#1189)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.6 to 3.26.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](4dd16135b6...8214744c54)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-14 03:18:23 +09:00
dependabot[bot]
0ed2ee44f8
Bump step-security/harden-runner from 2.9.1 to 2.10.1 (#1181)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.9.1 to 2.10.1.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](5c7944e73c...91182cccc0)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 07:02:56 +09:00
Thibault Deckers
7b982cfdd9 upgraded Flutter to stable v3.24.3 2024-09-13 00:02:29 +02:00
Thibault Deckers
f9bfbd5bea #1160 secure view: prevent access to export actions 2024-09-12 23:29:25 +02:00
Thibault Deckers
31f4737d3c Update CHANGELOG.md 2024-09-12 00:09:02 +02:00
Thibault Deckers
ef9bcebff2 fixed OOM when cataloguing malformed MP4 files 2024-09-12 00:08:08 +02:00
Thibault Deckers
590c353fcd minor fixes 2024-09-10 20:27:06 +02:00
Thibault Deckers
1b39bb6ab4 #1173 always display launch icon white background 2024-09-09 22:30:21 +02:00
dependabot[bot]
af764c1b42
Bump actions/setup-java from 4.2.2 to 4.3.0 (#1178)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.2.2 to 4.3.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](6a0805fcef...2dfa2011c5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 01:56:49 +09:00
dependabot[bot]
62f99e7d0e
Bump actions/upload-artifact from 4.3.6 to 4.4.0 (#1163)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.6 to 4.4.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](834a144ee9...50769540e7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 01:56:26 +09:00
Thibault Deckers
af689edae6 upgraded Flutter to stable v3.24.2 2024-09-04 19:05:30 +02:00
Thibault Deckers
61ccb04caa improved app mode init, fallback 2024-09-03 23:47:38 +02:00
Thibault Deckers
877fde1b26 #1160 support opening from the lock screen 2024-09-03 22:17:19 +02:00
Thibault Deckers
467093e283 improved report log for source init/refresh 2024-09-02 23:40:57 +02:00
Thibault Deckers
6869839b44 version bump 2024-09-01 01:31:12 +02:00
Thibault Deckers
9b1694cc36 request notification permission when launching scanning service 2024-09-01 01:08:07 +02:00
Thibault Deckers
5a273737fb l10n 2024-09-01 00:49:06 +02:00
Weblate (bot)
b6b8feecc4
l10n by weblate (#1155)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Leo Aaua Felix <g00g7el@gmail.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Taufan <taufanxxx@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
2024-09-01 07:44:04 +09:00
dependabot[bot]
f5ac09f083
Bump github/codeql-action from 3.26.5 to 3.26.6 (#1158)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.5 to 3.26.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](2c779ab0d0...4dd16135b6)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-01 07:39:02 +09:00
Thibault Deckers
c3ef0255fd #971 workaround to remove and report duplicates as they appear 2024-09-01 00:38:43 +02:00
Thibault Deckers
1ff1269e0f test fix 2024-08-30 19:36:46 +02:00
Thibault Deckers
306025380f #971 duplicate detection 2024-08-30 19:21:06 +02:00
Thibault Deckers
2c41e82444 albums: looser app folder matching 2024-08-29 19:19:05 +02:00
Thibault Deckers
98c39c2e7b prevent OOM when cataloguing some RAW files 2024-08-28 19:49:48 +02:00
Thibault Deckers
0f9fc2881a l10n 2024-08-28 17:35:35 +02:00
Weblate (bot)
aee082c950
l10n by weblate (#1139)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Shift18 <bribable.lawyer@posteo.net>
Co-authored-by: Taufan <taufanxxx@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
2024-08-29 00:20:05 +09:00
dependabot[bot]
66a7d30fce
Bump github/codeql-action from 3.26.2 to 3.26.5 (#1152)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.2 to 3.26.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](429e197704...2c779ab0d0)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-29 00:17:08 +09:00
Thibault Deckers
015d1ec32d upgraded Flutter to stable v3.24.1 2024-08-28 16:59:02 +02:00
Thibault Deckers
030133d274 l10n: sv 2024-08-18 20:51:00 +02:00
Weblate (bot)
5a5ddab301
l10n by weblate (#1117)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/sv/
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Scorza9999 <oliva.scorza@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
2024-08-19 02:26:44 +09:00
dependabot[bot]
5168ad07c8
Bump ossf/scorecard-action from 2.3.3 to 2.4.0 (#1138)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.3 to 2.4.0.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](dc50aa9510...62b2cac7ed)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 01:24:50 +09:00
StepSecurity Bot
8d1342cc0e
[StepSecurity] Apply security best practices (#1137)
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
2024-08-19 01:06:32 +09:00
Thibault Deckers
5824974d07 github: check on PR,
scripts: libre APKS extraction
2024-08-18 16:47:37 +02:00
Thibault Deckers
cfe4c72e8f version bump 2024-08-07 22:00:28 +02:00
Thibault Deckers
5d5f80d06f version bump 2024-08-07 22:00:13 +02:00
Thibault Deckers
f691983e0a l10n 2024-08-07 20:30:49 +02:00
Weblate (bot)
75188359c4
l10n by weblate (#1112)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: AJ07 <ajaykumarmeena676@gmail.com>
Co-authored-by: Adrien N <adriennathaniel1999@gmail.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Sartaj <ssaarrttaajj111@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Tung Anh <buihuutunganh2007@gmail.com>
Co-authored-by: fuzfyy <egeozce35@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
2024-08-08 03:25:20 +09:00
Thibault Deckers
59de10a1ce explorer: stats 2024-08-07 20:19:52 +02:00
Thibault Deckers
94cb6e2b80 explorer fixes 2024-08-07 20:05:47 +02:00
Thibault Deckers
4b2a7f8abc upgraded Flutter to stable v3.24.0 2024-08-07 00:49:13 +02:00
Thibault Deckers
554e1faf88 #1113 viewer: long descriptions are scrollable when overlay is expanded by tap 2024-08-05 21:01:55 +02:00
Thibault Deckers
867fb95426 upgrades 2024-08-05 19:52:38 +02:00
Thibault Deckers
aeeaf41e3a appbundle release for libre flavour on accrescent 2024-08-05 19:47:23 +02:00
Thibault Deckers
f824eedf63 minor 2024-08-04 20:17:33 +02:00
Thibault Deckers
4d80dfe1d6 #1087 open external map app from map views 2024-08-04 19:41:25 +02:00
Weblate (bot)
2d5a7f6c27
l10n by weblate (#1111)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: AJ07 <ajaykumarmeena676@gmail.com>
Co-authored-by: Adrien N <adriennathaniel1999@gmail.com>
Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Sartaj <ssaarrttaajj111@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Tung Anh <buihuutunganh2007@gmail.com>
Co-authored-by: fuzfyy <egeozce35@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: yangyangdaji <1504305527@qq.com>
2024-08-05 01:53:36 +09:00
Weblate (bot)
ed6acea591
l10n by weblate (#1099)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <101049867+ChAJ07@users.noreply.github.com>
Co-authored-by: AJ07 <ajaykumarmeena676@gmail.com>
Co-authored-by: Adrien N <adriennathaniel1999@gmail.com>
Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Sartaj <ssaarrttaajj111@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Tung Anh <buihuutunganh2007@gmail.com>
Co-authored-by: fuzfyy <egeozce35@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: yangyangdaji <1504305527@qq.com>
2024-08-05 01:48:00 +09:00
Thibault Deckers
bf987d63bf #1109 collection: sort by duration 2024-08-04 18:23:50 +02:00
Thibault Deckers
374a85ed7d Update CHANGELOG.md 2024-08-04 17:21:58 +02:00
Thibault Deckers
3bad4b6814 #1110 fixed widget setup race 2024-08-04 17:16:26 +02:00
Thibault Deckers
0bbed0ebda minor 2024-08-04 12:38:14 +02:00
Thibault Deckers
05bb2ab350 #1102 accessibility: enable/disable more animations 2024-07-28 20:05:35 +02:00
Thibault Deckers
1d2396193b #1102 accessibility: enable/disable more animations 2024-07-28 18:26:46 +02:00
Thibault Deckers
36c0fd2a3d #1102 accessibility: enable/disable more animations 2024-07-28 00:22:19 +02:00
Thibault Deckers
e3f6644366 #1102 accessibility: enable/disable more animations 2024-07-27 21:20:35 +02:00
Thibault Deckers
fcde32d555 #1102 accessibility: enable/disable more animations 2024-07-27 20:41:22 +02:00
Thibault Deckers
20ca48a5ed #1100 viewer: dismiss feedback when opening popup menu 2024-07-26 22:20:53 +02:00
Thibault Deckers
2a91306532 minor 2024-07-26 20:46:44 +02:00
Thibault Deckers
065a287f42 l10n 2024-07-25 20:03:52 +02:00
Weblate (bot)
ecbd4435fd
l10n by weblate (#1085)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <ajaykumarmeena676@gmail.com>
Co-authored-by: Adrien N <adriennathaniel1999@gmail.com>
Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Sartaj <ssaarrttaajj111@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Tung Anh <buihuutunganh2007@gmail.com>
Co-authored-by: fuzfyy <egeozce35@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: yangyangdaji <1504305527@qq.com>
2024-07-26 03:00:32 +09:00
Thibault Deckers
5498b5d4e5 #1097 fixed relaunch intent extras check 2024-07-25 19:40:17 +02:00
Thibault Deckers
39bb9251dc #1095fixed removing deactivated hidden item 2024-07-24 20:12:27 +02:00
Thibault Deckers
afc09e2ab4 #1080 scrolling quick action selector for tag/move/copy 2024-07-24 00:32:42 +02:00
Thibault Deckers
dc061e89e6 minor 2024-07-21 21:53:23 +02:00
Thibault Deckers
8f0ae0e79e removed legacy ijk plugin 2024-07-21 21:53:15 +02:00
Thibault Deckers
ddca1500c9 minor 2024-07-21 21:47:52 +02:00
Thibault Deckers
39ac82adf1 #1089 fixed collection menu action resolution 2024-07-21 12:28:09 +02:00
Thibault Deckers
b5848cf893 version bump 2024-07-19 08:31:34 +02:00
Thibault Deckers
29954510b7 #1088 revert to fragment activity, restoring auth for vaults, disabling predictive back 2024-07-19 08:29:26 +02:00
Thibault Deckers
e51ec1c50a version bump 2024-07-18 23:58:38 +02:00
Thibault Deckers
1eb007e43b fixed google map rendering on API < 23 2024-07-18 23:54:06 +02:00
Thibault Deckers
685d5c6944 fixed launch crash on API < 24 2024-07-18 23:54:00 +02:00
Thibault Deckers
b4c46ae9b2 upgraded Flutter to stable v3.22.3 2024-07-18 20:43:18 +02:00
Thibault Deckers
a2ea0c377a upgrades 2024-07-17 22:55:57 +02:00
Thibault Deckers
736bc881c6 version bump 2024-07-17 22:38:53 +02:00
Thibault Deckers
3a2ca9ea0c l10n 2024-07-17 22:35:50 +02:00
Weblate (bot)
acbfa69368
l10n by weblate (#1069)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/nl/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description

Co-authored-by: AJ07 <ajaykumarmeena676@gmail.com>
Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: Sartaj <ssaarrttaajj111@gmail.com>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: Tung Anh <buihuutunganh2007@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: yangyangdaji <1504305527@qq.com>
2024-07-18 05:35:16 +09:00
Thibault Deckers
4cf5a95d89 fix 2024-07-17 21:38:59 +02:00
Thibault Deckers
2d143f139f #1084 crashfix for png with large exif via ExifInterface,
fixed ExifInterface disambiguation,
improved safe mode
2024-07-17 21:11:36 +02:00
Thibault Deckers
a38c5b72ee analysis: do not chain workers, use prefs for data read/write instead 2024-07-16 20:23:46 +02:00
Thibault Deckers
fbd498bee8 explorer: custom home, shortcut 2024-07-15 23:53:46 +02:00
Thibault Deckers
3d424eb82b android 15 / api 35, predictive back 2024-07-13 01:32:30 +02:00
Thibault Deckers
0cb139b41a version bump 2024-07-11 19:03:20 +02:00
Thibault Deckers
dcc91cf5a3 fixed crash when opening app from widget 2024-07-11 18:59:02 +02:00
Thibault Deckers
f4d2fa6089
Update README.md 2024-07-10 07:01:21 +09:00
Weblate (bot)
dbccb34f6a
l10n by weblate (#1068)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translation: Aves/App - Main
2024-07-10 06:58:45 +09:00
Thibault Deckers
eb9a001919 removed huawei flavor 2024-07-09 23:54:10 +02:00
Thibault Deckers
5577657eee upgrades 2024-07-09 01:36:50 +02:00
Thibault Deckers
37475726ed version bump 2024-07-09 00:17:34 +02:00
Thibault Deckers
91e6ac1d88 analysis: ANR test fix 2024-07-08 23:06:51 +02:00
Thibault Deckers
8cb75f96b5 l10n 2024-07-08 20:50:04 +02:00
Weblate (bot)
94f097e042
l10n by weblate (#1044)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ckb/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/th/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translation: Aves/App - Main

Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Jerguš Fonfer <caro.jf@protonmail.com>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Olli <ollinen@ollit.dev>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: atilluF <110931720+atilluF@users.noreply.github.com>
Co-authored-by: elfriob <elfriob@ya.ru>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2024-07-09 03:39:54 +09:00
Thibault Deckers
22eee299c5 widget: async update 2024-07-08 20:37:40 +02:00
Thibault Deckers
b743d0de47 widgets: responsive layout 2024-07-06 00:17:58 +02:00
Thibault Deckers
fdb34edf13 widget: use system defined corner radius 2024-07-05 22:26:40 +02:00
Thibault Deckers
f8525db84e minor fix 2024-07-01 23:40:30 +02:00
Thibault Deckers
ead7adb36c about: clearing cache also removes external cache 2024-07-01 22:48:52 +02:00
Thibault Deckers
3e8bd108b0 resized launcher icon 2024-06-30 23:00:45 +02:00
Thibault Deckers
369be3cee2 minor fixes 2024-06-30 22:33:07 +02:00
Thibault Deckers
7777bf1550 fixed collection page app bar layout 2024-06-30 18:22:07 +02:00
Thibault Deckers
27db528e67 fixed handling wallpaper intent without uri
#1052 handle secure review intent
2024-06-29 01:29:22 +02:00
Thibault Deckers
0f1d8ec760 explorer fixes 2024-06-26 22:13:27 +02:00
Thibault Deckers
d78a897326 upgrades 2024-06-25 23:03:27 +02:00
Thibault Deckers
a5c5d5bad6 explorer: page review 2024-06-25 22:17:27 +02:00
Thibault Deckers
b51769e2c6 minor 2024-06-25 22:16:37 +02:00
Thibault Deckers
4a0009f4f0 explorer: source state listening 2024-06-25 00:05:54 +02:00
Thibault Deckers
97e05506a3 go to explorer from path filter 2024-06-24 23:44:08 +02:00
Thibault Deckers
5651d035e7 explorer fixes 2024-06-24 23:08:31 +02:00
Thibault Deckers
01dd1c925a explorer animations 2024-06-24 22:36:31 +02:00
Thibault Deckers
218846117c explorer fixes 2024-06-24 20:53:12 +02:00
Thibault Deckers
ace841212e #592 #910 explorer 2024-06-24 00:47:27 +02:00
Thibault Deckers
930cdf9120 minor 2024-06-23 19:23:54 +02:00
Thibault Deckers
2d56172c96 #1013 fixed handling of app inactive event by screen rotation on API33+ 2024-06-23 18:23:46 +02:00
Thibault Deckers
978c22dc50 #883 bulk converting motion photos to still images 2024-06-23 02:36:44 +02:00
Thibault Deckers
87cfae1e9a ask to rename/replace/skip when converting items with name conflict 2024-06-22 17:21:41 +02:00
Thibault Deckers
d890d9d9ae #1045 stack RAW and JPEG with same file names 2024-06-21 23:26:00 +02:00
Thibault Deckers
44eecd2e55 version bump 2024-06-17 21:40:28 +02:00
Thibault Deckers
4e811cc37c leaks: imageinfo, notifiers 2024-06-17 21:03:55 +02:00
Thibault Deckers
4116a55f8d leaks: curved animations 2024-06-17 19:09:04 +02:00
Thibault Deckers
404bc5471b minor fixes 2024-06-17 17:19:32 +02:00
Thibault Deckers
030afc70f7 #1052 handle MediaStore.ACTION_REVIEW intent 2024-06-15 19:42:36 +02:00
Thibault Deckers
201d458aef version bump 2024-06-11 23:10:52 +02:00
Weblate (bot)
9657501d6e
l10n by weblate (#1035)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ckb/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/th/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translation: Aves/App - Main

Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: atilluF <110931720+atilluF@users.noreply.github.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: syu_pf_ssy <syu.pf.ssy@outlook.com>
2024-06-12 06:05:53 +09:00
Thibault Deckers
3ccce6a529 viewer: narrower side tap area 2024-06-09 01:09:16 +02:00
Thibault Deckers
9816cc15b3 static analysis 2024-06-09 00:42:13 +02:00
Thibault Deckers
27cf1bf4aa #1008 opening app from launcher shows home page only when exited by back button 2024-06-09 00:37:30 +02:00
Thibault Deckers
1578d2d944 #1041 allow shifting dates by seconds 2024-06-08 20:05:14 +02:00
Thibault Deckers
da4a7ae38f fixed raw preview region decoding sample size 2024-06-08 18:49:39 +02:00
Thibault Deckers
1346e8867b #1037 use truncated preview when opening large DNG with metadata extractor 2024-06-08 01:53:10 +02:00
Thibault Deckers
981727cb1d Merge branch 'develop' 2024-06-08 00:39:05 +02:00
Thibault Deckers
e19b20fb59 upgraded Flutter to stable v3.22.2 2024-06-07 00:27:51 +02:00
Thibault Deckers
681f834e04 provide stream size, fixes 1037 but regression with other files 2024-06-06 22:28:15 +02:00
Thibault Deckers
dacb04c6bd upgrades 2024-06-02 00:01:24 +02:00
Weblate (bot)
71e95ca3f7
l10n by weblate (#1030)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ckb/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/th/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translation: Aves/App - Main

Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-05-31 06:54:23 +09:00
Thibault Deckers
fe0c2e345b #1012 show chip selection in collection via or filter 2024-05-30 23:32:13 +02:00
Thibault Deckers
ece28db3f8 #1032 screen saver: black background 2024-05-30 01:34:18 +02:00
Thibault Deckers
171258ba2b upgraded Flutter to stable v3.22.1 2024-05-22 22:59:30 +02:00
Thibault Deckers
c4a7969ed7 android compat fix for old devices 2024-05-22 22:16:42 +02:00
Thibault Deckers
ab92b883b6 minor 2024-05-21 22:54:00 +02:00
Thibault Deckers
17c88b2514 shaders for 3.22.0 2024-05-21 19:44:01 +02:00
Thibault Deckers
d8193584b1 l10n: typography fixes for arabic/persian 2024-05-20 18:04:46 +02:00
Weblate (bot)
b4af217d43
l10n by weblate (#1029)
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ckb/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ja/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/my/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sv/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translation: Aves/App - Main

Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-05-21 00:25:10 +09:00
Thibault Deckers
88decb71a2 l10n 2024-05-20 16:03:07 +02:00
Thibault Deckers
5e452bbd4d l10n 2024-05-20 15:58:27 +02:00
Weblate (bot)
7b761a1f41
l10n by weblate (#1017)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ca/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ckb/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/de/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/el/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/gl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/lt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nn/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/sk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ko/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Aitor Salaberria <trslbrr@gmail.com>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Evgeniy Khramov <65224669+thejenja@users.noreply.github.com>
Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: ID J <tabby4442@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Maxi <maxitendo01@proton.me>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Owen Elderbroek <o.elderbroek@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: cheese <deanlemans5646@gmail.com>
Co-authored-by: fuzfyy <egeozce35@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: randint <lancameb@hotmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: wanzh <wanzh66666@gmail.com>
Co-authored-by: wickdj <wickdj@gmail.com>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 何意挽秋風 <94283631+RejectVanity@users.noreply.github.com>
2024-05-20 22:52:30 +09:00
Thibault Deckers
1717b1fc90 l10n test 2024-05-20 15:23:57 +02:00
Thibault Deckers
f10abcd670 format review 2024-05-20 15:20:13 +02:00
Thibault Deckers
ff16651ce1 l10n: fixes for RTL 2024-05-20 01:41:00 +02:00
Thibault Deckers
dd6258d8ac l10n: typography fixes for arabic/persian 2024-05-19 01:28:26 +02:00
Thibault Deckers
ea53420c17 l10n: specify plural num pattern to apply locale 2024-05-18 23:59:21 +02:00
Thibault Deckers
56762eea9c upgraded Flutter to stable v3.22.0 2024-05-18 22:43:29 +02:00
Thibault Deckers
9a4657379b upgrades 2024-05-14 00:06:09 +02:00
Thibault Deckers
45bc2e222d l10n 2024-05-09 12:41:28 +02:00
Weblate (bot)
07ca1a9098
l10n by weblate (#1006)
Translate-URL: https://hosted.weblate.org/projects/aves/app-android/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ar/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/be/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/cs/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/es/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/eu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/fr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/hu/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/id/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/is/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/it/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/nl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pl/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/pt/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ro/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/ru/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/tr/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/uk/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/vi/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/aves/app-main/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/fa/
Translate-URL: https://hosted.weblate.org/projects/aves/store-full-description/ko/
Translate-URL: https://hosted.weblate.org/projects/aves/store-short-description/ko/
Translation: Aves/App - Android
Translation: Aves/App - Main
Translation: Aves/Store - Full description
Translation: Aves/Store - Short description

Co-authored-by: Aitor Salaberria <trslbrr@gmail.com>
Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Co-authored-by: Evgeniy Khramov <65224669+thejenja@users.noreply.github.com>
Co-authored-by: Fqwe1 <Fqwe1@users.noreply.hosted.weblate.org>
Co-authored-by: ID J <tabby4442@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Mohamed Zeroug <mzeroug19@gmail.com>
Co-authored-by: Owen Elderbroek <o.elderbroek@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thibault Deckers <thibault.deckers@gmail.com>
Co-authored-by: cheese <deanlemans5646@gmail.com>
Co-authored-by: fuzfyy <egeozce35@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: glemco <glemco@posteo.net>
Co-authored-by: marciozomb13 <marciozomb13@outlook.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: randint <lancameb@hotmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: vesp <vesp@post.cz>
Co-authored-by: wanzh <wanzh66666@gmail.com>
Co-authored-by: wickdj <wickdj@gmail.com>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 何意挽秋風 <94283631+RejectVanity@users.noreply.github.com>
2024-05-09 19:16:21 +09:00
Thibault Deckers
8eabed6bd2 l10n 2024-05-03 18:36:01 +02:00
Thibault Deckers
953013ea36 upgrades 2024-05-03 01:00:52 +02:00
1225 changed files with 40900 additions and 18041 deletions

@ -1 +1 @@
Subproject commit 54e66469a933b60ddf175f858f82eaeb97e48c8d
Subproject commit d8a9f9a52e5af486f80d932e838ee93861ffd863

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily

View file

@ -1,26 +0,0 @@
name: Quality check
on:
push:
branches:
- develop
jobs:
build:
name: Check code quality.
runs-on: ubuntu-latest
steps:
- name: Clone the repository.
uses: actions/checkout@v4
- name: Get packages for the Flutter project.
run: scripts/pub_get_all.sh
- name: Update the flutter version file.
run: scripts/update_flutter_version.sh
- name: Static analysis.
run: ./flutterw analyze
- name: Unit tests.
run: ./flutterw test

27
.github/workflows/dependency-review.yml vendored Normal file
View file

@ -0,0 +1,27 @@
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request,
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
# Once installed, if the workflow run is marked as required,
# PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 'Dependency Review'
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1

91
.github/workflows/quality-check.yml vendored Normal file
View file

@ -0,0 +1,91 @@
name: Quality check
on:
push:
branches: [ "develop", "main" ]
pull_request:
branches: [ "develop", "main" ]
types: [ opened, synchronize, reopened ]
schedule:
- cron: '17 8 * * 3'
# Declare default permissions as read only.
permissions: read-all
jobs:
analyze_flutter:
name: Flutter analysis
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Flutter packages
run: ./flutterw pub get
- name: Generate app localizations
run: ./flutterw gen-l10n
- name: Static analysis.
run: ./flutterw analyze
- name: Unit tests.
run: ./flutterw test
analyze_codeql:
name: CodeQL analysis (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
strategy:
fail-fast: false
matrix:
include:
- language: java-kotlin
build-mode: manual
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
# Building relies on the Android Gradle plugin,
# which requires a modern Java version (not the default one).
- name: Set up JDK for Android Gradle plugin
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: 'temurin'
java-version: '21'
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- if: matrix.build-mode == 'manual'
shell: bash
# build in profile mode, instead of release,
# so that setting up signing environment variables is not required
run: |
scripts/apply_flavor_play.sh
./flutterw build apk --profile -t lib/main_play.dart --flavor play
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with:
category: "/language:${{matrix.language}}"

View file

@ -5,32 +5,44 @@ on:
tags:
- v*
# Declare default permissions as read only.
permissions: read-all
jobs:
build:
name: Build and release artifacts.
release_github:
name: GitHub release
runs-on: ubuntu-latest
permissions:
attestations: write
contents: write
id-token: write
steps:
- uses: actions/setup-java@v4
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
distribution: 'zulu'
java-version: '17'
egress-policy: audit
- name: Clone the repository.
uses: actions/checkout@v4
# Building relies on the Android Gradle plugin,
# which requires a modern Java version (not the default one).
- name: Set up JDK for Android Gradle plugin
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: 'temurin'
java-version: '21'
- name: Get packages for the Flutter project.
run: scripts/pub_get_all.sh
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Update the flutter version file.
- name: Get Flutter packages
run: ./flutterw pub get
- name: Generate app localizations
run: ./flutterw gen-l10n
- name: Update Flutter version file
run: scripts/update_flutter_version.sh
- name: Static analysis.
run: ./flutterw analyze
- name: Unit tests.
run: ./flutterw test
- name: Build signed artifacts.
- name: Build signed artifacts
# `KEY_JKS` should contain the result of:
# gpg -c --armor keystore.jks
# `KEY_JKS_PASSPHRASE` should contain the passphrase used for the command above
@ -49,13 +61,12 @@ jobs:
cp build/app/outputs/bundle/playRelease/*.aab outputs
./flutterw build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders.sksl.json
cp build/app/outputs/apk/play/release/*.apk outputs
scripts/apply_flavor_huawei.sh
./flutterw build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders.sksl.json
cp build/app/outputs/apk/huawei/release/*.apk outputs
scripts/apply_flavor_izzy.sh
./flutterw build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi
cp build/app/outputs/apk/izzy/release/*.apk outputs
scripts/apply_flavor_libre.sh
./flutterw build appbundle -t lib/main_libre.dart --flavor libre
cp build/app/outputs/bundle/libreRelease/*.aab outputs
./flutterw build apk -t lib/main_libre.dart --flavor libre --split-per-abi
cp build/app/outputs/apk/libre/release/*.apk outputs
rm $AVES_STORE_FILE
@ -65,35 +76,45 @@ jobs:
AVES_KEY_ALIAS: ${{ secrets.AVES_KEY_ALIAS }}
AVES_KEY_PASSWORD: ${{ secrets.AVES_KEY_PASSWORD }}
AVES_GOOGLE_API_KEY: ${{ secrets.AVES_GOOGLE_API_KEY }}
AVES_HUAWEI_API_KEY: ${{ secrets.AVES_HUAWEI_API_KEY }}
- name: Create a release with the APK and App Bundle.
uses: ncipollo/release-action@v1
- name: Generate artifact attestation
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
with:
subject-path: 'outputs/*'
- name: Create GitHub release
uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0
with:
artifacts: "outputs/*"
body: "[Changelog](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ github.ref_name }})"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: appbundle
path: outputs/app-play-release.aab
release:
name: Create beta release on Play Store.
needs: [ build ]
release_play:
name: Play Store beta release
needs: [ release_github ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
- name: Get appbundle from artifacts.
uses: actions/download-artifact@v4
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get appbundle from artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: appbundle
- name: Release app to beta channel.
uses: r0adkll/upload-google-play@v1.1.3
- name: Release to beta channel
uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb # v1.1.3
with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: deckers.thibault.aves

76
.github/workflows/scorecards.yml vendored Normal file
View file

@ -0,0 +1,76 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '20 7 * * 2'
push:
branches: ["develop"]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
contents: read
actions: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecards on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with:
sarif_file: results.sarif

6
.gitignore vendored
View file

@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
@ -27,7 +29,6 @@ migrate_working_dir/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
@ -46,3 +47,6 @@ app.*.map.json
# screenshot generation
/test_driver/assets/screenshots/
/screenshots/
# generated files
/lib/l10ngen/app_localizations*

18
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,18 @@
repos:
- repo: https://github.com/gherynos/pre-commit-java
rev: v0.2.4
hooks:
- id: Checkstyle
- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.3
hooks:
- id: gitleaks
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace

29
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,29 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "aves (main play)",
"request": "launch",
"type": "dart",
"program": "lib/main_play.dart",
"args": [
"--flavor",
"play"
]
},
{
"name": "aves (main play) [profile]",
"request": "launch",
"type": "dart",
"program": "lib/main_play.dart",
"args": [
"--flavor",
"play"
],
"flutterMode": "profile"
}
]
}

View file

@ -4,6 +4,400 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
### Added
- Info: show matching dynamic albums
### Fixed
- crash when decoding some large thumbnails
## <a id="v1.13.2"></a>[v1.13.2] - 2025-06-02
### Changed
- downgraded Flutter to stable v3.27.4
- prevent display orientation flip when device rotation is locked
### Fixed
- moved file losing its extension and no longer being detected as media in some cases
- opening home when launching app as media picker
- removing groups with obsolete albums
- loading group custom covers
- crash when parsing some large media with trailing thumbnail
## <a id="v1.13.1"></a>[v1.13.1] - 2025-05-14
### Fixed
- albums: show groups to move/copy/export items
- albums: hide grouped albums containing hidden items only
## <a id="v1.13.0"></a>[v1.13.0] - 2025-05-12
### Added
- Albums: groups
- Collection: sort by storage path
- Search: week day filters
### Changed
- revert to Skia rendering engine
## <a id="v1.12.10"></a>[v1.12.10] - 2025-04-16
### Added
- Search: format filters
- Albums: sort by path
### Changed
- upgraded Flutter to stable v3.29.3
### Fixed
- region decoding failing to access decoder pool
## <a id="v1.12.9"></a>[v1.12.9] - 2025-04-06
### Added
- Kannada translation (thanks Chethan, Prasannakumar T Bhat)
### Changed
- enable Impeller rendering engine
### Fixed
- memory pressure during browsing
## <a id="v1.12.8"></a>[v1.12.8] - 2025-03-25
### Fixed
- swiping images for some combinations of screen size, device pixel ratio, and image size
## <a id="v1.12.7"></a>[v1.12.7] - 2025-03-16
### Added
- handle launch error to report and export DB
### Changed
- DB post-upgrade sanitization
- upgraded Flutter to stable v3.29.2
## <a id="v1.12.6"></a>[v1.12.6] - 2025-03-11
### Fixed
- data loss when editing metadata of items with incorrect mime types
- metadata inconsistency in the DB due to v1.12.4 upgrade
## <a id="v1.12.5"></a>[v1.12.5] - 2025-03-07
### Added
- support for Samsung HEIC motion photos embedding video in sefd box
- Cataloguing: identify video location from Apple QuickTime metadata, and 3GPP `loci` atom
- Collection: stack RAW and HEIC with same file names
- display home tile in side drawer when customized
- Galician translation (thanks Rubén Castiñeiras Lorenzo)
### Changed
- increased precision of file modified date to milliseconds
- upgraded Flutter to stable v3.29.1
### Fixed
- opening motion photo embedded video when video track is not the first one
- some SVG rendering issues
- decoding of SVG containing references to namespaces in !ATTLIST
- fallback decoding of images packed in RGBA_1010102 config
## <a id="v1.12.4"></a>[v1.12.4] - 2025-03-05 [YANKED]
## <a id="v1.12.3"></a>[v1.12.3] - 2025-02-06
### Added
- Metadata: edit location via GPX
- Metadata: toggle for all types in removal dialog
### Changed
- Viewer: improved subsampling and filter quality strategy
- Collection: ignore moving an item to its current directory
- Collection: keep selection when action on several items is interrupted before processing
- Collection: preserve favourite status when converting items
- upgraded Flutter to stable v3.27.4
### Fixed
- editing TIFF metadata increasing file size
- region decoding for some RAW files
- incorrect video size or orientation as reported by Media Store
- corrupting image when removing video from motion photo with incorrect metadata
## <a id="v1.12.2"></a>[v1.12.2] - 2025-01-13
### Added
- DDM coordinate format option
### Changed
- Video: use `media-kit` instead of `ffmpeg-kit` for metadata fetch
- Info: show video chapters
- Accessibility: apply system "touch and hold delay" setting
### Fixed
- crash when cataloguing some videos
- switching to PiP for any inactive app state
## <a id="v1.12.1"></a>[v1.12.1] - 2025-01-05
### Added
- dynamic album decompose action
- Danish translation (thanks Grooty12, Victor M, cat)
### Fixed
- analysis service not triggering because of uninitialized app lifecycle
- Viewer: displaying neighbour items when the initial item of a view intent is a new one
- Search: dynamic album name filtering
## <a id="v1.12.0"></a>[v1.12.0] - 2024-12-19
### Added
- Countries: show states for Mexico
- Estonian translation (thanks Priit Jõerüüt)
### Changed
- upgraded Flutter to stable v3.27.1
### Fixed
- crash when loading many new items on low memory devices
## <a id="v1.11.20"></a>[v1.11.20] - 2024-12-11
### Added
- Albums: dynamic albums from filter sets
- Bulgarian translation (thanks Petrov)
- Tamil translation (thanks தமிழ்நேரம்)
## <a id="v1.11.19"></a>[v1.11.19] - 2024-11-24
### Added
- integrate with OS app language settings on Android >=14
### Changed
- remember title filter visibility by page
## <a id="v1.11.18"></a>[v1.11.18] - 2024-11-18
### Changed
- Albums: improved album creation feedback
- upgraded Flutter to stable v3.24.5
### Fixed
- crash when playing video with DCL restriction enabled
- cataloguing images with wrong MPF offsets
- printing multi-page items containing some unprintable pages
- English (Shavian) locale tags for store listing
## <a id="v1.11.17"></a>[v1.11.17] - 2024-10-30
### Added
- Map: create shortcut to custom region and filters
- Video: frame stepping forward/backward
- Video: custom playback buttons
- English (Shavian) translation (thanks Paranoid Android)
### Changed
- upgraded Flutter to stable v3.24.4
### Fixed
- crash when loading large collection
- Viewer: copying content URI item
- Albums: creating album with same name as existing empty directory
- Privacy: tagging while vaults are unlocked does not yield recent tags visible when vaults are locked
## <a id="v1.11.16"></a>[v1.11.16] - 2024-10-10
### Fixed
- case-insensitive access to restricted directories
## <a id="v1.11.15"></a>[v1.11.15] - 2024-10-09
### Changed
- Enterprise: do not request `INTERACT_ACROSS_PROFILES` permission (Play Store compatibility)
## <a id="v1.11.14"></a>[v1.11.14] - 2024-10-09
### Added
- Map: OpenTopoMap raster layer
- Map: OSM Liberty vector layer (hosted by OSM Americana)
- Interoperability: receiving `geo:` URI generally opens map page at location
- Interoperability: receiving `geo:` URI when editing item location fills in coordinates
- Map basic app shortcut
- Enterprise: support for work profile switching from the drawer
- Settings: hidden path filters are merged with others and can be toggled
### Removed
- `Safe mode` basic app shortcut
### Fixed
- hanging when cataloguing some JPEG MPF images
- Apple HDR image detection
## <a id="v1.11.13"></a>[v1.11.13] - 2024-09-17
### Added
- support opening from the lock screen
### Changed
- upgraded Flutter to stable v3.24.3
### Fixed
- crash when cataloguing some malformed MP4 files
- inconsistent launch screen
## <a id="v1.11.12"></a>[v1.11.12] - 2024-09-16 [YANKED AGAIN!]
## <a id="v1.11.11"></a>[v1.11.11] - 2024-09-16 [YANKED]
## <a id="v1.11.10"></a>[v1.11.10] - 2024-09-01
### Added
- Swedish translation (thanks Shift18, Andreas Håll)
### Changed
- request notification permission when launching scanning service
- upgraded Flutter to stable v3.24.1
### Fixed
- duplicates from new item loading/refreshing
## <a id="v1.11.9"></a>[v1.11.9] - 2024-08-07
### Added
- Viewer: display more items in tag/copy/move quick action choosers
- Viewer: long descriptions are scrollable when overlay is expanded by tap
- Collection: sort by duration
- Map: open external map app from map views
- Explorer: stats
### Changed
- Accessibility: more animations and effects are suppressed when animations are disabled
- upgraded Flutter to stable v3.24.0
### Fixed
- opening app from launcher always showing home page
- collection quick actions not showing in the top bar nor the menu
- multiple widget setup after device reboot
## <a id="v1.11.8"></a>[v1.11.8] - 2024-07-19
### Added
- Explorer: set custom path as home
- Explorer: create shortcut to custom path
### Changed
- target Android 15 (API 35)
- upgraded Flutter to stable v3.22.3
### Fixed
- crash when cataloguing some PNG files
## <a id="v1.11.7"></a>[v1.11.7] - 2024-07-18 [YANKED AGAIN!]
## <a id="v1.11.6"></a>[v1.11.6] - 2024-07-17 [YANKED]
## <a id="v1.11.5"></a>[v1.11.5] - 2024-07-11
### Added
- Collection: stack RAW and JPEG with same file names
- Collection: ask to rename/replace/skip when converting items with name conflict
- Export: bulk converting motion photos to still images
- Explorer: view folder tree and filter paths
### Fixed
- switching to PiP when changing device orientation on Android >=13
- handling wallpaper intent without URI
- sizing widgets with some launchers on Android >=12
### Removed
- `huawei` app flavor
## <a id="v1.11.4"></a>[v1.11.4] - 2024-07-09 [YANKED]
## <a id="v1.11.3"></a>[v1.11.3] - 2024-06-17
### Added
- handle `MediaStore.ACTION_REVIEW` intent
## <a id="v1.11.2"></a>[v1.11.2] - 2024-06-11
### Added
- Albums / Countries / Tags: show selection in Collection
- allow shifting dates by seconds
### Changed
- opening app from launcher shows home page only when exited by back button
- Screen saver: black background, consistent with slideshow
- upgraded Flutter to stable v3.22.2
### Removed
- support for Android KitKat (API 19)
### Fixed
- crash when cataloguing large images
## <a id="v1.11.1"></a>[v1.11.1] - 2024-05-03
### Added
@ -1003,6 +1397,7 @@ All notable changes to this project will be documented in this file.
- app launching on some devices
- corrupting motion photo exif editing (e.g. rotation)
- accessing files in `Download` directory when not using reference case
## [v1.4.9] - 2021-08-20

View file

@ -12,9 +12,6 @@ Aves is a gallery and metadata explorer app. It is built for Android, with Flutt
[<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
alt='Get it on Google Play'
height="80">](https://play.google.com/store/apps/details?id=deckers.thibault.aves&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/huawei-appgallery-badge-english-black.png"
alt='Get it on Huawei AppGallery'
height="80">](https://appgallery.huawei.com/app/C106014023)
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
alt='Get it on IzzyOnDroid'
height="80">](https://apt.izzysoft.de/fdroid/index/apk/deckers.thibault.aves)
@ -38,7 +35,7 @@ It scans your media collection to identify **motion photos**, **panoramas** (aka
**Navigation and search** is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc.
Aves integrates with Android (from KitKat to Android 14, including Android TV) with features such as **widgets**, **app shortcuts**, **screen saver** and **global search** handling. It also works as a **media viewer and picker**.
Aves integrates with Android (including Android TV) with features such as **widgets**, **app shortcuts**, **screen saver** and **global search** handling. It also works as a **media viewer and picker**.
## Screenshots
@ -114,17 +111,96 @@ Some users have expressed the wish to financially support the project. Thanks!
## Project Setup
### Install dependencies
Before running or building the app, update the dependencies for the desired flavor:
```
# scripts/apply_flavor_play.sh
scripts/apply_flavor_play.sh
```
To build the project, create a file named `<app dir>/android/key.properties`. It should contain a reference to a keystore for app signing, and other necessary credentials. See [key_template.properties](https://github.com/deckerst/aves/blob/develop/android/key_template.properties) for the expected keys.
To run the app:
### To run the app:
```
# ./flutterw run -t lib/main_play.dart --flavor play
./flutterw run -t lib/main_play.dart --flavor play
```
### To build the app:
creare file con le tue credenziali file.keystore
dove YOUR_ALIAS_NAME è il tuo unico alias name
e YOUR_ALIAS_PWD è la password del tuo alias
```sh
keytool -genkey -v -keystore file.keystore -alias YOUR_ALIAS_NAME -storepass YOUR_ALIAS_PWD -keypass YOUR_ALIAS_PWD -keyalg RSA -validity 36500
```
in questo caso ho inserito
```sh
cd android
keytool -genkey -v -keystore file.keystore -alias FabioMich66 -storepass Master66 -keypass Master66 -keyalg RSA -validity 36500
```
se non puoi eseguire keytool perchè non è nel path di sistema cercalo usando
```sh
cd /
sudo find -name keytool
```
compilare il file `<app dir>/android/key.properties`
```
nano android/key.properties
```
questi i miei dati utilizzando il format key_template.properties
```
storeFile=/Users/fabio/flutter_apps/aves/android/file.keystore
storePassword=Master66
keyAlias=FabioMich66
keyPassword=Master66
googleApiKey=<GOOGLE_API_KEY>
```
infine compilare l'apk
```
./flutterw build apk -t lib/main_play.dart --flavor play
```
[Version badge]: https://img.shields.io/github/v/release/deckerst/aves?include_prereleases&sort=semver
[Build badge]: https://img.shields.io/github/actions/workflow/status/deckerst/aves/check.yml?branch=develop
[Build badge]: https://img.shields.io/github/actions/workflow/status/deckerst/aves/quality-check.yml?branch=develop
## Android studio
caricare il file da github selezionando le mnù a tendina File-New-project from Version Control
selezionare version control tipo: git
inserire URL di aves
https://github.com/deckerst/aves
flaggare shallow clone with history troncated 1 commits
aprire la console sulla dir aves appena creata e caricare le dipendenze
```
scripts/apply_flavor_izzy.sh
```
in settings - Languages and Framework - Dart inserire il path
```
/home/fabio/flutter/bin/cache/
```
e spuntare project aves
Edit configurations e aggiungere shell script con un nome x es izzi
poi flaggare script text e inserire
./flutterw run -t lib/main_izzy.dart --flavor izzy
la working directory sarà una cosa così
/home/fabio/StudioProjects/aves

View file

@ -9,6 +9,11 @@ analyzer:
# implicit-casts: false
# implicit-dynamic: false
# cf https://github.com/dart-lang/dart_style/wiki/Configuration
formatter:
page_width: 240
trailing_commas: preserve
linter:
rules:
# from 'flutter_lints', excluded
@ -36,3 +41,8 @@ linter:
prefer_single_quotes: true
sort_child_properties_last: true
unawaited_futures: true
# `const` related, included
prefer_const_constructors: true
prefer_const_literals_to_create_immutables: true
prefer_const_declarations: true

5
android/.gitignore vendored
View file

@ -5,9 +5,12 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
.kotlin/
/build/
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View file

@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id 'com.android.application'
id 'com.google.devtools.ksp'
@ -10,18 +8,6 @@ plugins {
def packageName = "deckers.thibault.aves"
// Flutter properties
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionName = localProperties.getProperty('flutter.versionName')
// Keys
def keystoreProperties = new Properties()
@ -40,41 +26,23 @@ if (keystorePropertiesFile.exists()) {
keystoreProperties["keyAlias"] = System.getenv("AVES_KEY_ALIAS") ?: "<NONE>"
keystoreProperties["keyPassword"] = System.getenv("AVES_KEY_PASSWORD") ?: "<NONE>"
keystoreProperties["googleApiKey"] = System.getenv("AVES_GOOGLE_API_KEY") ?: "<NONE>"
keystoreProperties["huaweiApiKey"] = System.getenv("AVES_HUAWEI_API_KEY") ?: "<NONE>"
}
kotlin {
jvmToolchain 17
}
android {
namespace 'deckers.thibault.aves'
compileSdk 34
// cf https://developer.android.com/studio/projects/install-ndk#default-ndk-per-agp
ndkVersion '26.1.10909125'
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
lint {
checkAllWarnings true
warningsAsErrors true
disable 'InvalidPackage'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
namespace = 'deckers.thibault.aves'
compileSdk = 36
defaultConfig {
applicationId packageName
// minSdk constraints:
// - Flutter & other plugins: 19 (cf `flutter.minSdkVersion`)
// - google_maps_flutter v2.1.1: 20
minSdk flutter.minSdkVersion
targetSdk 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>",
huaweiApiKey: keystoreProperties["huaweiApiKey"] ?: "<NONE>"]
targetSdk 36
versionCode flutter.versionCode
versionName flutter.versionName
manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>"]
multiDexEnabled true
}
@ -97,13 +65,6 @@ android {
ext.useNdkAbiFilters = true
}
huawei {
// Huawei AppGallery
dimension "store"
// generate a universal APK without x86 native libs
ext.useNdkAbiFilters = true
}
izzy {
// IzzyOnDroid
// check offending libraries with `scanapk`
@ -167,29 +128,20 @@ android {
}
}
tasks.withType(KotlinCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
jvmToolchain(17)
}
flutter {
source '../..'
}
repositories {
maven {
url 'https://jitpack.io'
url = 'https://jitpack.io'
content {
includeGroup "com.github.deckerst"
includeGroup "com.github.deckerst.mp4parser"
}
}
maven {
url 'https://s3.amazonaws.com/repo.commonsware.com'
url = 'https://s3.amazonaws.com/repo.commonsware.com'
content {
excludeGroupByRegex "com\\.github\\.deckerst.*"
}
@ -197,39 +149,38 @@ repositories {
}
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2'
implementation "androidx.appcompat:appcompat:1.6.1"
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
implementation "androidx.appcompat:appcompat:1.7.1"
implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.lifecycle:lifecycle-process:2.9.1'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'androidx.work:work-runtime-ktx:2.9.0'
implementation 'androidx.security:security-crypto:1.1.0-beta01'
implementation 'androidx.work:work-runtime-ktx:2.10.1'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.commonsware.cwac:document:0.5.0'
implementation 'com.drewnoakes:metadata-extractor:2.19.0'
implementation "com.github.bumptech.glide:glide:$glide_version"
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.android.material:material:1.12.0'
// SLF4J implementation for `mp4parser`
implementation 'org.slf4j:slf4j-simple:2.0.12'
implementation 'org.slf4j:slf4j-simple:2.0.17'
// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
// - https://jitpack.io/p/deckerst/androidsvg
// - https://jitpack.io/p/deckerst/mp4parser
// - https://jitpack.io/p/deckerst/pixymeta-android
implementation 'com.github.deckerst:Android-TiffBitmapFactory:90c06eebf4'
implementation 'com.github.deckerst.mp4parser:isoparser:4cc0c5d06c'
implementation 'com.github.deckerst.mp4parser:muxer:4cc0c5d06c'
implementation 'com.github.deckerst:pixymeta-android:9ec7097f17'
implementation 'com.github.deckerst:Android-TiffBitmapFactory:d6b2b0aa4f'
implementation 'com.github.deckerst:androidsvg:67db933051'
implementation 'com.github.deckerst.mp4parser:isoparser:c2898f1832'
implementation 'com.github.deckerst.mp4parser:muxer:c2898f1832'
implementation 'com.github.deckerst:pixymeta-android:cb1cdc932e'
implementation project(':exifinterface')
// huawei flavor only
huaweiImplementation "com.huawei.agconnect:agconnect-core:$huawei_agconnect_version"
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.13.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
kapt 'androidx.annotation:annotation:1.7.1'
kapt 'androidx.annotation:annotation:1.9.1'
ksp "com.github.bumptech.glide:ksp:$glide_version"
compileOnly rootProject.findProject(':streams_channel')
@ -240,8 +191,3 @@ if (useCrashlytics) {
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
}
if (useHms) {
println("Building flavor with HMS plugin")
apply plugin: 'com.huawei.agconnect'
}

View file

@ -14,10 +14,6 @@
android:name="android.software.leanback"
android:required="false" />
<!--
TODO TLAD [Android 14 (API 34)] request/handle READ_MEDIA_VISUAL_USER_SELECTED permission
cf https://developer.android.com/about/versions/14/changes/partial-photo-video-access
-->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
@ -35,10 +31,13 @@
<!-- to access media with original metadata with scoped storage (API >=29) -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- to provide a foreground service type, as required by Android 14 (API 34) -->
<!-- TODO TLAD [Android 15 (API 35)] use `FOREGROUND_SERVICE_MEDIA_PROCESSING` -->
<!-- to provide a foreground service type, as required from Android 14 (API 34) -->
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
android:maxSdkVersion="34"
tools:ignore="SystemPermissionTypo" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING"
tools:ignore="SystemPermissionTypo" />
<!-- TODO TLAD still needed to fetch map tiles / reverse geocoding / else ? check in release mode -->
<uses-permission android:name="android.permission.INTERNET" />
@ -72,14 +71,12 @@
-->
<!--
allow install on API 19, despite the `minSdk` declared in dependencies:
- the Security library is from API 21
allow install on API 21, despite the `minSdk` declared in dependencies:
- FFmpegKit for Flutter is from API 24 (when not LTS)
- Google Maps is from API 20
-->
<uses-sdk tools:overrideLibrary="androidx.security, com.arthenica.ffmpegkit.flutter, io.flutter.plugins.googlemaps" />
<uses-sdk tools:overrideLibrary="com.arthenica.ffmpegkit.flutter" />
<!-- from Android 11, we should define <queries> to make other apps visible to this app -->
<!-- from Android 11 (API 30), we should define <queries> to make other apps visible to this app -->
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
@ -95,7 +92,7 @@
<data android:mimeType="video/*" />
</intent>
<!--
from Android 11, `url_launcher` method `canLaunchUrl()` will return false,
from Android 11 (API 30), `url_launcher` method `canLaunchUrl()` will return false,
if appropriate intents are not declared, cf https://pub.dev/packages/url_launcher#configuration=
-->
<!-- to open https URLs -->
@ -106,7 +103,7 @@
</queries>
<!--
as of Flutter v3.16.0, predictive back gesture does not work
as of Flutter v3.22.2, predictive back gesture does not work
as expected when extending `FlutterFragmentActivity`
so we disable `enableOnBackInvokedCallback`
-->
@ -144,6 +141,8 @@
<action android:name="android.intent.action.PICK" />
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<action android:name="android.provider.action.REVIEW" />
<action android:name="android.provider.action.REVIEW_SECURE" />
<action android:name="com.android.camera.action.REVIEW" />
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
@ -163,6 +162,8 @@
<action android:name="android.intent.action.PICK" />
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<action android:name="android.provider.action.REVIEW" />
<action android:name="android.provider.action.REVIEW_SECURE" />
<action android:name="com.android.camera.action.REVIEW" />
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
@ -178,6 +179,13 @@
<data android:scheme="content" />
<data android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="geo" />
</intent-filter>
<!--
<intent-filter>
<action android:name="android.intent.action.EDIT" />
@ -258,11 +266,14 @@
</intent-filter>
</receiver>
<!-- anonymous service for analysis worker is specified here to provide service type -->
<!-- TODO TLAD [Android 15 (API 35)] use `mediaProcessing` -->
<!--
anonymous service for analysis worker is specified here to provide service type:
- `dataSync` for Android 14 (API 34)
- `mediaProcessing` from Android 15 (API 35)
-->
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
android:foregroundServiceType="dataSync|mediaProcessing"
tools:node="merge" />
<service
@ -310,9 +321,6 @@
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${googleApiKey}" />
<meta-data
android:name="deckers.thibault.aves.huawei.API_KEY"
android:value="${huaweiApiKey}" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
@ -321,8 +329,6 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- as of Flutter v3.19.0 (stable),
Impeller fails to render videos & platform views, has poor performance -->
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />

View file

@ -14,6 +14,7 @@ import androidx.work.ForegroundInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.GeocodingHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
@ -27,6 +28,10 @@ import deckers.thibault.aves.utils.LogUtils
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -34,13 +39,18 @@ import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class AnalysisWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {
private val defaultScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private var workCont: Continuation<Any?>? = null
private var flutterEngine: FlutterEngine? = null
private var backgroundChannel: MethodChannel? = null
override suspend fun doWork(): Result {
createNotificationChannel()
setForeground(createForegroundInfo())
Log.i(LOG_TAG, "Start analysis worker $id")
defaultScope.launch {
// prevent ANR triggered by slow operations in main thread
createNotificationChannel()
setForeground(createForegroundInfo())
}.join()
suspendCoroutine { cont ->
workCont = cont
onStart()
@ -60,9 +70,8 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
}
private fun onStart() {
Log.i(LOG_TAG, "Start analysis worker $id")
runBlocking {
FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, PREF_CALLBACK_HANDLE_KEY) {
flutterEngine = it
}
}
@ -70,14 +79,15 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
try {
initChannels(applicationContext)
val preferences = applicationContext.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
val entryIdStrings = preferences.getStringSet(PREF_ENTRY_IDS_KEY, null)
runBlocking {
FlutterUtils.runOnUiThread {
backgroundChannel?.invokeMethod(
"start", hashMapOf(
"entryIds" to inputData.getIntArray(KEY_ENTRY_IDS)?.toList(),
"entryIds" to entryIdStrings?.map { Integer.parseUnsignedInt(it) }?.toList(),
"force" to inputData.getBoolean(KEY_FORCE, false),
"progressTotal" to inputData.getInt(KEY_PROGRESS_TOTAL, 0),
"progressOffset" to inputData.getInt(KEY_PROGRESS_OFFSET, 0),
)
)
}
@ -123,12 +133,7 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
result.success(null)
}
"updateNotification" -> {
val title = call.argument<String>("title")
val message = call.argument<String>("message")
setForegroundAsync(createForegroundInfo(title, message))
result.success(null)
}
"updateNotification" -> defaultScope.launch { safeSuspend(call, result, ::updateNotification) }
"stop" -> {
workCont?.resume(null)
@ -161,41 +166,42 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
applicationContext.getString(R.string.analysis_notification_action_stop),
WorkManager.getInstance(applicationContext).createCancelPendingIntent(id)
).build()
val icon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) R.drawable.ic_notification else R.mipmap.ic_launcher_round
val contentTitle = title ?: applicationContext.getText(R.string.analysis_notification_default_title)
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL)
.setContentTitle(contentTitle)
.setTicker(contentTitle)
.setContentText(message)
.setSmallIcon(icon)
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
.setContentIntent(openAppIntent)
.addAction(stopAction)
.build()
return if (Build.VERSION.SDK_INT >= 34) {
// from Android 14 (API 34), foreground service type is mandatory
// despite the sample code omitting it at:
// https://developer.android.com/guide/background/persistent/how-to/long-running
// TODO TLAD [Android 15 (API 35)] use `FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING`
val type = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
ForegroundInfo(NOTIFICATION_ID, notification, type)
} else {
ForegroundInfo(NOTIFICATION_ID, notification)
// from Android 14 (API 34), foreground service type is mandatory for long-running workers:
// https://developer.android.com/guide/background/persistent/how-to/long-running
return when {
Build.VERSION.SDK_INT >= 35 -> ForegroundInfo(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
Build.VERSION.SDK_INT == 34 -> ForegroundInfo(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
else -> ForegroundInfo(NOTIFICATION_ID, notification)
}
}
private suspend fun updateNotification(call: MethodCall, result: MethodChannel.Result) {
val title = call.argument<String>("title")
val message = call.argument<String>("message")
setForeground(createForegroundInfo(title, message))
result.success(null)
}
companion object {
private val LOG_TAG = LogUtils.createTag<AnalysisWorker>()
private const val BACKGROUND_CHANNEL = "deckers.thibault/aves/analysis_service_background"
const val SHARED_PREFERENCES_KEY = "analysis_service"
const val CALLBACK_HANDLE_KEY = "callback_handle"
const val PREF_CALLBACK_HANDLE_KEY = "callback_handle"
const val PREF_ENTRY_IDS_KEY = "entry_ids"
const val NOTIFICATION_CHANNEL = "analysis"
const val NOTIFICATION_ID = 1
const val KEY_ENTRY_IDS = "entry_ids"
const val KEY_FORCE = "force"
const val KEY_PROGRESS_TOTAL = "progress_total"
const val KEY_PROGRESS_OFFSET = "progress_offset"
}
}

View file

@ -4,8 +4,6 @@ import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
@ -13,9 +11,6 @@ class HomeWidgetSettingsActivity : MainActivity() {
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
public override fun onCreate(savedInstanceState: Bundle?) {
if (FlutterUtils.isSoftwareRenderingRequired()) {
intent.enableSoftwareRendering()
}
super.onCreate(savedInstanceState)
// cancel if user does not complete widget setup

View file

@ -8,14 +8,22 @@ import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.SizeF
import android.widget.RemoteViews
import androidx.core.graphics.createBitmap
import androidx.core.net.toUri
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.model.FieldMap
@ -25,8 +33,14 @@ import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@ -40,12 +54,15 @@ class HomeWidgetProvider : AppWidgetProvider() {
for (widgetId in appWidgetIds) {
val widgetInfo = appWidgetManager.getAppWidgetOptions(widgetId)
val pendingResult = goAsync()
defaultScope.launch {
val backgroundProps = getProps(context, widgetId, widgetInfo, drawEntryImage = false)
updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, backgroundProps)
updateWidgetImage(context, appWidgetManager, widgetId, backgroundProps)
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false)
updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
pendingResult?.finish()
}
}
}
@ -61,20 +78,32 @@ class HomeWidgetProvider : AppWidgetProvider() {
imageByteFetchJob = defaultScope.launch {
delay(500)
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = true)
updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
}
}
private fun getDevicePixelRatio(): Float = Resources.getSystem().displayMetrics.density
private fun getWidgetSizePx(context: Context, widgetInfo: Bundle): Pair<Int, Int> {
val devicePixelRatio = getDevicePixelRatio()
val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
val widthPx = (widgetInfo.getInt(widthKey) * devicePixelRatio).roundToInt()
val heightPx = (widgetInfo.getInt(heightKey) * devicePixelRatio).roundToInt()
return Pair(widthPx, heightPx)
private fun getWidgetSizesDip(context: Context, widgetInfo: Bundle): List<SizeF> {
var sizes: List<SizeF>? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, SizeF::class.java)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@Suppress("DEPRECATION")
widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES)
} else {
null
}
if (sizes.isNullOrEmpty()) {
val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
val widthDip = widgetInfo.getInt(widthKey)
val heightDip = widgetInfo.getInt(heightKey)
sizes = listOf(SizeF(widthDip.toFloat(), heightDip.toFloat()))
}
return sizes
}
private suspend fun getProps(
@ -84,89 +113,155 @@ class HomeWidgetProvider : AppWidgetProvider() {
drawEntryImage: Boolean,
reuseEntry: Boolean = false,
): FieldMap? {
val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
if (widthPx == 0 || heightPx == 0) return null
val sizesDip = getWidgetSizesDip(context, widgetInfo)
if (sizesDip.isEmpty()) return null
val sizeDip = sizesDip.first()
if (sizeDip.width == 0f || sizeDip.height == 0f) return null
val sizesDipMap = sizesDip.map { size -> hashMapOf("widthDip" to size.width, "heightDip" to size.height) }
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
val params = hashMapOf(
"widgetId" to widgetId,
"sizesDip" to sizesDipMap,
"devicePixelRatio" to getDevicePixelRatio(),
"drawEntryImage" to drawEntryImage,
"reuseEntry" to reuseEntry,
"isSystemThemeDark" to isNightModeOn,
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius))
}
}
initFlutterEngine(context)
val messenger = flutterEngine!!.dartExecutor
val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
try {
val props = suspendCoroutine<Any?> { cont ->
val props = suspendCoroutine { cont ->
defaultScope.launch {
FlutterUtils.runOnUiThread {
channel.invokeMethod("drawWidget", hashMapOf(
"widgetId" to widgetId,
"widthPx" to widthPx,
"heightPx" to heightPx,
"devicePixelRatio" to getDevicePixelRatio(),
"drawEntryImage" to drawEntryImage,
"reuseEntry" to reuseEntry,
"isSystemThemeDark" to isNightModeOn,
), object : MethodChannel.Result {
override fun success(result: Any?) {
cont.resume(result)
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
}
override fun notImplemented() {
cont.resumeWithException(Exception("not implemented"))
}
})
tryDrawWidget(params, cont, 0)
}
}
}
@Suppress("unchecked_cast")
return props as FieldMap?
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId widthPx=$widthPx heightPx=$heightPx", e)
Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId sizesPx=$sizesDip", e)
}
return null
}
private fun tryDrawWidget(params: HashMap<String, Any>, cont: Continuation<Any?>, drawRetry: Int) {
val messenger = flutterEngine!!.dartExecutor
val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
channel.invokeMethod("drawWidget", params, object : MethodChannel.Result {
override fun success(result: Any?) {
cont.resume(result)
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
}
override fun notImplemented() {
if (drawRetry > DRAW_RETRY_MAX) {
cont.resumeWithException(Exception("not implemented"))
} else {
Handler(Looper.getMainLooper()).postDelayed({
tryDrawWidget(params, cont, drawRetry + 1)
}, 2000L)
}
}
})
}
private fun updateWidgetImage(
context: Context,
appWidgetManager: AppWidgetManager,
widgetId: Int,
widgetInfo: Bundle,
props: FieldMap?,
) {
props ?: return
val bytes = props["bytes"] as ByteArray?
val bytesBySizeDip = (props["bytesBySizeDip"] as List<*>?)?.mapNotNull {
if (it is Map<*, *>) {
val widthDip = (it["widthDip"] as Number?)?.toFloat()
val heightDip = (it["heightDip"] as Number?)?.toFloat()
val bytes = it["bytes"] as ByteArray?
if (widthDip != null && heightDip != null && bytes != null) {
Pair(SizeF(widthDip, heightDip), bytes)
} else null
} else null
}
val updateOnTap = props["updateOnTap"] as Boolean?
if (bytes == null || updateOnTap == null) {
if (bytesBySizeDip == null || updateOnTap == null) {
Log.e(LOG_TAG, "missing arguments")
return
}
val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
if (widthPx == 0 || heightPx == 0) return
if (bytesBySizeDip.isEmpty()) {
Log.e(LOG_TAG, "empty image list")
return
}
val bitmaps = ArrayList<Bitmap>()
fun createRemoteViewsForSize(
context: Context,
widgetId: Int,
sizeDip: SizeF,
bytes: ByteArray,
updateOnTap: Boolean,
): RemoteViews? {
val devicePixelRatio = getDevicePixelRatio()
val widthPx = (sizeDip.width * devicePixelRatio).roundToInt()
val heightPx = (sizeDip.height * devicePixelRatio).roundToInt()
try {
val bitmap = createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888).also {
bitmaps.add(it)
it.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
}
val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
return RemoteViews(context.packageName, R.layout.app_widget).apply {
setImageViewBitmap(R.id.widget_img, bitmap)
setOnClickPendingIntent(R.id.widget_img, pendingIntent)
}
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to draw widget", e)
}
return null
}
try {
val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
val views = RemoteViews(context.packageName, R.layout.app_widget).apply {
setImageViewBitmap(R.id.widget_img, bitmap)
setOnClickPendingIntent(R.id.widget_img, pendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// multiple rendering for all possible sizes
val views = RemoteViews(
bytesBySizeDip.associateBy(
{ (sizeDip, _) -> sizeDip },
{ (sizeDip, bytes) -> createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap) },
).filterValues { it != null }.mapValues { (_, view) -> view!! }
)
appWidgetManager.updateAppWidget(widgetId, views)
} else {
// single rendering
val (sizeDip, bytes) = bytesBySizeDip.first()
val views = createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap)
appWidgetManager.updateAppWidget(widgetId, views)
}
appWidgetManager.updateAppWidget(widgetId, views)
bitmap.recycle()
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to draw widget", e)
} finally {
bitmaps.forEach { it.recycle() }
bitmaps.clear()
}
}
private fun buildUpdateIntent(context: Context, widgetId: Int): PendingIntent {
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, Uri.parse("widget://$widgetId"), context, HomeWidgetProvider::class.java)
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, "widget://$widgetId".toUri(), context, HomeWidgetProvider::class.java)
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(widgetId))
return PendingIntent.getBroadcast(
@ -183,7 +278,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
private fun buildOpenAppIntent(context: Context, widgetId: Int): PendingIntent {
// set a unique URI to prevent the intent (and its extras) from being shared by different widgets
val intent = Intent(MainActivity.INTENT_ACTION_WIDGET_OPEN, Uri.parse("widget://$widgetId"), context, MainActivity::class.java)
val intent = Intent(MainActivity.INTENT_ACTION_WIDGET_OPEN, "widget://$widgetId".toUri(), context, MainActivity::class.java)
.putExtra(MainActivity.EXTRA_KEY_WIDGET_ID, widgetId)
return PendingIntent.getActivity(
@ -202,6 +297,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
private val LOG_TAG = LogUtils.createTag<HomeWidgetProvider>()
private const val WIDGET_DART_ENTRYPOINT = "widgetMain"
private const val WIDGET_DRAW_CHANNEL = "deckers.thibault/aves/widget_draw"
private const val DRAW_RETRY_MAX = 5
private var flutterEngine: FlutterEngine? = null
private var imageByteFetchJob: Job? = null

View file

@ -1,30 +1,63 @@
package deckers.thibault.aves
import android.annotation.SuppressLint
import android.app.KeyguardManager
import android.app.SearchManager
import android.appwidget.AppWidgetManager
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.TransactionTooLargeException
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.net.toUri
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.AccessibilityHandler
import deckers.thibault.aves.channel.calls.AnalysisHandler
import deckers.thibault.aves.channel.calls.AppAdapterHandler
import deckers.thibault.aves.channel.calls.AppProfileHandler
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.DebugHandler
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
import deckers.thibault.aves.channel.calls.GeocodingHandler
import deckers.thibault.aves.channel.calls.GlobalSearchHandler
import deckers.thibault.aves.channel.calls.HomeWidgetHandler
import deckers.thibault.aves.channel.calls.MediaEditHandler
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
import deckers.thibault.aves.channel.calls.MediaSessionHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
import deckers.thibault.aves.channel.calls.MetadataEditHandler
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
import deckers.thibault.aves.channel.calls.SecurityHandler
import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.calls.WallpaperHandler
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.*
import deckers.thibault.aves.channel.streams.ActivityResultStreamHandler
import deckers.thibault.aves.channel.streams.AnalysisStreamHandler
import deckers.thibault.aves.channel.streams.ErrorStreamHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.ImageOpStreamHandler
import deckers.thibault.aves.channel.streams.IntentStreamHandler
import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreChangeStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.channel.streams.SettingsChangeStreamHandler
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import deckers.thibault.aves.utils.FlutterUtils.isSoftwareRenderingRequired
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.anyCauseIs
import deckers.thibault.aves.utils.getParcelableExtraCompat
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
@ -38,6 +71,7 @@ import kotlinx.coroutines.launch
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
// `FlutterFragmentActivity` because of local auth plugin
open class MainActivity : FlutterFragmentActivity() {
private val defaultScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
@ -52,13 +86,6 @@ open class MainActivity : FlutterFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(LOG_TAG, "onCreate intent=$intent")
if (isSoftwareRenderingRequired()) {
intent.enableSoftwareRendering()
// running the app from Android Studio automatically adds to the intent the `start-paused` flag
// so the IDE can connect to the app, but launching on KitKat emulators fails because of a timeout
intent.removeExtra("start-paused")
}
intent.extras?.takeUnless { it.isEmpty }?.let {
Log.i(LOG_TAG, "onCreate intent extras=$it")
}
@ -117,7 +144,9 @@ open class MainActivity : FlutterFragmentActivity() {
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
MethodChannel(messenger, MediaEditHandler.CHANNEL).setMethodCallHandler(MediaEditHandler(this))
MethodChannel(messenger, MetadataEditHandler.CHANNEL).setMethodCallHandler(MetadataEditHandler(this))
MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
// - need Activity
MethodChannel(messenger, AppProfileHandler.CHANNEL).setMethodCallHandler(AppProfileHandler(this))
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
// result streaming: dart -> platform ->->-> dart
@ -150,7 +179,7 @@ open class MainActivity : FlutterFragmentActivity() {
intentDataMap.clear()
}
"submitPickedItems" -> submitPickedItems(call)
"submitPickedItems" -> safe(call, result, ::submitPickedItems)
"submitPickedCollectionFilters" -> submitPickedCollectionFilters(call)
}
}
@ -168,11 +197,9 @@ open class MainActivity : FlutterFragmentActivity() {
// as of Flutter v3.0.1, the window `viewInsets` and `viewPadding`
// are incorrect on startup in some environments (e.g. API 29 emulator),
// so we manually request to apply the insets to update the window metrics
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
Handler(Looper.getMainLooper()).postDelayed({
window.decorView.requestApplyInsets()
}, 100)
}
Handler(Looper.getMainLooper()).postDelayed({
window.decorView.requestApplyInsets()
}, 100)
}
override fun onStop() {
@ -208,6 +235,7 @@ open class MainActivity : FlutterFragmentActivity() {
intentStreamHandler.notifyNewIntent(extractIntentData(intent))
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
@ -270,30 +298,62 @@ open class MainActivity : FlutterFragmentActivity() {
open fun extractIntentData(intent: Intent?): FieldMap {
when (val action = intent?.action) {
Intent.ACTION_MAIN -> {
val fields = hashMapOf<String, Any?>(
INTENT_DATA_KEY_LAUNCHER to intent.hasCategory(Intent.CATEGORY_LAUNCHER),
INTENT_DATA_KEY_SAFE_MODE to intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false),
return hashMapOf(
INTENT_DATA_KEY_PAGE to intent.getStringExtra(EXTRA_KEY_PAGE),
INTENT_DATA_KEY_FILTERS to extractFiltersFromIntent(intent),
INTENT_DATA_KEY_EXPLORER_PATH to intent.getStringExtra(EXTRA_KEY_EXPLORER_PATH),
)
intent.getStringExtra(EXTRA_KEY_PAGE)?.let { page ->
val filters = extractFiltersFromIntent(intent)
fields[INTENT_DATA_KEY_PAGE] = page
fields[INTENT_DATA_KEY_FILTERS] = filters
}
return fields
}
Intent.ACTION_VIEW,
Intent.ACTION_SEND,
MediaStore.ACTION_REVIEW,
MediaStore.ACTION_REVIEW_SECURE,
"com.android.camera.action.REVIEW",
"com.android.camera.action.SPLIT_SCREEN_REVIEW" -> {
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
if (uri.scheme == "geo") {
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW_GEO,
INTENT_DATA_KEY_URI to uri.toString(),
INTENT_DATA_KEY_FILTERS to extractFiltersFromIntent(intent),
)
}
// MIME type is optional
val type = intent.type ?: intent.resolveType(this)
return hashMapOf(
val fields = hashMapOf<String, Any?>(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW,
INTENT_DATA_KEY_MIME_TYPE to type,
INTENT_DATA_KEY_URI to uri.toString(),
)
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
val isLocked = keyguardManager.isKeyguardLocked
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(isLocked)
}
if (isLocked) {
// device is locked, so access to content is limited to intent URI by default
fields[INTENT_DATA_KEY_SECURE_URIS] = listOf(uri.toString())
}
if (action == MediaStore.ACTION_REVIEW_SECURE) {
val uris = ArrayList<String>()
intent.clipData?.let { clipData ->
for (i in 0 until clipData.itemCount) {
clipData.getItemAt(i).uri?.let { uris.add(it.toString()) }
}
}
if (uris.isNotEmpty()) {
fields[INTENT_DATA_KEY_SECURE_URIS] = uris
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && intent.hasExtra(MediaStore.EXTRA_BRIGHTNESS)) {
fields[INTENT_DATA_KEY_BRIGHTNESS] = intent.getFloatExtra(MediaStore.EXTRA_BRIGHTNESS, 0f)
}
return fields
}
}
@ -313,7 +373,8 @@ open class MainActivity : FlutterFragmentActivity() {
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_PICK_ITEMS,
INTENT_DATA_KEY_MIME_TYPE to intent.type,
INTENT_DATA_KEY_ALLOW_MULTIPLE to (intent.extras?.getBoolean(Intent.EXTRA_ALLOW_MULTIPLE) ?: false),
INTENT_DATA_KEY_MIME_TYPES to intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES)?.toList(),
INTENT_DATA_KEY_ALLOW_MULTIPLE to intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false),
)
}
@ -373,28 +434,54 @@ open class MainActivity : FlutterFragmentActivity() {
return null
}
private fun submitPickedItems(call: MethodCall) {
open fun submitPickedItems(call: MethodCall, result: MethodChannel.Result) {
val pickedUris = call.argument<List<String>>("uris")
if (!pickedUris.isNullOrEmpty()) {
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
val intent = Intent().apply {
val firstUri = toUri(pickedUris.first())
if (pickedUris.size == 1) {
data = firstUri
} else {
clipData = ClipData.newUri(contentResolver, null, firstUri).apply {
pickedUris.drop(1).forEach {
addItem(ClipData.Item(toUri(it)))
}
if (pickedUris.isNullOrEmpty()) {
setResult(RESULT_CANCELED)
// move code triggering `Binder` call off the main thread
defaultScope.launch { finish() }
return
}
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this@MainActivity, uriString.toUri()) }
val intent = Intent().apply {
val firstUri = toUri(pickedUris.first())
if (pickedUris.size == 1) {
data = firstUri
} else {
clipData = ClipData.newUri(contentResolver, null, firstUri).apply {
pickedUris.drop(1).forEach {
addItem(ClipData.Item(toUri(it)))
}
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
setResult(RESULT_OK, intent)
} else {
setResult(RESULT_CANCELED)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
// move code triggering `Binder` call off the main thread
defaultScope.launch {
submitPickedItemsIntent(intent, result)
}
}
private fun submitPickedItemsIntent(intent: Intent, result: MethodChannel.Result) {
try {
setResult(RESULT_OK, intent)
finish()
} catch (e: Exception) {
setResult(RESULT_CANCELED)
if (e is SecurityException && intent.flags and Intent.FLAG_GRANT_WRITE_URI_PERMISSION != 0) {
// in some environments, providing the write flag yields a `SecurityException`:
// "UID XXXX does not have permission to content://XXXX"
// so we retry without it
Log.i(LOG_TAG, "retry submitting picked items without FLAG_GRANT_WRITE_URI_PERMISSION")
intent.flags = intent.flags and Intent.FLAG_GRANT_WRITE_URI_PERMISSION.inv()
submitPickedItemsIntent(intent, result)
} else if (e.anyCauseIs<TransactionTooLargeException>()) {
result.error("submitPickedItems-large", "transaction too large with ${intent.clipData?.itemCount} URIs", e)
} else {
result.error("submitPickedItems-exception", "failed to pick ${intent.clipData?.itemCount} URIs", e)
}
}
finish()
}
private fun submitPickedCollectionFilters(call: MethodCall) {
@ -423,7 +510,16 @@ open class MainActivity : FlutterFragmentActivity() {
.setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_search else R.drawable.ic_shortcut_search))
.setIntent(
Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, "/search")
.putExtra(EXTRA_KEY_PAGE, SEARCH_PAGE_ROUTE_NAME)
)
.build()
val map = ShortcutInfoCompat.Builder(this, "map")
.setShortLabel(getString(R.string.map_shortcut_short_label))
.setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_map else R.drawable.ic_shortcut_map))
.setIntent(
Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, MAP_PAGE_ROUTE_NAME)
)
.build()
@ -432,21 +528,12 @@ open class MainActivity : FlutterFragmentActivity() {
.setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_movie else R.drawable.ic_shortcut_movie))
.setIntent(
Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, "/collection")
.putExtra(EXTRA_KEY_PAGE, COLLECTION_PAGE_ROUTE_NAME)
.putExtra("filters", arrayOf("{\"type\":\"mime\",\"mime\":\"video/*\"}"))
)
.build()
val safeMode = ShortcutInfoCompat.Builder(this, "safeMode")
.setShortLabel(getString(R.string.safe_mode_shortcut_short_label))
.setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_safe_mode else R.drawable.ic_shortcut_safe_mode))
.setIntent(
Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java)
.putExtra(EXTRA_KEY_SAFE_MODE, true)
)
.build()
val shortcutInfoList = listOf(videos, search, safeMode)
val shortcutInfoList = listOf(videos, search, map)
ShortcutManagerCompat.setDynamicShortcuts(this, shortcutInfoList)
Log.i(LOG_TAG, "set shortcuts: ${shortcutInfoList.joinToString(", ") { v -> v.id }}")
}
@ -476,26 +563,36 @@ open class MainActivity : FlutterFragmentActivity() {
const val INTENT_ACTION_SEARCH = "search"
const val INTENT_ACTION_SET_WALLPAPER = "set_wallpaper"
const val INTENT_ACTION_VIEW = "view"
const val INTENT_ACTION_VIEW_GEO = "view_geo"
const val INTENT_ACTION_WIDGET_OPEN = "widget_open"
const val INTENT_ACTION_WIDGET_SETTINGS = "widget_settings"
const val INTENT_DATA_KEY_ACTION = "action"
const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple"
const val INTENT_DATA_KEY_BRIGHTNESS = "brightness"
const val INTENT_DATA_KEY_EXPLORER_PATH = "explorerPath"
const val INTENT_DATA_KEY_FILTERS = "filters"
const val INTENT_DATA_KEY_LAUNCHER = "launcher"
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
const val INTENT_DATA_KEY_MIME_TYPES = "mimeTypes"
const val INTENT_DATA_KEY_PAGE = "page"
const val INTENT_DATA_KEY_QUERY = "query"
const val INTENT_DATA_KEY_SAFE_MODE = "safeMode"
const val INTENT_DATA_KEY_SECURE_URIS = "secureUris"
const val INTENT_DATA_KEY_URI = "uri"
const val INTENT_DATA_KEY_WIDGET_ID = "widgetId"
const val EXTRA_KEY_PAGE = "page"
const val EXTRA_KEY_EXPLORER_PATH = "explorerPath"
const val EXTRA_KEY_FILTERS_ARRAY = "filters"
const val EXTRA_KEY_FILTERS_STRING = "filtersString"
const val EXTRA_KEY_SAFE_MODE = "safeMode"
const val EXTRA_KEY_WIDGET_ID = "widgetId"
// dart page routes
const val COLLECTION_PAGE_ROUTE_NAME = "/collection"
const val ENTRY_VIEWER_PAGE_ROUTE_NAME = "/viewer"
const val EXPLORER_PAGE_ROUTE_NAME = "/explorer"
const val MAP_PAGE_ROUTE_NAME = "/map"
const val SEARCH_PAGE_ROUTE_NAME = "/search"
// request code to pending runnable
val pendingStorageAccessResultHandlers = ConcurrentHashMap<Int, PendingStorageAccessResultHandler>()

View file

@ -5,10 +5,19 @@ import android.util.Log
import android.view.View
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.AccessibilityHandler
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
import deckers.thibault.aves.channel.calls.MediaSessionHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.calls.window.ServiceWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.utils.LogUtils
import io.flutter.FlutterInjector
@ -18,12 +27,14 @@ import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
// for FlutterView-level integration, cf https://docs.flutter.dev/development/add-to-app/android/add-flutter-view
class ScreenSaverService : DreamService() {
private var flutterEngine: FlutterEngine? = null
private var flutterView: FlutterView? = null
private lateinit var mediaSessionHandler: MediaSessionHandler
override fun onAttachedToWindow() {
Log.i(LOG_TAG, "onAttachedToWindow")
@ -77,6 +88,7 @@ class ScreenSaverService : DreamService() {
private fun release() {
destroyView()
mediaSessionHandler.dispose()
flutterEngine = null
flutterView = null
}
@ -96,12 +108,19 @@ class ScreenSaverService : DreamService() {
private fun initChannels() {
val messenger = flutterEngine!!.dartExecutor
// notification: platform -> dart
val mediaCommandStreamHandler = MediaCommandStreamHandler().apply {
EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this)
}
// dart -> platform -> dart
// - need Context
mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler)
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))

View file

@ -16,8 +16,12 @@ import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.LogUtils
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.*
import java.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.Locale
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@ -34,7 +38,7 @@ class SearchSuggestionsProvider : ContentProvider() {
val columns = arrayOf(
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) SearchManager.SUGGEST_COLUMN_CONTENT_TYPE else "mimeType",
SearchManager.SUGGEST_COLUMN_CONTENT_TYPE,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_ICON_1,

View file

@ -2,132 +2,55 @@ package deckers.thibault.aves
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
import androidx.core.net.toUri
import deckers.thibault.aves.channel.calls.AppAdapterHandler
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getParcelableExtraCompat
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class WallpaperActivity : FlutterFragmentActivity() {
private lateinit var intentDataMap: FieldMap
private lateinit var mediaSessionHandler: MediaSessionHandler
class WallpaperActivity : MainActivity() {
private var originalIntent: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
if (FlutterUtils.isSoftwareRenderingRequired()) {
intent.enableSoftwareRendering()
}
super.onCreate(savedInstanceState)
override fun extractIntentData(intent: Intent?): FieldMap {
if (intent != null) {
when (intent.action) {
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
// MIME type is optional
val type = intent.type ?: intent.resolveType(this)
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_SET_WALLPAPER,
INTENT_DATA_KEY_MIME_TYPE to type,
INTENT_DATA_KEY_URI to uri.toString(),
)
}
Log.i(LOG_TAG, "onCreate intent=$intent")
intent.extras?.takeUnless { it.isEmpty }?.let {
Log.i(LOG_TAG, "onCreate intent extras=$it")
}
intentDataMap = extractIntentData(intent)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val messenger = flutterEngine.dartExecutor
// notification: platform -> dart
val mediaCommandStreamHandler = MediaCommandStreamHandler().apply {
EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this)
}
// dart -> platform -> dart
// - need Context
mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler)
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))
// - need ContextWrapper
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
// - need Activity
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
// result streaming: dart -> platform ->->-> dart
// - need Context
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) }
// intent handling
// detail fetch: dart -> platform
MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result -> onMethodCall(call, result) }
}
override fun onStart() {
Log.i(LOG_TAG, "onStart")
super.onStart()
// as of Flutter v3.0.1, the window `viewInsets` and `viewPadding`
// are incorrect on startup in some environments (e.g. API 29 emulator),
// so we manually request to apply the insets to update the window metrics
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
Handler(Looper.getMainLooper()).postDelayed({
window.decorView.requestApplyInsets()
}, 100)
}
}
override fun onDestroy() {
mediaSessionHandler.dispose()
super.onDestroy()
}
private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getIntentData" -> {
result.success(intentDataMap)
intentDataMap.clear()
}
}
}
private fun extractIntentData(intent: Intent?): FieldMap {
when (intent?.action) {
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
// MIME type is optional
val type = intent.type ?: intent.resolveType(this)
return hashMapOf(
MainActivity.INTENT_DATA_KEY_ACTION to MainActivity.INTENT_ACTION_SET_WALLPAPER,
MainActivity.INTENT_DATA_KEY_MIME_TYPE to type,
MainActivity.INTENT_DATA_KEY_URI to uri.toString(),
)
// if the media URI is not provided we need to pick one first
originalIntent = intent.action
intent.action = Intent.ACTION_PICK
}
}
Intent.ACTION_RUN -> {
// flutter run
}
else -> {
Log.w(LOG_TAG, "unhandled intent action=${intent?.action}")
}
}
return HashMap()
return super.extractIntentData(intent)
}
companion object {
private val LOG_TAG = LogUtils.createTag<WallpaperActivity>()
override fun submitPickedItems(call: MethodCall, result: MethodChannel.Result) {
if (originalIntent != null) {
val pickedUris = call.argument<List<String>>("uris")
if (!pickedUris.isNullOrEmpty()) {
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, uriString.toUri()) }
onNewIntent(Intent().apply {
action = originalIntent
data = toUri(pickedUris.first())
})
} else {
setResult(RESULT_CANCELED)
finish()
}
} else {
super.submitPickedItems(call, result)
}
}
}

View file

@ -21,27 +21,28 @@ class AvesByteSendingMethodCodec private constructor() : MethodCodec {
return STANDARD.encodeMethodCall(methodCall)
}
override fun encodeErrorEnvelope(errorCode: String, errorMessage: String?, errorDetails: Any?): ByteBuffer {
return STANDARD.encodeErrorEnvelope(errorCode, errorMessage, errorDetails)
}
override fun encodeErrorEnvelopeWithStacktrace(errorCode: String, errorMessage: String?, errorDetails: Any?, errorStacktrace: String?): ByteBuffer {
return STANDARD.encodeErrorEnvelopeWithStacktrace(errorCode, errorMessage, errorDetails, errorStacktrace)
}
// `StandardMethodCodec` writes the result to a `ByteArrayOutputStream`, then writes the stream to a `ByteBuffer`.
// Here we only handle `ByteArray` results, but we avoid the intermediate stream.
override fun encodeSuccessEnvelope(result: Any?): ByteBuffer {
if (result is ByteArray) {
val size = result.size
return ByteBuffer.allocateDirect(4 + size).apply {
return ByteBuffer.allocateDirect(1 + result.size).apply {
// following `StandardMethodCodec`:
// First byte is zero in success case, and non-zero otherwise.
put(0)
put(result)
}
}
Log.e(LOG_TAG, "encodeSuccessEnvelope failed with result=$result")
return ByteBuffer.allocateDirect(0)
}
override fun encodeErrorEnvelope(errorCode: String, errorMessage: String?, errorDetails: Any?): ByteBuffer {
Log.e(LOG_TAG, "encodeErrorEnvelope failed with errorCode=$errorCode, errorMessage=$errorMessage, errorDetails=$errorDetails")
return ByteBuffer.allocateDirect(0)
}
override fun encodeErrorEnvelopeWithStacktrace(errorCode: String, errorMessage: String?, errorDetails: Any?, errorStacktrace: String?): ByteBuffer {
Log.e(LOG_TAG, "encodeErrorEnvelopeWithStacktrace failed with errorCode=$errorCode, errorMessage=$errorMessage, errorDetails=$errorDetails, errorStacktrace=$errorStacktrace")
return ByteBuffer.allocateDirect(0)
return encodeErrorEnvelope("invalid-result-type", "Called success with a result which is not a `ByteArray`, type=${result?.javaClass}", null)
}
companion object {

View file

@ -6,6 +6,7 @@ import android.content.res.Configuration
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.ViewConfiguration
import android.view.accessibility.AccessibilityManager
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.utils.LogUtils
@ -17,6 +18,7 @@ class AccessibilityHandler(private val contextWrapper: ContextWrapper) : MethodC
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"areAnimationsRemoved" -> safe(call, result, ::areAnimationsRemoved)
"getLongPressTimeout" -> safe(call, result, ::getLongPressTimeout)
"hasRecommendedTimeouts" -> safe(call, result, ::hasRecommendedTimeouts)
"getRecommendedTimeoutMillis" -> safe(call, result, ::getRecommendedTimeoutMillis)
"shouldUseBoldFont" -> safe(call, result, ::shouldUseBoldFont)
@ -34,6 +36,10 @@ class AccessibilityHandler(private val contextWrapper: ContextWrapper) : MethodC
result.success(removed)
}
private fun getLongPressTimeout(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(ViewConfiguration.getLongPressTimeout())
}
private fun hasRecommendedTimeouts(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
}

View file

@ -1,15 +1,16 @@
package deckers.thibault.aves.channel.calls
import android.app.ActivityManager
import android.content.Context
import androidx.core.app.ComponentActivity
import androidx.core.content.edit
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.workDataOf
import deckers.thibault.aves.AnalysisWorker
import deckers.thibault.aves.utils.FlutterUtils
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.CoroutineScope
@ -18,8 +19,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class AnalysisHandler(private val activity: ComponentActivity, private val onAnalysisCompleted: () -> Unit) : MethodChannel.MethodCallHandler {
class AnalysisHandler(private val activity: FlutterFragmentActivity, private val onAnalysisCompleted: () -> Unit) : MethodChannel.MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
@ -37,10 +37,10 @@ class AnalysisHandler(private val activity: ComponentActivity, private val onAna
return
}
activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
.edit()
.putLong(AnalysisWorker.CALLBACK_HANDLE_KEY, callbackHandle)
.apply()
val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
preferences.edit {
putLong(AnalysisWorker.PREF_CALLBACK_HANDLE_KEY, callbackHandle)
}
result.success(true)
}
@ -51,35 +51,36 @@ class AnalysisHandler(private val activity: ComponentActivity, private val onAna
return
}
val activityManager: ActivityManager = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningAppProcesses = activityManager.runningAppProcesses
if (runningAppProcesses != null) {
val importance = runningAppProcesses[0].importance
if (importance < ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
// the app is in the background
result.error("startAnalysis-background", "app is in the background (process importance=$importance)", null)
return
}
}
// can be null or empty
val allEntryIds = call.argument<List<Int>>("entryIds")
val progressTotal = allEntryIds?.size ?: 0
var progressOffset = 0
// work `Data` cannot occupy more than 10240 bytes when serialized
// so we split it when we have a long list of entry IDs
val chunked = allEntryIds?.chunked(WORK_DATA_CHUNK_SIZE) ?: listOf(null)
fun buildRequest(entryIds: List<Int>?, progressOffset: Int): OneTimeWorkRequest {
val workData = workDataOf(
AnalysisWorker.KEY_ENTRY_IDS to entryIds?.toIntArray(),
AnalysisWorker.KEY_FORCE to force,
AnalysisWorker.KEY_PROGRESS_TOTAL to progressTotal,
AnalysisWorker.KEY_PROGRESS_OFFSET to progressOffset,
)
return OneTimeWorkRequestBuilder<AnalysisWorker>().apply { setInputData(workData) }.build()
// so we save the possibly long list of entry IDs to shared preferences
val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
preferences.edit {
putStringSet(AnalysisWorker.PREF_ENTRY_IDS_KEY, allEntryIds?.map { it.toString() }?.toSet())
}
var work = WorkManager.getInstance(activity).beginUniqueWork(
val workData = workDataOf(
AnalysisWorker.KEY_FORCE to force,
)
WorkManager.getInstance(activity).beginUniqueWork(
ANALYSIS_WORK_NAME,
ExistingWorkPolicy.KEEP,
buildRequest(chunked.first(), progressOffset),
)
chunked.drop(1).forEach { entryIds ->
progressOffset += WORK_DATA_CHUNK_SIZE
work = work.then(buildRequest(entryIds, progressOffset))
}
work.enqueue()
OneTimeWorkRequestBuilder<AnalysisWorker>().apply { setInputData(workData) }.build(),
).enqueue()
attachToActivity()
result.success(null)
@ -105,6 +106,5 @@ class AnalysisHandler(private val activity: ComponentActivity, private val onAna
companion object {
const val CHANNEL = "deckers.thibault/aves/analysis"
private const val ANALYSIS_WORK_NAME = "analysis_work"
private const val WORK_DATA_CHUNK_SIZE = 1000
}
}

View file

@ -1,6 +1,10 @@
package deckers.thibault.aves.channel.calls
import android.content.*
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.res.Configuration
import android.content.res.Resources
@ -15,21 +19,27 @@ import androidx.core.content.FileProvider
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.request.RequestOptions
import deckers.thibault.aves.MainActivity
import deckers.thibault.aves.MainActivity.Companion.COLLECTION_PAGE_ROUTE_NAME
import deckers.thibault.aves.MainActivity.Companion.ENTRY_VIEWER_PAGE_ROUTE_NAME
import deckers.thibault.aves.MainActivity.Companion.EXPLORER_PAGE_ROUTE_NAME
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_EXPLORER_PATH
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_ARRAY
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_STRING
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_PAGE
import deckers.thibault.aves.MainActivity.Companion.EXTRA_STRING_ARRAY_SEPARATOR
import deckers.thibault.aves.MainActivity.Companion.MAP_PAGE_ROUTE_NAME
import deckers.thibault.aves.R
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.anyCauseIs
import deckers.thibault.aves.utils.getApplicationInfoCompat
import deckers.thibault.aves.utils.queryIntentActivitiesCompat
import io.flutter.plugin.common.MethodCall
@ -41,7 +51,8 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.*
import java.util.Locale
import java.util.UUID
import kotlin.math.roundToInt
class AppAdapterHandler(private val context: Context) : MethodCallHandler {
@ -142,7 +153,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
// convert DIP to physical pixels here, instead of using `devicePixelRatio` in Flutter
val density = context.resources.displayMetrics.density
val size = (sizeDip * density).roundToInt()
var data: ByteArray? = null
var bytes: ByteArray? = null
try {
val iconResourceId = context.packageManager.getApplicationInfoCompat(packageName, 0).icon
if (iconResourceId != Resources.ID_NULL) {
@ -163,7 +174,9 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
try {
val bitmap = withContext(Dispatchers.IO) { target.get() }
data = bitmap?.getBytes(canHaveAlpha = true, recycle = false)
// do not recycle bitmaps fetched from `ContentResolver` as their lifecycle is unknown
val recycle = false
bytes = BitmapUtils.getRawBytes(bitmap, recycle = recycle)
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to decode app icon for packageName=$packageName", e)
}
@ -173,15 +186,15 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
Log.w(LOG_TAG, "failed to get app info for packageName=$packageName", e)
return
}
if (data != null) {
result.success(data)
if (bytes != null) {
result.success(bytes)
} else {
result.error("getAppIcon-null", "failed to get icon for packageName=$packageName", null)
}
}
private fun copyToClipboard(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val label = call.argument<String>("label")
if (uri == null) {
result.error("copyToClipboard-args", "missing arguments", null)
@ -208,7 +221,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
private fun open(call: MethodCall, result: MethodChannel.Result) {
val title = call.argument<String>("title")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val mimeType = call.argument<String>("mimeType")
val forceChooser = call.argument<Boolean>("forceChooser")
if (uri == null || forceChooser == null) {
@ -225,7 +238,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
}
private fun openMap(call: MethodCall, result: MethodChannel.Result) {
val geoUri = call.argument<String>("geoUri")?.let { Uri.parse(it) }
val geoUri = call.argument<String>("geoUri")?.toUri()
if (geoUri == null) {
result.error("openMap-args", "missing arguments", null)
return
@ -239,7 +252,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
private fun setAs(call: MethodCall, result: MethodChannel.Result) {
val title = call.argument<String>("title")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val mimeType = call.argument<String>("mimeType")
if (uri == null) {
result.error("setAs-args", "missing arguments", null)
@ -262,7 +275,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
return
}
val uriList = ArrayList(urisByMimeType.values.flatten().mapNotNull { getShareableUri(context, Uri.parse(it)) })
val uriList = ArrayList(urisByMimeType.values.flatten().mapNotNull { getShareableUri(context, it.toUri()) })
val mimeTypes = urisByMimeType.keys.toTypedArray()
// simplify share intent for a single item, as some apps can handle one item but not more
@ -297,7 +310,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
val started = safeStartActivityChooser(title, intent)
result.success(started)
} catch (e: Exception) {
if (e is TransactionTooLargeException || e.cause is TransactionTooLargeException) {
if (e.anyCauseIs<TransactionTooLargeException>()) {
result.error("share-large", "transaction too large with ${uriList.size} URIs", e)
} else {
result.error("share-exception", "failed to share ${uriList.size} URIs", e)
@ -348,11 +361,17 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
// shortcuts
private fun pinShortcut(call: MethodCall, result: MethodChannel.Result) {
// common arguments
val label = call.argument<String>("label")
val iconBytes = call.argument<ByteArray>("iconBytes")
val route = call.argument<String>("route")
// route dependent arguments
val filters = call.argument<List<String>>("filters")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (label == null || (filters == null && uri == null)) {
val explorerPath = call.argument<String>("path")
val viewUri = call.argument<String>("viewUri")?.toUri()
val geoUri = call.argument<String>("geoUri")?.toUri()
if (label == null || route == null) {
result.error("pin-args", "missing arguments", null)
return
}
@ -376,20 +395,60 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
// so that foreground is rendered at the intended scale
val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
icon = IconCompat.createWithResource(context, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection)
val resId = when (route) {
MAP_PAGE_ROUTE_NAME -> if (supportAdaptiveIcon) R.mipmap.ic_shortcut_map else R.drawable.ic_shortcut_map
else -> if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection
}
icon = IconCompat.createWithResource(context, resId)
}
val intent = when {
uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java)
filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, "/collection")
.putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray())
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
// so we use a joined `String` as fallback
.putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
val intent: Intent = when (route) {
COLLECTION_PAGE_ROUTE_NAME -> {
if (filters == null) {
result.error("pin-filters", "collection shortcut requires filters", null)
return
}
Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, route)
.putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray())
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
// so we use a joined `String` as fallback
.putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
}
ENTRY_VIEWER_PAGE_ROUTE_NAME -> {
if (viewUri == null) {
result.error("pin-viewUri", "viewer shortcut requires URI", null)
return
}
Intent(Intent.ACTION_VIEW, viewUri, context, MainActivity::class.java)
}
EXPLORER_PAGE_ROUTE_NAME -> {
Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
.putExtra(EXTRA_KEY_PAGE, route)
.putExtra(EXTRA_KEY_EXPLORER_PATH, explorerPath)
}
MAP_PAGE_ROUTE_NAME -> {
if (geoUri == null) {
result.error("pin-geoUri", "map shortcut requires URI", null)
return
}
Intent(Intent.ACTION_VIEW, geoUri, context, MainActivity::class.java).apply {
putExtra(EXTRA_KEY_PAGE, route)
// filters are optional
filters?.let {
putExtra(EXTRA_KEY_FILTERS_ARRAY, it.toTypedArray())
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
// so we use a joined `String` as fallback
putExtra(EXTRA_KEY_FILTERS_STRING, it.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
}
}
}
else -> {
result.error("pin-intent", "failed to build intent", null)
result.error("pin-route", "unsupported shortcut route=$route", null)
return
}
}

View file

@ -0,0 +1,97 @@
package deckers.thibault.aves.channel.calls
import android.app.Activity
import android.content.Context
import android.content.pm.CrossProfileApps
import android.os.Build
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
class AppProfileHandler(private val activity: Activity) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"canInteractAcrossProfiles" -> safe(call, result, ::canInteractAcrossProfiles)
"canRequestInteractAcrossProfiles" -> safe(call, result, ::canRequestInteractAcrossProfiles)
"requestInteractAcrossProfiles" -> safe(call, result, ::requestInteractAcrossProfiles)
"switchProfile" -> safe(call, result, ::switchProfile)
"getProfileSwitchingLabel" -> safe(call, result, ::getProfileSwitchingLabel)
"getTargetUserProfiles" -> safe(call, result, ::getTargetUserProfiles)
else -> result.notImplemented()
}
}
private fun canInteractAcrossProfiles(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
result.success(false)
return
}
val crossProfileApps = activity.getSystemService(Context.CROSS_PROFILE_APPS_SERVICE) as CrossProfileApps
result.success(crossProfileApps.canInteractAcrossProfiles())
}
private fun canRequestInteractAcrossProfiles(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
result.success(false)
return
}
val crossProfileApps = activity.getSystemService(Context.CROSS_PROFILE_APPS_SERVICE) as CrossProfileApps
result.success(crossProfileApps.canRequestInteractAcrossProfiles())
}
private fun requestInteractAcrossProfiles(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
result.success(false)
return
}
val crossProfileApps = activity.getSystemService(Context.CROSS_PROFILE_APPS_SERVICE) as CrossProfileApps
val intent = crossProfileApps.createRequestInteractAcrossProfilesIntent()
val started = activity.startActivity(intent)
result.success(started)
}
private fun switchProfile(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
result.success(false)
return
}
val crossProfileApps = activity.getSystemService(Context.CROSS_PROFILE_APPS_SERVICE) as CrossProfileApps
val userHandles = crossProfileApps.targetUserProfiles
crossProfileApps.startMainActivity(activity.componentName, userHandles.first())
result.success(null)
}
private fun getProfileSwitchingLabel(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
result.success(null)
return
}
val crossProfileApps = activity.getSystemService(Context.CROSS_PROFILE_APPS_SERVICE) as CrossProfileApps
val userHandles = crossProfileApps.targetUserProfiles
val label = if (userHandles.isEmpty()) "" else crossProfileApps.getProfileSwitchingLabel(userHandles.first())
result.success(label)
}
private fun getTargetUserProfiles(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
result.success(false)
return
}
val crossProfileApps = activity.getSystemService(Context.CROSS_PROFILE_APPS_SERVICE) as CrossProfileApps
val userProfiles = crossProfileApps.targetUserProfiles.map { it.toString() }.toList()
result.success(userProfiles)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/app_profile"
}
}

View file

@ -12,7 +12,7 @@ import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import androidx.core.net.toUri
import com.drew.metadata.file.FileTypeDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.ExifInterfaceHelper
@ -44,6 +44,7 @@ import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import org.mp4parser.IsoFile
import java.io.FileInputStream
import java.io.IOException
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
class DebugHandler(private val context: Context) : MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@ -79,15 +80,9 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
"obbDir" to context.obbDir,
"externalCacheDir" to context.externalCacheDir,
"externalFilesDir" to context.getExternalFilesDir(null),
"codeCacheDir" to context.codeCacheDir,
"noBackupFilesDir" to context.noBackupFilesDir,
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
putAll(
hashMapOf(
"codeCacheDir" to context.codeCacheDir,
"noBackupFilesDir" to context.noBackupFilesDir,
)
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
put("dataDir", context.dataDir)
}
@ -108,8 +103,6 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
}
private fun getCodecs(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val codecs = ArrayList<FieldMap>()
fun getFields(info: MediaCodecInfo): FieldMap {
val fields: FieldMap = hashMapOf(
"name" to info.name,
@ -126,18 +119,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
return fields
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
codecs.addAll(MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.map(::getFields))
} else {
@Suppress("deprecation")
val count = MediaCodecList.getCodecCount()
for (i in 0 until count) {
@Suppress("deprecation")
val info = MediaCodecList.getCodecInfoAt(i)
codecs.add(getFields(info))
}
}
val codecs = MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.map(::getFields).toList()
result.success(codecs)
}
@ -146,7 +128,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
}
private fun getBitmapFactoryInfo(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
if (uri == null) {
result.error("getBitmapDecoderInfo-args", "missing arguments", null)
return
@ -175,7 +157,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
private fun getContentResolverMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
if (mimeType == null || uri == null) {
result.error("getContentResolverMetadata-args", "missing arguments", null)
return
@ -231,7 +213,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
private fun getExifInterfaceMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getExifInterfaceMetadata-args", "missing arguments", null)
@ -258,7 +240,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
}
private fun getMediaMetadataRetrieverMetadata(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
if (uri == null) {
result.error("getMediaMetadataRetrieverMetadata-args", "missing arguments", null)
return
@ -283,7 +265,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
private fun getMetadataExtractorSummary(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getMetadataExtractorSummary-args", "missing arguments", null)
@ -294,7 +276,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
metadataMap["mimeType"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir ->
if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)) {
dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)
@ -327,14 +309,14 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
private fun getMp4ParserDump(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
if (mimeType == null || uri == null) {
result.error("getMp4ParserDump-args", "missing arguments", null)
return
}
val sb = StringBuilder()
if (mimeType == MimeTypes.MP4) {
if (mimeType == MimeTypes.MP4 || MimeTypes.isHeic(mimeType)) {
try {
// we can skip uninteresting boxes with a seekable data source
val pfd = StorageUtils.openInputFileDescriptor(context, uri) ?: throw Exception("failed to open file descriptor for uri=$uri")
@ -357,7 +339,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
private fun getPixyMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
if (mimeType == null || uri == null) {
result.error("getPixyMetadata-args", "missing arguments", null)
return
@ -378,7 +360,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
}
private fun getTiffStructure(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
if (uri == null) {
result.error("getTiffStructure-args", "missing arguments", null)
return

View file

@ -1,14 +1,19 @@
package deckers.thibault.aves.channel.calls
import android.annotation.SuppressLint
import android.app.LocaleConfig
import android.app.LocaleManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Resources
import android.location.Geocoder
import android.net.Uri
import android.os.Build
import android.os.LocaleList
import android.provider.MediaStore
import android.provider.Settings
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.net.toUri
import com.google.android.material.color.DynamicColors
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.FieldMap
@ -21,7 +26,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.Locale
import java.util.TimeZone
class DeviceHandler(private val context: Context) : MethodCallHandler {
private val defaultScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
@ -30,9 +34,10 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
when (call.method) {
"canManageMedia" -> safe(call, result, ::canManageMedia)
"getCapabilities" -> defaultScope.launch { safe(call, result, ::getCapabilities) }
"getDefaultTimeZoneRawOffsetMillis" -> safe(call, result, ::getDefaultTimeZoneRawOffsetMillis)
"getLocales" -> safe(call, result, ::getLocales)
"setLocaleConfig" -> safe(call, result, ::setLocaleConfig)
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
"isLocked" -> safe(call, result, ::isLocked)
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
"requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission)
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
@ -49,36 +54,32 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
val sdkInt = Build.VERSION.SDK_INT
result.success(
hashMapOf(
"canGrantDirectoryAccess" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
"canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.M),
"canRenderSubdivisionFlagEmojis" to (sdkInt >= Build.VERSION_CODES.O),
"canRequestManageMedia" to (sdkInt >= Build.VERSION_CODES.S),
"canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N),
"canUseCrypto" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
"hasGeocoder" to Geocoder.isPresent(),
"isDynamicColorAvailable" to DynamicColors.isDynamicColorAvailable(),
"showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O),
"supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q),
"supportPictureInPicture" to supportPictureInPicture(),
)
)
}
private fun getDefaultTimeZoneRawOffsetMillis(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(TimeZone.getDefault().rawOffset)
private fun supportPictureInPicture(): Boolean {
// minimum version for `PictureInPictureParams.Builder#setAutoEnterEnabled`
val supportPipOnLeave = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
return supportPipOnLeave && context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
}
private fun getLocales(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
fun toMap(locale: Locale): FieldMap {
val fields: HashMap<String, Any?> = hashMapOf(
"language" to locale.language,
"country" to locale.country,
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
fields["script"] = locale.script
}
return fields
}
fun toMap(locale: Locale): FieldMap = hashMapOf(
"language" to locale.language,
"country" to locale.country,
"script" to locale.script,
)
val locales = ArrayList<FieldMap>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@ -94,6 +95,22 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
result.success(locales)
}
private fun setLocaleConfig(call: MethodCall, result: MethodChannel.Result) {
val locales = call.argument<List<String>>("locales")
if (locales.isNullOrEmpty()) {
result.error("setLocaleConfig-args", "missing arguments", null)
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
@SuppressLint("WrongConstant")
val lm = context.getSystemService(Context.LOCALE_SERVICE) as? LocaleManager
lm?.overrideLocaleConfig = LocaleConfig(LocaleList.forLanguageTags(locales.joinToString(",")))
}
result.success(true)
}
private fun getPerformanceClass(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val performanceClass = Build.VERSION.MEDIA_PERFORMANCE_CLASS
@ -105,12 +122,14 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
result.success(Build.VERSION.SDK_INT)
}
private fun isLocked(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as android.app.KeyguardManager
val isLocked = keyguardManager.isKeyguardLocked
result.success(isLocked)
}
private fun isSystemFilePickerEnabled(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val enabled = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).resolveActivity(context.packageManager) != null
} else {
false
}
val enabled = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).resolveActivity(context.packageManager) != null
result.success(enabled)
}
@ -120,7 +139,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
return
}
val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA, Uri.parse("package:${context.packageName}"))
val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA, "package:${context.packageName}".toUri())
context.startActivity(intent)
result.success(true)
}

View file

@ -1,16 +1,15 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import androidx.exifinterface.media.ExifInterface
import com.adobe.internal.xmp.XMPException
import com.adobe.internal.xmp.XMPUtils
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.metadata.Metadata
import deckers.thibault.aves.metadata.MultiPage
import deckers.thibault.aves.metadata.metadataextractor.Helper
@ -18,11 +17,11 @@ import deckers.thibault.aves.metadata.xmp.GoogleDeviceContainer
import deckers.thibault.aves.metadata.xmp.GoogleXMP
import deckers.thibault.aves.metadata.xmp.XMP.getSafeStructField
import deckers.thibault.aves.metadata.xmp.XMPPropName
import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.provider.ImageProvider
import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.FileUtils.transferFrom
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
@ -46,7 +45,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getExifThumbnails" -> ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
"getExifThumbnails" -> ioScope.launch { safe(call, result, ::getExifThumbnails) }
"extractGoogleDeviceItem" -> ioScope.launch { safe(call, result, ::extractGoogleDeviceItem) }
"extractJpegMpfItem" -> ioScope.launch { safe(call, result, ::extractJpegMpfItem) }
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
@ -57,9 +56,9 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
}
}
private suspend fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) {
private fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getExifThumbnails-args", "missing arguments", null)
@ -74,7 +73,9 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
exif.thumbnailBitmap?.let { bitmap ->
TransformationUtils.rotateImageExif(BitmapUtils.getBitmapPool(context), bitmap, orientation)?.let {
it.getBytes(canHaveAlpha = false, recycle = false)?.let { bytes -> thumbnails.add(bytes) }
// do not recycle bitmaps fetched from `ExifInterface` as their lifecycle is unknown
val recycle = false
BitmapUtils.getRawBytes(it, recycle = recycle)?.let { bytes -> thumbnails.add(bytes) }
}
}
}
@ -88,7 +89,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
private fun extractGoogleDeviceItem(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val displayName = call.argument<String>("displayName")
val dataUri = call.argument<String>("dataUri")
@ -102,7 +103,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
// data can be large and stored in "Extended XMP",
// which is returned as a second XMP directory
val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java)
@ -143,7 +144,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
private fun extractJpegMpfItem(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val displayName = call.argument<String>("displayName")
val id = call.argument<Int>("id")
@ -177,7 +178,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
private fun extractMotionPhotoImage(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val displayName = call.argument<String>("displayName")
if (mimeType == null || uri == null || sizeBytes == null) {
@ -185,7 +186,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
return
}
MultiPage.getMotionPhotoOffset(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
MultiPage.getTrailerVideoSize(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
val imageSizeBytes = sizeBytes - videoSizeBytes
StorageUtils.openInputStream(context, uri)?.let { input ->
copyEmbeddedBytes(result, mimeType, displayName, input, imageSizeBytes)
@ -198,7 +199,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
private fun extractMotionPhotoVideo(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val displayName = call.argument<String>("displayName")
if (mimeType == null || uri == null || sizeBytes == null) {
@ -206,11 +207,10 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
return
}
MultiPage.getMotionPhotoOffset(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
val videoStartOffset = sizeBytes - videoSizeBytes
MultiPage.getMotionPhotoVideoSizing(context, uri, mimeType, sizeBytes)?.let { (videoOffset, videoSize) ->
StorageUtils.openInputStream(context, uri)?.let { input ->
input.skip(videoStartOffset)
copyEmbeddedBytes(result, MimeTypes.MP4, displayName, input, videoSizeBytes)
input.skip(videoOffset)
copyEmbeddedBytes(result, MimeTypes.MP4, displayName, input, videoSize)
}
return
}
@ -219,7 +219,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
}
private fun extractVideoEmbeddedPicture(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val displayName = call.argument<String>("displayName")
if (uri == null) {
result.error("extractVideoEmbeddedPicture-args", "missing arguments", null)
@ -251,7 +251,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
private fun extractXmpDataProp(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val displayName = call.argument<String>("displayName")
val dataProp = call.argument<List<Any>>("propPath")
@ -272,7 +272,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
// data can be large and stored in "Extended XMP",
// which is returned as a second XMP directory
val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java)
@ -311,7 +311,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
embeddedByteStream: InputStream,
embeddedByteLength: Long,
) {
val extension = extensionFor(mimeType)
val extension = extensionFor(mimeType, defaultExtension = null)
val targetFile = StorageUtils.createTempFile(context, extension).apply {
transferFrom(embeddedByteStream, embeddedByteLength)
}
@ -319,7 +319,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
val authority = "${context.applicationContext.packageName}.file_provider"
val uri = if (displayName != null) {
// add extension to ease type identification when sharing this content
val displayNameWithExtension = if (extension == null || displayName.endsWith(extension, ignoreCase = true)) {
val displayNameWithExtension = if (displayName.endsWith(extension, ignoreCase = true)) {
displayName
} else {
"$displayName$extension"
@ -329,8 +329,8 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
FileProvider.getUriForFile(context, authority, targetFile)
}
val resultFields: FieldMap = hashMapOf(
"uri" to uri.toString(),
"mimeType" to mimeType,
EntryFields.URI to uri.toString(),
EntryFields.MIME_TYPE to mimeType,
)
if (isImage(mimeType) || isVideo(mimeType)) {
val provider = getProvider(context, uri)
@ -340,7 +340,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
}
ioScope.launch {
provider.fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback {
provider.fetchSingle(context, uri, mimeType, false, object : ImageProvider.ImageOpCallback {
override fun onSuccess(fields: FieldMap) {
resultFields.putAll(fields)
result.success(resultFields)

View file

@ -31,7 +31,7 @@ class GeocodingHandler(private val context: Context) : MethodCallHandler {
private fun getAddress(call: MethodCall, result: MethodChannel.Result) {
val latitude = call.argument<Number>("latitude")?.toDouble()
val longitude = call.argument<Number>("longitude")?.toDouble()
val localeString = call.argument<String>("locale")
val localeLanguageTag = call.argument<String>("localeLanguageTag")
val maxResults = call.argument<Int>("maxResults") ?: 1
if (latitude == null || longitude == null) {
result.error("getAddress-args", "missing arguments", null)
@ -43,11 +43,8 @@ class GeocodingHandler(private val context: Context) : MethodCallHandler {
return
}
geocoder = geocoder ?: if (localeString != null) {
val split = localeString.split("_")
val language = split[0]
val country = if (split.size > 1) split[1] else ""
Geocoder(context, Locale(language, country))
geocoder = geocoder ?: if (localeLanguageTag != null) {
Geocoder(context, Locale.forLanguageTag(localeLanguageTag))
} else {
Geocoder(context)
}

View file

@ -1,6 +1,7 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import androidx.core.content.edit
import deckers.thibault.aves.SearchSuggestionsProvider
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import io.flutter.plugin.common.MethodCall
@ -28,10 +29,10 @@ class GlobalSearchHandler(private val context: Context) : MethodCallHandler {
return
}
context.getSharedPreferences(SearchSuggestionsProvider.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
.edit()
.putLong(SearchSuggestionsProvider.CALLBACK_HANDLE_KEY, callbackHandle)
.apply()
val preferences = context.getSharedPreferences(SearchSuggestionsProvider.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
preferences.edit {
putLong(SearchSuggestionsProvider.CALLBACK_HANDLE_KEY, callbackHandle)
}
result.success(true)
}

View file

@ -1,8 +1,8 @@
package deckers.thibault.aves.channel.calls
import android.content.ContextWrapper
import android.net.Uri
import android.util.Log
import androidx.core.net.toUri
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.model.FieldMap
@ -44,7 +44,7 @@ class MediaEditHandler(private val contextWrapper: ContextWrapper) : MethodCallH
}
private suspend fun captureFrame(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val desiredName = call.argument<String>("desiredName")
val exifFields = call.argument<FieldMap>("exif") ?: HashMap()
val bytes = call.argument<ByteArray>("bytes")

View file

@ -2,12 +2,13 @@ package deckers.thibault.aves.channel.calls
import android.content.Context
import android.graphics.Rect
import android.net.Uri
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import androidx.core.net.toUri
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.fetchers.RegionFetcher
import deckers.thibault.aves.channel.calls.fetchers.SvgRegionFetcher
import deckers.thibault.aves.channel.calls.fetchers.ThumbnailFetcher
import deckers.thibault.aves.channel.calls.fetchers.TiffRegionFetcher
import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.utils.MimeTypes
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
@ -16,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.Date
import kotlin.math.roundToInt
class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
@ -26,25 +28,25 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getThumbnail" -> ioScope.launch { safeSuspend(call, result, ::getThumbnail) }
"getRegion" -> ioScope.launch { safeSuspend(call, result, ::getRegion) }
"getThumbnail" -> ioScope.launch { safe(call, result, ::getThumbnail) }
"getRegion" -> ioScope.launch { safe(call, result, ::getRegion) }
else -> result.notImplemented()
}
}
private suspend fun getThumbnail(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")
val mimeType = call.argument<String>("mimeType")
val dateModifiedSecs = call.argument<Number>("dateModifiedSecs")?.toLong()
val rotationDegrees = call.argument<Int>("rotationDegrees")
val isFlipped = call.argument<Boolean>("isFlipped")
private fun getThumbnail(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>(EntryFields.URI)
val mimeType = call.argument<String>(EntryFields.MIME_TYPE)
val dateModifiedMillis = call.argument<Number>(EntryFields.DATE_MODIFIED_MILLIS)?.toLong()
val rotationDegrees = call.argument<Int>(EntryFields.ROTATION_DEGREES)
val isFlipped = call.argument<Boolean>(EntryFields.IS_FLIPPED)
val widthDip = call.argument<Number>("widthDip")?.toDouble()
val heightDip = call.argument<Number>("heightDip")?.toDouble()
val pageId = call.argument<Int>("pageId")
val defaultSizeDip = call.argument<Number>("defaultSizeDip")?.toDouble()
val quality = call.argument<Int>("quality")
if (uri == null || mimeType == null || dateModifiedSecs == null || rotationDegrees == null || isFlipped == null || widthDip == null || heightDip == null || defaultSizeDip == null || quality == null) {
if (uri == null || mimeType == null || rotationDegrees == null || isFlipped == null || widthDip == null || heightDip == null || defaultSizeDip == null || quality == null) {
result.error("getThumbnail-args", "missing arguments", null)
return
}
@ -54,7 +56,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
context = context,
uri = uri,
mimeType = mimeType,
dateModifiedSecs = dateModifiedSecs,
dateModifiedMillis = dateModifiedMillis ?: (Date().time),
rotationDegrees = rotationDegrees,
isFlipped = isFlipped,
width = (widthDip * density).roundToInt(),
@ -66,8 +68,8 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
).fetch()
}
private suspend fun getRegion(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
private fun getRegion(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.toUri()
val mimeType = call.argument<String>("mimeType")
val pageId = call.argument<Int>("pageId")
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
@ -95,6 +97,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
imageHeight = imageHeight,
result = result,
)
MimeTypes.TIFF -> TiffRegionFetcher(context).fetch(
uri = uri,
page = pageId ?: 0,
@ -102,6 +105,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
regionRect = regionRect,
result = result,
)
else -> regionFetcher.fetch(
uri = uri,
mimeType = mimeType,

View file

@ -1,7 +1,9 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.os.Looper
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.FieldMap
@ -21,14 +23,16 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getEntry" -> ioScope.launch { safe(call, result, ::getEntry) }
"clearSizedThumbnailDiskCache" -> ioScope.launch { safe(call, result, ::clearSizedThumbnailDiskCache) }
"clearImageDiskCache" -> ioScope.launch { safe(call, result, ::clearImageDiskCache) }
"clearImageMemoryCache" -> ioScope.launch { safe(call, result, ::clearImageMemoryCache) }
else -> result.notImplemented()
}
}
private fun getEntry(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType") // MIME type is optional
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val allowUnsized = call.argument<Boolean>("allowUnsized") ?: false
if (uri == null) {
result.error("getEntry-args", "missing arguments", null)
return
@ -40,17 +44,24 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
return
}
provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback {
provider.fetchSingle(context, uri, mimeType, allowUnsized, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = result.success(fields)
override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri mimeType=$mimeType", throwable.message)
})
}
private fun clearSizedThumbnailDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
private fun clearImageDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Glide.get(context).clearDiskCache()
result.success(null)
}
private fun clearImageMemoryCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Handler(Looper.getMainLooper()).post {
Glide.get(context).clearMemory()
}
result.success(null)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/media_fetch_object"
}

View file

@ -1,12 +1,16 @@
package deckers.thibault.aves.channel.calls
import android.content.*
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioManager
import android.media.session.PlaybackState
import android.net.Uri
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import androidx.core.net.toUri
import androidx.media.session.MediaButtonReceiver
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
@ -59,7 +63,7 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand
}
private suspend fun updateSession(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val title = call.argument<String>("title") ?: uri?.toString()
val durationMillis = call.argument<Number>("durationMillis")?.toLong()
val stateString = call.argument<String>("state")

View file

@ -59,7 +59,14 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
private fun getGeneration(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val generation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
MediaStore.getGeneration(context, MediaStore.VOLUME_EXTERNAL_PRIMARY)
try {
MediaStore.getGeneration(context, MediaStore.VOLUME_EXTERNAL_PRIMARY)
} catch (e: Exception) {
// may yield `IllegalArgumentException: Volume external_primary not found`
val volumes = MediaStore.getExternalVolumeNames(context).joinToString(", ")
result.error("getGeneration-primary", e.message + " (available volumes are [$volumes])", e)
return
}
} else {
null
}

View file

@ -1,7 +1,7 @@
package deckers.thibault.aves.channel.calls
import android.content.ContextWrapper
import android.net.Uri
import androidx.core.net.toUri
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.Mp4TooLargeException
import deckers.thibault.aves.model.ExifOrientationOp
@ -54,7 +54,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val uri = (entryMap["uri"] as String?)?.let { Uri.parse(it) }
val uri = (entryMap["uri"] as String?)?.toUri()
val path = entryMap["path"] as String?
val mimeType = entryMap["mimeType"] as String?
if (uri == null || path == null || mimeType == null) {
@ -74,7 +74,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
private fun editDate(call: MethodCall, result: MethodChannel.Result) {
val dateMillis = call.argument<Number>("dateMillis")?.toLong()
val shiftMinutes = call.argument<Number>("shiftMinutes")?.toLong()
val shiftSeconds = call.argument<Number>("shiftSeconds")?.toLong()
val fields = call.argument<List<String>>("fields")
val entryMap = call.argument<FieldMap>("entry")
if (entryMap == null || fields == null) {
@ -82,7 +82,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val uri = (entryMap["uri"] as String?)?.let { Uri.parse(it) }
val uri = (entryMap["uri"] as String?)?.toUri()
val path = entryMap["path"] as String?
val mimeType = entryMap["mimeType"] as String?
if (uri == null || path == null || mimeType == null) {
@ -97,7 +97,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
}
val callback = MetadataOpCallback("editDate", entryMap, result)
provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftMinutes, fields, callback)
provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftSeconds, fields, callback)
}
private fun editMetadata(call: MethodCall, result: MethodChannel.Result) {
@ -109,7 +109,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val uri = (entryMap["uri"] as String?)?.let { Uri.parse(it) }
val uri = (entryMap["uri"] as String?)?.toUri()
val path = entryMap["path"] as String?
val mimeType = entryMap["mimeType"] as String?
if (uri == null || path == null || mimeType == null) {
@ -134,7 +134,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val uri = (entryMap["uri"] as String?)?.let { Uri.parse(it) }
val uri = (entryMap["uri"] as String?)?.toUri()
val path = entryMap["path"] as String?
val mimeType = entryMap["mimeType"] as String?
if (uri == null || path == null || mimeType == null) {
@ -160,7 +160,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val uri = (entryMap["uri"] as String?)?.let { Uri.parse(it) }
val uri = (entryMap["uri"] as String?)?.toUri()
val path = entryMap["path"] as String?
val mimeType = entryMap["mimeType"] as String?
if (uri == null || path == null || mimeType == null) {

View file

@ -6,7 +6,7 @@ import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import androidx.core.net.toUri
import com.adobe.internal.xmp.XMPException
import com.adobe.internal.xmp.XMPMeta
import com.adobe.internal.xmp.XMPMetaFactory
@ -20,10 +20,10 @@ import com.drew.metadata.exif.ExifDirectoryBase
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.exif.ExifSubIFDDirectory
import com.drew.metadata.exif.GpsDirectory
import com.drew.metadata.exif.makernotes.AppleMakernoteDirectory
import com.drew.metadata.file.FileTypeDirectory
import com.drew.metadata.gif.GifAnimationDirectory
import com.drew.metadata.iptc.IptcDirectory
import com.drew.metadata.mov.metadata.QuickTimeMetadataDirectory
import com.drew.metadata.mp4.media.Mp4UuidBoxDirectory
import com.drew.metadata.png.PngDirectory
import com.drew.metadata.webp.WebpDirectory
@ -102,12 +102,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.json.JSONObject
import org.mp4parser.boxes.threegpp.ts26244.LocationInformationBox
import org.mp4parser.tools.Path
import java.nio.charset.StandardCharsets
import java.text.DecimalFormat
import java.text.ParseException
import java.util.Locale
import kotlin.math.roundToInt
import kotlin.math.roundToLong
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@ -132,7 +135,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getAllMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getAllMetadata-args", "missing arguments", null)
@ -164,9 +167,11 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
// remove this stat as it is not actual XMP data
dirMap.remove(XmpDirectory().getTagName(XmpDirectory.TAG_XMP_VALUE_COUNT))
// add schema prefixes for namespace resolution
val prefixes = XMPMetaFactory.getSchemaRegistry().prefixes
dirMap["schemaRegistryPrefixes"] = JSONObject(prefixes).toString()
if (dirMap.isNotEmpty()) {
// add schema prefixes for namespace resolution
val prefixes = XMPMetaFactory.getSchemaRegistry().prefixes
dirMap["schemaRegistryPrefixes"] = JSONObject(prefixes).toString()
}
}
val mp4UuidDirCount = HashMap<String, Int>()
@ -229,7 +234,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundExif = metadata.directories.any { it is ExifDirectoryBase && it.tagCount > 0 }
foundMp4Uuid = metadata.directories.any { it is Mp4UuidBoxDirectory && it.tagCount > 0 }
@ -296,7 +301,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
}
mimeType == MimeTypes.DNG -> {
mimeType == MimeTypes.DNG || mimeType == MimeTypes.DNG_ADOBE -> {
// split DNG tags in their own directory
val dngDirMap = metadataMap[DIR_DNG] ?: HashMap()
metadataMap[DIR_DNG] = dngDirMap
@ -447,9 +452,8 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (isVideo(mimeType)) {
// `metadata-extractor` do not extract custom tags in user data box
val userDataDir = Mp4ParserHelper.getUserData(context, mimeType, uri)
if (userDataDir.isNotEmpty()) {
metadataMap[Metadata.DIR_MP4_USER_DATA] = userDataDir
Mp4ParserHelper.getUserDataBox(context, mimeType, uri)?.let { box ->
metadataMap[Metadata.DIR_MP4_USER_DATA] = Mp4ParserHelper.extractBoxFields(box)
}
// this is used as fallback when the video metadata cannot be found on the Dart side
@ -468,6 +472,12 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// Android's `MediaExtractor` and `MediaPlayer` cannot be used for details
// about embedded images as they do not list them as separate tracks
// and only identify at most one
} else if (isHeic(mimeType)) {
Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (_, bytes) ->
metadataMap[Mp4ParserHelper.SAMSUNG_MAKERNOTE_BOX_TYPE] = hashMapOf(
"Size" to bytes.size.toString(),
)
}
}
if (metadataMap.isNotEmpty()) {
@ -515,7 +525,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// - XMP / MicrosoftPhoto:Rating
private fun getCatalogMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val path = call.argument<String>("path")
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
@ -525,8 +535,33 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
val metadataMap = HashMap<String, Any>()
getCatalogMetadataByMetadataExtractor(mimeType, uri, path, sizeBytes, metadataMap)
if (isVideo(mimeType) || isHeic(mimeType)) {
getMultimediaCatalogMetadataByMediaMetadataRetriever(mimeType, uri, metadataMap)
// fallback to MP4 `loci` box for location
if (!metadataMap.contains(KEY_LATITUDE) || !metadataMap.contains(KEY_LONGITUDE)) {
try {
Mp4ParserHelper.getUserDataBox(context, mimeType, uri)?.let { userDataBox ->
Path.getPath<LocationInformationBox>(userDataBox, LocationInformationBox.TYPE)?.let { locationBox ->
if (!locationBox.isParsed) {
locationBox.parseDetails()
}
metadataMap[KEY_LATITUDE] = locationBox.latitude
metadataMap[KEY_LONGITUDE] = locationBox.longitude
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get Location Information box by MP4 parser for mimeType=$mimeType uri=$uri", e)
}
}
}
if (isHeic(mimeType)) {
val flags = (metadataMap[KEY_FLAGS] ?: 0) as Int
if ((flags and MASK_IS_MOTION_PHOTO == 0) && MultiPage.isHeicSefdMotionPhoto(context, uri)) {
metadataMap[KEY_FLAGS] = flags or MASK_IS_MULTIPAGE or MASK_IS_MOTION_PHOTO
}
}
// report success even when empty
@ -599,7 +634,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundExif = metadata.directories.any { it is ExifDirectoryBase && it.tagCount > 0 }
foundMp4Uuid = metadata.directories.any { it is Mp4UuidBoxDirectory && it.tagCount > 0 }
@ -660,7 +695,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (metadata.getDirectoriesOfType(MpEntryDirectory::class.java).count { !it.entry.isThumbnail } > 1) {
flags = flags or MASK_IS_MULTIPAGE
if (hasAppleHdrGainMap(uri, sizeBytes, metadata)) {
if (hasAppleHdrGainMap(uri, sizeBytes)) {
flags = flags or MASK_IS_HDR
}
}
@ -684,6 +719,22 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}
if (!metadataMap.containsKey(KEY_LATITUDE) || !metadataMap.containsKey(KEY_LONGITUDE)) {
for (dir in metadata.getDirectoriesOfType(QuickTimeMetadataDirectory::class.java)) {
dir.getSafeString(QuickTimeMetadataDirectory.TAG_LOCATION_ISO6709) { locationString ->
val matcher = Metadata.VIDEO_LOCATION_PATTERN.matcher(locationString)
if (matcher.find() && matcher.groupCount() >= 2) {
val latitude = matcher.group(1)?.toDoubleOrNull()
val longitude = matcher.group(2)?.toDoubleOrNull()
if (latitude != null && longitude != null) {
metadataMap[KEY_LATITUDE] = latitude
metadataMap[KEY_LONGITUDE] = longitude
}
}
}
}
}
when (mimeType) {
MimeTypes.PNG -> {
// date fallback to PNG time chunk
@ -789,11 +840,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
metadataMap[KEY_FLAGS] = flags
}
private fun hasAppleHdrGainMap(uri: Uri, sizeBytes: Long?, primaryMetadata: com.drew.metadata.Metadata): Boolean {
if (!primaryMetadata.containsDirectoryOfType(AppleMakernoteDirectory::class.java)) return false
private fun hasAppleHdrGainMap(uri: Uri, sizeBytes: Long?): Boolean {
val mpEntries = MultiPage.getJpegMpfEntries(context, uri, sizeBytes) ?: return false
mpEntries.filter { it.type == MpEntry.TYPE_UNDEFINED }.forEach { mpEntry ->
mpEntries.filter { it.type == MpEntry.TYPE_UNDEFINED }.forEachIndexed { mpIndex, mpEntry ->
var dataOffset = mpEntry.dataOffset
if (dataOffset > 0) {
val baseOffset = MultiPage.getJpegMpfBaseOffset(context, uri, sizeBytes)
@ -803,9 +852,13 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
StorageUtils.openInputStream(context, uri)?.let { input ->
input.skip(dataOffset)
val pageMetadata = Helper.safeRead(input)
if (pageMetadata.getDirectoriesOfType(XmpDirectory::class.java).any { it.xmpMeta.hasHdrGainMap() }) {
return true
try {
val pageMetadata = Helper.safeRead(input, sizeBytes)
if (pageMetadata.getDirectoriesOfType(XmpDirectory::class.java).any { it.xmpMeta.hasHdrGainMap() }) {
return true
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for uri=$uri mpIndex=$mpIndex mpEntry=$mpEntry", e)
}
}
}
@ -826,7 +879,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { metadataMap[KEY_DATE_MILLIS] = it }
}
if (!metadataMap.containsKey(KEY_LATITUDE)) {
if (!metadataMap.containsKey(KEY_LATITUDE) || !metadataMap.containsKey(KEY_LONGITUDE)) {
val locationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
if (locationString != null) {
val matcher = Metadata.VIDEO_LOCATION_PATTERN.matcher(locationString)
@ -866,7 +919,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getOverlayMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val fields = call.argument<List<String>>("fields")
if (mimeType == null || uri == null || fields == null) {
@ -897,7 +950,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
foundExif = true
if (fields.contains(KEY_APERTURE)) {
@ -954,7 +1007,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
result.success(metadataMap)
}
// return description from these fields (by precedence):
// returns description from these fields (by precedence):
// - XMP / dc:description
// - IPTC / caption-abstract
// - Exif / UserComment
@ -997,7 +1050,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getGeoTiffInfo(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getGeoTiffInfo-args", "missing arguments", null)
@ -1007,7 +1060,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
val fields = HashMap<Int, Any?>()
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
if (dir.containsGeoTiffTags()) {
@ -1038,7 +1091,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getMultiPageInfo(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val isMotionPhoto = call.argument<Boolean>("isMotionPhoto")
if (mimeType == null || uri == null || sizeBytes == null || isMotionPhoto == null) {
@ -1065,7 +1118,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getPanoramaInfo(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getPanoramaInfo-args", "missing arguments", null)
@ -1084,7 +1137,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType) && !isLargeMp4(mimeType, sizeBytes)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
metadata.getDirectoriesOfType(XmpDirectory::class.java).map { it.xmpMeta }.forEach {
processXmp(it, allowMultiple = true)
}
@ -1117,7 +1170,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getIptc(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
if (mimeType == null || uri == null) {
result.error("getIptc-args", "missing arguments", null)
return
@ -1139,11 +1192,11 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
result.success(null)
}
// return XMP components
// return an empty list if there is no XMP
// returns XMP components
// returns an empty list if there is no XMP
private fun getXmp(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getXmp-args", "missing arguments", null)
@ -1166,7 +1219,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType) && !isLargeMp4(mimeType, sizeBytes)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
metadata.getDirectoriesOfType(XmpDirectory::class.java).map { it.xmpMeta }.forEach {
processXmp(it, allowMultiple = true)
}
@ -1215,7 +1268,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getContentPropValue(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val prop = call.argument<String>("prop")
if (mimeType == null || uri == null || prop == null) {
result.error("getContentPropValue-args", "missing arguments", null)
@ -1232,7 +1285,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getDate(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val field = call.argument<String>("field")
if (mimeType == null || uri == null || field == null) {
@ -1244,7 +1297,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
val tag = when (field) {
ExifInterface.TAG_DATETIME -> ExifIFD0Directory.TAG_DATETIME
ExifInterface.TAG_DATETIME_DIGITIZED -> ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED
@ -1301,7 +1354,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private fun getFields(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val uri = call.argument<String>("uri")?.toUri()
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val fields = call.argument<List<String>>("fields")
if (mimeType == null || uri == null || fields == null) {
@ -1345,7 +1398,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
for (dir in metadata.getDirectoriesOfType(ExifDirectoryBase::class.java)) {
foundExif = true
val allTags = ExifInterfaceHelper.allTags

View file

@ -2,6 +2,7 @@ package deckers.thibault.aves.channel.calls
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
@ -44,7 +45,8 @@ class SecurityHandler(private val context: Context) : MethodCallHandler {
return
}
with(getStore().edit()) {
val preferences = getStore()
preferences.edit {
when (value) {
is Boolean -> putBoolean(key, value)
is Float -> putFloat(key, value)
@ -57,7 +59,6 @@ class SecurityHandler(private val context: Context) : MethodCallHandler {
return
}
}
apply()
}
result.success(true)
}

View file

@ -29,6 +29,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
when (call.method) {
"getDataUsage" -> ioScope.launch { safe(call, result, ::getDataUsage) }
"getStorageVolumes" -> ioScope.launch { safe(call, result, ::getStorageVolumes) }
"getCacheDirectory" -> ioScope.launch { safe(call, result, ::getCacheDirectory) }
"getUntrackedTrashPaths" -> ioScope.launch { safe(call, result, ::getUntrackedTrashPaths) }
"getUntrackedVaultPaths" -> ioScope.launch { safe(call, result, ::getUntrackedVaultPaths) }
"getVaultRoot" -> ioScope.launch { safe(call, result, ::getVaultRoot) }
@ -39,6 +40,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
"deleteEmptyDirectories" -> ioScope.launch { safe(call, result, ::deleteEmptyDirectories) }
"deleteTempDirectory" -> ioScope.launch { safe(call, result, ::deleteTempDirectory) }
"deleteExternalCache" -> ioScope.launch { safe(call, result, ::deleteExternalCache) }
"canRequestMediaFileBulkAccess" -> safe(call, result, ::canRequestMediaFileBulkAccess)
"canInsertMedia" -> safe(call, result, ::canInsertMedia)
else -> result.notImplemented()
@ -47,20 +49,19 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
private fun getDataUsage(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
var internalCache = getFolderSize(context.cacheDir)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
internalCache += getFolderSize(context.codeCacheDir)
}
internalCache += getFolderSize(context.codeCacheDir)
val externalCache = context.externalCacheDirs.map(::getFolderSize).sum()
val externalFilesDirs = context.getExternalFilesDirs(null)
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.dataDir else File(context.applicationInfo.dataDir)
val database = getFolderSize(File(dataDir, "databases"))
val flutter = getFolderSize(File(PathUtils.getDataDirectory(context)))
val vaults = getFolderSize(File(StorageUtils.getVaultRoot(context)))
val trash = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
val trash = externalFilesDirs.mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
val internalData = getFolderSize(dataDir) - internalCache
val externalData = context.getExternalFilesDirs(null).map(::getFolderSize).sum()
val externalData = externalFilesDirs.map(::getFolderSize).sum()
val miscData = internalData + externalData - (database + flutter + vaults + trash)
result.success(
@ -105,12 +106,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
val volumeFile = File(volumePath)
try {
val isPrimary = volumePath == primaryVolumePath
val isRemovable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Environment.isExternalStorageRemovable(volumeFile)
} else {
// random guess
!isPrimary
}
val isRemovable = Environment.isExternalStorageRemovable(volumeFile)
volumes.add(
hashMapOf(
"path" to volumePath,
@ -127,6 +123,18 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
result.success(volumes)
}
private fun getCacheDirectory(call: MethodCall, result: MethodChannel.Result) {
val external = call.argument<Boolean>("external")
if (external == null) {
result.error("getCacheDirectory-args", "missing arguments", null)
return
}
val dir = (if (external) context.externalCacheDir else context.cacheDir)
result.success(dir!!.path)
}
private fun getUntrackedTrashPaths(call: MethodCall, result: MethodChannel.Result) {
val knownPaths = call.argument<List<String>>("knownPaths")
if (knownPaths == null) {
@ -202,11 +210,6 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
result.error("revokeDirectoryAccess-unsupported", "volume access is not allowed before Android Lollipop", null)
return
}
val success = PermissionManager.revokeDirectoryAccess(context, path)
result.success(success)
}
@ -236,6 +239,11 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
result.success(StorageUtils.deleteTempDirectory(context))
}
private fun deleteExternalCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
context.externalCacheDirs.filter { it.exists() }.forEach { it.deleteRecursively() }
result.success(true)
}
private fun canRequestMediaFileBulkAccess(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
}

View file

@ -4,20 +4,25 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.ColorSpace
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.util.Log
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import deckers.thibault.aves.decoder.AvesAppGlideModule
import deckers.thibault.aves.decoder.MultiPageImage
import deckers.thibault.aves.utils.BitmapRegionDecoderCompat
import deckers.thibault.aves.utils.BitmapUtils.ARGB_8888_BYTE_SIZE
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MathUtils
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.math.max
import kotlin.math.roundToInt
// As of Android 14 (API 34), `BitmapRegionDecoder` documentation states
@ -26,16 +31,10 @@ import kotlin.math.roundToInt
class RegionFetcher internal constructor(
private val context: Context,
) {
private var lastDecoderRef: LastDecoderRef? = null
private val pageTempUris = HashMap<Pair<Uri, Int>, Uri>()
private val multiTrackGlideOptions = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
suspend fun fetch(
// returns decoded bytes in ARGB_8888, with trailer bytes:
// - width (int32)
// - height (int32)
fun fetch(
uri: Uri,
mimeType: String,
pageId: Int?,
@ -43,86 +42,117 @@ class RegionFetcher internal constructor(
regionRect: Rect,
imageWidth: Int,
imageHeight: Int,
requestKey: Pair<Uri, Int?> = Pair(uri, pageId),
result: MethodChannel.Result,
) {
if (pageId != null && MultiPageImage.isSupported(mimeType)) {
val id = Pair(uri, pageId)
// use JPEG export for requested page
fetch(
uri = pageTempUris.getOrPut(id) { createJpegForPage(uri, mimeType, pageId) },
uri = exportUris.getOrPut(requestKey) { createTemporaryJpegExport(uri, mimeType, pageId) },
mimeType = MimeTypes.JPEG,
pageId = null,
sampleSize = sampleSize,
regionRect = regionRect,
imageWidth = imageWidth,
imageHeight = imageHeight,
requestKey = requestKey,
result = result,
)
return
}
val options = BitmapFactory.Options().apply {
inSampleSize = sampleSize
}
var currentDecoderRef = lastDecoderRef
if (currentDecoderRef != null && currentDecoderRef.uri != uri) {
currentDecoderRef = null
}
try {
if (currentDecoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
BitmapRegionDecoderCompat.newInstance(input)
}
if (newDecoder == null) {
result.error("fetch-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null)
return
}
currentDecoderRef = LastDecoderRef(uri, newDecoder)
val decoder = getOrCreateDecoder(context, uri, requestKey)
if (decoder == null) {
result.error("fetch-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null)
return
}
val decoder = currentDecoderRef.decoder
lastDecoderRef = currentDecoderRef
// with raw images, the known image size may not match the decoded image size
// so we scale the requested region accordingly
val effectiveRect = if (imageWidth != decoder.width || imageHeight != decoder.height) {
var effectiveRect = regionRect
var effectiveSampleSize = sampleSize
if (imageWidth != decoder.width || imageHeight != decoder.height) {
val xf = decoder.width.toDouble() / imageWidth
val yf = decoder.height.toDouble() / imageHeight
Rect(
effectiveRect = Rect(
(regionRect.left * xf).roundToInt(),
(regionRect.top * yf).roundToInt(),
(regionRect.right * xf).roundToInt(),
(regionRect.bottom * yf).roundToInt(),
)
} else {
regionRect
val factor = MathUtils.highestPowerOf2((1 / max(xf, yf)).roundToInt())
if (factor > 1) {
effectiveSampleSize = max(1, effectiveSampleSize / factor)
}
}
// use `Long` as rect size could be unexpectedly large and go beyond `Int` max
val targetBitmapSizeBytes: Long = ARGB_8888_BYTE_SIZE.toLong() * effectiveRect.width() * effectiveRect.height() / sampleSize
val options = BitmapFactory.Options().apply {
inSampleSize = effectiveSampleSize
// Specifying preferred config and color space avoids the need for conversion afterwards,
// but may prevent decoding (e.g. from RGBA_1010102 to ARGB_8888 on some devices).
inPreferredConfig = PREFERRED_CONFIG
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
}
}
val pixelCount = effectiveRect.width() * effectiveRect.height() / effectiveSampleSize
val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), options.inPreferredConfig)
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
// decoding a region that large would yield an OOM when creating the bitmap
result.error("fetch-large-region", "Region too large for uri=$uri regionRect=$regionRect", null)
return
}
val bitmap = decoder.decodeRegion(effectiveRect, options)
if (bitmap != null) {
result.success(bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = true))
var bitmap = decoder.decodeRegion(effectiveRect, options)
if (bitmap == null) {
// retry without specifying config or color space,
// falling back to custom byte conversion afterwards
options.inPreferredConfig = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && options.inPreferredColorSpace != null) {
options.inPreferredColorSpace = null
}
bitmap = decoder.decodeRegion(effectiveRect, options)
}
val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true)
if (bytes != null) {
result.success(bytes)
} else {
result.error("fetch-null", "failed to decode region for uri=$uri regionRect=$regionRect", null)
}
} catch (e: Exception) {
if (mimeType != MimeTypes.JPEG) {
// retry with JPEG export on failure,
// as some formats are not fully supported by `BitmapRegionDecoder`
fetch(
uri = exportUris.getOrPut(requestKey) { createTemporaryJpegExport(uri, mimeType, pageId) },
mimeType = MimeTypes.JPEG,
pageId = null,
sampleSize = sampleSize,
regionRect = regionRect,
imageWidth = imageWidth,
imageHeight = imageHeight,
requestKey = requestKey,
result = result,
)
return
}
result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message)
}
}
private fun createJpegForPage(sourceUri: Uri, mimeType: String, pageId: Int): Uri {
private fun createTemporaryJpegExport(uri: Uri, mimeType: String, pageId: Int?): Uri {
Log.d(LOG_TAG, "create JPEG export for uri=$uri mimeType=$mimeType pageId=$pageId")
val target = Glide.with(context)
.asBitmap()
.apply(multiTrackGlideOptions)
.load(MultiPageImage(context, sourceUri, mimeType, pageId))
.apply(AvesAppGlideModule.uncachedFullImageOptions)
.load(AvesAppGlideModule.getModel(context, uri, mimeType, pageId))
.submit()
try {
val bitmap = target.get()
val tempFile = StorageUtils.createTempFile(context).apply {
@ -136,8 +166,40 @@ class RegionFetcher internal constructor(
}
}
private data class LastDecoderRef(
val uri: Uri,
private data class DecoderRef(
val requestKey: Pair<Uri, Int?>,
val decoder: BitmapRegionDecoder,
)
companion object {
private val LOG_TAG = LogUtils.createTag<RegionFetcher>()
private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
private const val DECODER_POOL_SIZE = 3
private val decoderPool = ArrayList<DecoderRef>()
private val exportUris = HashMap<Pair<Uri, Int?>, Uri>()
private val poolLock = ReentrantLock()
private fun getOrCreateDecoder(context: Context, uri: Uri, requestKey: Pair<Uri, Int?>): BitmapRegionDecoder? {
poolLock.withLock {
var decoderRef = decoderPool.firstOrNull { it.requestKey == requestKey }
if (decoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
BitmapRegionDecoderCompat.newInstance(input)
}
if (newDecoder == null) {
return null
}
decoderRef = DecoderRef(requestKey, newDecoder)
} else {
decoderPool.remove(decoderRef)
}
decoderPool.add(0, decoderRef)
while (decoderPool.size > DECODER_POOL_SIZE) {
decoderPool.removeAt(decoderPool.size - 1)
}
return decoderRef.decoder
}
}
}
}

View file

@ -6,25 +6,25 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.RectF
import android.net.Uri
import androidx.core.graphics.createBitmap
import com.caverock.androidsvg.PreserveAspectRatio
import com.caverock.androidsvg.RenderOptions
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
import deckers.thibault.aves.metadata.SVGParserBufferedInputStream
import deckers.thibault.aves.metadata.SvgHelper.normalizeSize
import deckers.thibault.aves.utils.BitmapUtils.ARGB_8888_BYTE_SIZE
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.math.ceil
class SvgRegionFetcher internal constructor(
private val context: Context,
) {
private var lastSvgRef: LastSvgRef? = null
suspend fun fetch(
fun fetch(
uri: Uri,
sizeBytes: Long?,
scale: Int,
@ -39,32 +39,12 @@ class SvgRegionFetcher internal constructor(
return
}
var currentSvgRef = lastSvgRef
if (currentSvgRef != null && currentSvgRef.uri != uri) {
currentSvgRef = null
}
try {
if (currentSvgRef == null) {
val newSvg = StorageUtils.openInputStream(context, uri)?.use { input ->
try {
SVG.getFromInputStream(SVGParserBufferedInputStream(input))
} catch (ex: SVGParseException) {
result.error("fetch-parse", "failed to parse SVG for uri=$uri regionRect=$regionRect", null)
return
}
}
if (newSvg == null) {
result.error("fetch-read-null", "failed to open file for uri=$uri regionRect=$regionRect", null)
return
}
newSvg.normalizeSize()
currentSvgRef = LastSvgRef(uri, newSvg)
val svg = getOrCreateDecoder(context, uri)
if (svg == null) {
result.error("fetch-read-null", "failed to open file for uri=$uri regionRect=$regionRect", null)
return
}
val svg = currentSvgRef.svg
lastSvgRef = currentSvgRef
// we scale the requested region accordingly to the viewbox size
val viewBox = svg.documentViewBox
@ -91,32 +71,65 @@ class SvgRegionFetcher internal constructor(
val targetBitmapWidth = regionRect.width()
val targetBitmapHeight = regionRect.height()
val canvasWidth = targetBitmapWidth + bleedX * 2
val canvasHeight = targetBitmapHeight + bleedY * 2
// use `Long` as rect size could be unexpectedly large and go beyond `Int` max
val targetBitmapSizeBytes: Long = ARGB_8888_BYTE_SIZE.toLong() * targetBitmapWidth * targetBitmapHeight
val config = PREFERRED_CONFIG
val pixelCount = canvasWidth * canvasHeight
val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), config)
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
// decoding a region that large would yield an OOM when creating the bitmap
result.error("fetch-read-large-region", "SVG region too large for uri=$uri regionRect=$regionRect", null)
return
}
var bitmap = Bitmap.createBitmap(
targetBitmapWidth + bleedX * 2,
targetBitmapHeight + bleedY * 2,
Bitmap.Config.ARGB_8888
)
var bitmap = createBitmap(canvasWidth, canvasHeight, config)
val canvas = Canvas(bitmap)
svg.renderToCanvas(canvas, renderOptions)
bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight)
result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true)
result.success(bytes)
} catch (e: SVGParseException) {
result.error("fetch-parse", "failed to parse SVG for uri=$uri regionRect=$regionRect", null)
} catch (e: Exception) {
result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message)
}
}
private data class LastSvgRef(
private data class DecoderRef(
val uri: Uri,
val svg: SVG,
val decoder: SVG,
)
companion object {
private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
private const val DECODER_POOL_SIZE = 3
private val decoderPool = ArrayList<DecoderRef>()
private val poolLock = ReentrantLock()
private fun getOrCreateDecoder(context: Context, uri: Uri): SVG? {
poolLock.withLock {
var decoderRef = decoderPool.firstOrNull { it.uri == uri }
if (decoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
SVG.getFromInputStream(SVGParserBufferedInputStream(input))
}
if (newDecoder == null) {
return null
}
newDecoder.normalizeSize()
decoderRef = DecoderRef(uri, newDecoder)
} else {
decoderPool.remove(decoderRef)
}
decoderPool.add(0, decoderRef)
while (decoderPool.size > DECODER_POOL_SIZE) {
decoderPool.removeAt(decoderPool.size - 1)
}
return decoderRef.decoder
}
}
}
}

View file

@ -5,19 +5,21 @@ import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.util.Log
import android.util.Size
import androidx.annotation.RequiresApi
import androidx.core.graphics.scale
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.signature.ObjectKey
import deckers.thibault.aves.decoder.AvesAppGlideModule
import deckers.thibault.aves.decoder.MultiPageImage
import deckers.thibault.aves.decoder.SvgImage
import deckers.thibault.aves.decoder.TiffImage
import deckers.thibault.aves.decoder.VideoThumbnail
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.SVG
import deckers.thibault.aves.utils.MimeTypes.isVideo
@ -26,12 +28,14 @@ import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodChannel
import kotlin.math.min
import kotlin.math.roundToInt
class ThumbnailFetcher internal constructor(
private val context: Context,
uri: String,
private val mimeType: String,
private val dateModifiedSecs: Long,
private val dateModifiedMillis: Long,
private val rotationDegrees: Int,
private val isFlipped: Boolean,
width: Int?,
@ -41,7 +45,7 @@ class ThumbnailFetcher internal constructor(
private val quality: Int,
private val result: MethodChannel.Result,
) {
private val uri: Uri = Uri.parse(uri)
private val uri: Uri = uri.toUri()
private val width: Int = if (width?.takeIf { it > 0 } != null) width else defaultSize
private val height: Int = if (height?.takeIf { it > 0 } != null) height else defaultSize
private val svgFetch = mimeType == SVG
@ -49,7 +53,7 @@ class ThumbnailFetcher internal constructor(
private val multiPageFetch = pageId != null && MultiPageImage.isSupported(mimeType)
private val customFetch = svgFetch || tiffFetch || multiPageFetch
suspend fun fetch() {
fun fetch() {
var bitmap: Bitmap? = null
var exception: Exception? = null
@ -79,7 +83,33 @@ class ThumbnailFetcher internal constructor(
}
if (bitmap != null) {
result.success(bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = false, quality = quality))
if (bitmap.width > width && bitmap.height > height) {
val scalingFactor: Double = min(bitmap.width.toDouble() / width, bitmap.height.toDouble() / height)
val dstWidth = (bitmap.width / scalingFactor).roundToInt()
val dstHeight = (bitmap.height / scalingFactor).roundToInt()
Log.d(
LOG_TAG, "rescale thumbnail for mimeType=$mimeType uri=$uri width=$width height=$height" +
", with bitmap byteCount=${bitmap.byteCount} size=${bitmap.width}x${bitmap.height}" +
", to target=${dstWidth}x${dstHeight}"
)
bitmap = bitmap.scale(dstWidth, dstHeight)
}
if (bitmap.byteCount > BITMAP_SIZE_DANGER_THRESHOLD) {
result.error(
"getThumbnail-large", "thumbnail bitmap dangerously large" +
" for mimeType=$mimeType uri=$uri pageId=$pageId width=$width height=$height" +
", with bitmap byteCount=${bitmap.byteCount} size=${bitmap.width}x${bitmap.height} config=${bitmap.config?.name}", null
)
return
}
}
// do not recycle bitmaps fetched from `ContentResolver` or Glide as their lifecycle is unknown
val recycle = false
val bytes = BitmapUtils.getRawBytes(bitmap, recycle = recycle)
if (bytes != null) {
result.success(bytes)
} else {
var errorDetails: String? = exception?.message
if (errorDetails?.isNotEmpty() == true) {
@ -120,30 +150,18 @@ class ThumbnailFetcher internal constructor(
// add signature to ignore cache for images which got modified but kept the same URI
var options = RequestOptions()
.format(if (quality == 100) DecodeFormat.PREFER_ARGB_8888 else DecodeFormat.PREFER_RGB_565)
.signature(ObjectKey("$dateModifiedSecs-$rotationDegrees-$isFlipped-$width-$pageId"))
.signature(ObjectKey("$dateModifiedMillis-$rotationDegrees-$isFlipped-$width-$pageId"))
.override(width, height)
val target = if (isVideo(mimeType)) {
if (isVideo(mimeType)) {
options = options.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
Glide.with(context)
.asBitmap()
.apply(options)
.load(VideoThumbnail(context, uri))
.submit(width, height)
} else {
val model: Any = when {
svgFetch -> SvgImage(context, uri)
tiffFetch -> TiffImage(context, uri, pageId)
multiPageFetch -> MultiPageImage(context, uri, mimeType, pageId)
else -> StorageUtils.getGlideSafeUri(context, uri, mimeType)
}
Glide.with(context)
.asBitmap()
.apply(options)
.load(model)
.submit(width, height)
}
val target = Glide.with(context)
.asBitmap()
.apply(options)
.load(AvesAppGlideModule.getModel(context, uri, mimeType, pageId))
.submit(width, height)
return try {
var bitmap = target.get()
if (needRotationAfterGlide(mimeType, pageId)) {
@ -154,4 +172,9 @@ class ThumbnailFetcher internal constructor(
Glide.with(context).clear(target)
}
}
companion object {
private val LOG_TAG = LogUtils.createTag<ThumbnailFetcher>()
private const val BITMAP_SIZE_DANGER_THRESHOLD = 20 * (1 shl 20) // MB
}
}

View file

@ -1,9 +1,10 @@
package deckers.thibault.aves.channel.calls.fetchers
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
import android.net.Uri
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.BitmapUtils
import io.flutter.plugin.common.MethodChannel
import org.beyka.tiffbitmapfactory.DecodeArea
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
@ -11,7 +12,7 @@ import org.beyka.tiffbitmapfactory.TiffBitmapFactory
class TiffRegionFetcher internal constructor(
private val context: Context,
) {
suspend fun fetch(
fun fetch(
uri: Uri,
page: Int,
sampleSize: Int,
@ -31,9 +32,10 @@ class TiffRegionFetcher internal constructor(
inSampleSize = sampleSize
inDecodeArea = DecodeArea(regionRect.left, regionRect.top, regionRect.width(), regionRect.height())
}
val bitmap = TiffBitmapFactory.decodeFileDescriptor(fd, options)
if (bitmap != null) {
result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
val bitmap: Bitmap? = TiffBitmapFactory.decodeFileDescriptor(fd, options)
val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true)
if (bytes != null) {
result.success(bytes)
} else {
result.error("getRegion-tiff-null", "failed to decode region for uri=$uri page=$page regionRect=$regionRect", null)
}

View file

@ -77,19 +77,30 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
)
}
override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.getDisplayCompat()?.isHdr ?: false)
override fun supportsWideGamut(call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.resources.configuration.isScreenWideColorGamut)
}
override fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result) {
val on = call.argument<Boolean>("on")
if (on == null) {
result.error("setHdrColorMode-args", "missing arguments", null)
override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.resources.configuration.isScreenHdr)
}
override fun setColorMode(call: MethodCall, result: MethodChannel.Result) {
val wideColorGamut = call.argument<Boolean>("wideColorGamut")
val hdr = call.argument<Boolean>("hdr")
if (wideColorGamut == null || hdr == null) {
result.error("setColorMode-args", "missing arguments", null)
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.window.colorMode = if (on) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT
activity.window.colorMode = if (hdr) {
ActivityInfo.COLOR_MODE_HDR
} else if (wideColorGamut) {
ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
} else {
ActivityInfo.COLOR_MODE_DEFAULT
}
}
result.success(null)
}

View file

@ -29,11 +29,15 @@ class ServiceWindowHandler(service: Service) : WindowHandler(service) {
result.success(HashMap<String, Any>())
}
override fun supportsWideGamut(call: MethodCall, result: MethodChannel.Result) {
result.success(false)
}
override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
result.success(false)
}
override fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result) {
override fun setColorMode(call: MethodCall, result: MethodChannel.Result) {
result.success(null)
}
}

View file

@ -18,8 +18,9 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
"isCutoutAware" -> Coresult.safe(call, result, ::isCutoutAware)
"getCutoutInsets" -> Coresult.safe(call, result, ::getCutoutInsets)
"supportsWideGamut" -> Coresult.safe(call, result, ::supportsWideGamut)
"supportsHdr" -> Coresult.safe(call, result, ::supportsHdr)
"setHdrColorMode" -> Coresult.safe(call, result, ::setHdrColorMode)
"setColorMode" -> Coresult.safe(call, result, ::setColorMode)
else -> result.notImplemented()
}
}
@ -46,9 +47,11 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
abstract fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result)
abstract fun supportsWideGamut(call: MethodCall, result: MethodChannel.Result)
abstract fun supportsHdr(call: MethodCall, result: MethodChannel.Result)
abstract fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result)
abstract fun setColorMode(call: MethodCall, result: MethodChannel.Result)
companion object {
private val LOG_TAG = LogUtils.createTag<WindowHandler>()

View file

@ -7,6 +7,7 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.net.toUri
import deckers.thibault.aves.MainActivity
import deckers.thibault.aves.PendingStorageAccessResultHandler
import deckers.thibault.aves.channel.calls.AppAdapterHandler
@ -48,6 +49,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
"requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() }
"createFile" -> ioScope.launch { createFile() }
"openFile" -> ioScope.launch { openFile() }
"copyFile" -> ioScope.launch { copyFile() }
"edit" -> edit()
"pickCollectionFilters" -> pickCollectionFilters()
else -> endOfStream()
@ -61,11 +63,6 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
error("requestDirectoryAccess-unsupported", "directory access is not allowed before Android Lollipop", null)
return
}
PermissionManager.requestDirectoryAccess(activity, ensureTrailingSeparator(path), {
success(true)
endOfStream()
@ -76,7 +73,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
}
private fun requestMediaFileAccess() {
val uris = (args["uris"] as List<*>?)?.mapNotNull { if (it is String) Uri.parse(it) else null }
val uris = (args["uris"] as List<*>?)?.mapNotNull { if (it is String) it.toUri() else null }
val mimeTypes = (args["mimeTypes"] as List<*>?)?.mapNotNull { if (it is String) it else null }
if (uris.isNullOrEmpty() || mimeTypes == null || mimeTypes.size != uris.size) {
error("requestMediaFileAccess-args", "missing arguments", null)
@ -185,6 +182,49 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
safeStartActivityForStorageAccessResult(intent, MainActivity.OPEN_FILE_REQUEST, ::onGranted, ::onDenied)
}
private suspend fun copyFile() {
val name = args["name"] as String?
val mimeType = args["mimeType"] as String?
val sourceUri = (args["sourceUri"] as String?)?.toUri()
if (name == null || mimeType == null || sourceUri == null) {
error("copyFile-args", "missing arguments", null)
return
}
fun onGranted(uri: Uri) {
ioScope.launch {
try {
StorageUtils.openInputStream(activity, sourceUri)?.use { input ->
// truncate is necessary when overwriting a longer file
activity.contentResolver.openOutputStream(uri, "wt")?.use { output ->
val buffer = ByteArray(BUFFER_SIZE)
var len: Int
while (input.read(buffer).also { len = it } != -1) {
output.write(buffer, 0, len)
}
}
}
success(true)
} catch (e: Exception) {
error("copyFile-write", "failed to copy file from sourceUri=$sourceUri to uri=$uri", e.message)
}
endOfStream()
}
}
fun onDenied() {
success(null)
endOfStream()
}
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = mimeType
putExtra(Intent.EXTRA_TITLE, name)
}
safeStartActivityForStorageAccessResult(intent, MainActivity.CREATE_FILE_REQUEST, ::onGranted, ::onDenied)
}
private fun edit() {
val uri = args["uri"] as String?
val mimeType = args["mimeType"] as String? // optional
@ -195,7 +235,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
val intent = Intent(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.setDataAndType(AppAdapterHandler.getShareableUri(activity, Uri.parse(uri)), mimeType)
.setDataAndType(AppAdapterHandler.getShareableUri(activity, uri.toUri()), mimeType)
if (intent.resolveActivity(activity.packageManager) == null) {
error("edit-resolve", "cannot resolve activity for this intent for uri=$uri mimeType=$mimeType", null)

View file

@ -5,15 +5,11 @@ import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import deckers.thibault.aves.decoder.MultiPageImage
import deckers.thibault.aves.decoder.TiffImage
import deckers.thibault.aves.decoder.VideoThumbnail
import deckers.thibault.aves.decoder.AvesAppGlideModule
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.MimeTypes
@ -28,6 +24,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream
import java.io.InputStream
class ImageByteStreamHandler(private val context: Context, private val arguments: Any?) : EventChannel.StreamHandler {
@ -84,11 +81,13 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
return
}
val decoded = arguments["decoded"] as Boolean
val mimeType = arguments["mimeType"] as String?
val uri = (arguments["uri"] as String?)?.let { Uri.parse(it) }
val uri = (arguments["uri"] as String?)?.toUri()
val sizeBytes = (arguments["sizeBytes"] as Number?)?.toLong()
val rotationDegrees = arguments["rotationDegrees"] as Int
val isFlipped = arguments["isFlipped"] as Boolean
val isAnimated = arguments["isAnimated"] as Boolean
val pageId = arguments["pageId"] as Int?
if (mimeType == null || uri == null) {
@ -97,19 +96,31 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
return
}
if (isVideo(mimeType)) {
streamVideoByGlide(uri, mimeType, sizeBytes)
} else if (!canDecodeWithFlutter(mimeType, pageId, rotationDegrees, isFlipped)) {
// decode exotic format on platform side, then encode it in portable format for Flutter
streamImageByGlide(uri, pageId, mimeType, sizeBytes, rotationDegrees, isFlipped)
} else {
if (canDecodeWithFlutter(mimeType, isAnimated) && !decoded) {
// to be decoded by Flutter
streamImageAsIs(uri, mimeType, sizeBytes)
streamOriginalEncodedBytes(uri, mimeType, sizeBytes)
} else if (isVideo(mimeType)) {
streamVideoByGlide(
uri = uri,
mimeType = mimeType,
sizeBytes = sizeBytes,
decoded = decoded,
)
} else {
streamImageByGlide(
uri = uri,
pageId = pageId,
mimeType = mimeType,
sizeBytes = sizeBytes,
rotationDegrees = rotationDegrees,
isFlipped = isFlipped,
decoded = decoded,
)
}
endOfStream()
}
private fun streamImageAsIs(uri: Uri, mimeType: String, sizeBytes: Long?) {
private fun streamOriginalEncodedBytes(uri: Uri, mimeType: String, sizeBytes: Long?) {
if (!MemoryUtils.canAllocate(sizeBytes)) {
error("streamImage-image-read-large", "original image too large at $sizeBytes bytes, for mimeType=$mimeType uri=$uri", null)
return
@ -129,19 +140,12 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
sizeBytes: Long?,
rotationDegrees: Int,
isFlipped: Boolean,
decoded: Boolean,
) {
val model: Any = if (pageId != null && MultiPageImage.isSupported(mimeType)) {
MultiPageImage(context, uri, mimeType, pageId)
} else if (mimeType == MimeTypes.TIFF) {
TiffImage(context, uri, pageId)
} else {
StorageUtils.getGlideSafeUri(context, uri, mimeType, sizeBytes)
}
val target = Glide.with(context)
.asBitmap()
.apply(glideOptions)
.load(model)
.apply(AvesAppGlideModule.uncachedFullImageOptions)
.load(AvesAppGlideModule.getModel(context, uri, mimeType, pageId, sizeBytes))
.submit()
try {
var bitmap = withContext(Dispatchers.IO) { target.get() }
@ -149,9 +153,16 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
bitmap = applyExifOrientation(context, bitmap, rotationDegrees, isFlipped)
}
if (bitmap != null) {
val bytes = bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = false)
// do not recycle bitmaps fetched from Glide as their lifecycle is unknown
val recycle = false
val bytes = if (decoded) {
BitmapUtils.getRawBytes(bitmap, recycle = recycle)
} else {
BitmapUtils.getEncodedBytes(bitmap, canHaveAlpha = MimeTypes.canHaveAlpha(mimeType), recycle = recycle)
}
if (MemoryUtils.canAllocate(sizeBytes)) {
success(bytes)
streamBytes(ByteArrayInputStream(bytes))
} else {
error("streamImage-image-decode-large", "decoded image too large at $sizeBytes bytes, for mimeType=$mimeType uri=$uri", null)
}
@ -159,24 +170,31 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
error("streamImage-image-decode-null", "failed to get image for mimeType=$mimeType uri=$uri", null)
}
} catch (e: Exception) {
error("streamImage-image-decode-exception", "failed to get image for mimeType=$mimeType uri=$uri model=$model", toErrorDetails(e))
error("streamImage-image-decode-exception", "failed to get image for mimeType=$mimeType uri=$uri", toErrorDetails(e))
} finally {
Glide.with(context).clear(target)
}
}
private suspend fun streamVideoByGlide(uri: Uri, mimeType: String, sizeBytes: Long?) {
private suspend fun streamVideoByGlide(uri: Uri, mimeType: String, sizeBytes: Long?, decoded: Boolean) {
val target = Glide.with(context)
.asBitmap()
.apply(glideOptions)
.load(VideoThumbnail(context, uri))
.apply(AvesAppGlideModule.uncachedFullImageOptions)
.load(AvesAppGlideModule.getModel(context, uri, mimeType, null, sizeBytes))
.submit()
try {
val bitmap = withContext(Dispatchers.IO) { target.get() }
if (bitmap != null) {
val bytes = bitmap.getBytes(canHaveAlpha = false, recycle = false)
// do not recycle bitmaps fetched from Glide as their lifecycle is unknown
val recycle = false
val bytes = if (decoded) {
BitmapUtils.getRawBytes(bitmap, recycle = recycle)
} else {
BitmapUtils.getEncodedBytes(bitmap, canHaveAlpha = false, recycle = recycle)
}
if (MemoryUtils.canAllocate(sizeBytes)) {
success(bytes)
streamBytes(ByteArrayInputStream(bytes))
} else {
error("streamImage-video-large", "decoded image too large at $sizeBytes bytes, for mimeType=$mimeType uri=$uri", null)
}
@ -218,11 +236,5 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
const val CHANNEL = "deckers.thibault/aves/media_byte_stream"
private const val BUFFER_SIZE = 2 shl 17 // 256kB
// request a fresh image with the highest quality format
private val glideOptions = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
}
}

View file

@ -1,10 +1,10 @@
package deckers.thibault.aves.channel.streams
import android.net.Uri
import android.app.Activity
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.fragment.app.FragmentActivity
import androidx.core.net.toUri
import deckers.thibault.aves.channel.calls.MediaEditHandler.Companion.cancelledOps
import deckers.thibault.aves.model.AvesEntry
import deckers.thibault.aves.model.FieldMap
@ -21,9 +21,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.*
class ImageOpStreamHandler(private val activity: FragmentActivity, private val arguments: Any?) : EventChannel.StreamHandler {
class ImageOpStreamHandler(private val activity: Activity, private val arguments: Any?) : EventChannel.StreamHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private lateinit var eventSink: EventSink
private lateinit var handler: Handler
@ -142,7 +141,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a
// assume same provider for all entries
val firstEntry = entryMapList.first()
val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(activity, it) }
val provider = (firstEntry["uri"] as String?)?.toUri()?.let { getProvider(activity, it) }
if (provider == null) {
error("convert-provider", "failed to find provider for entry=$firstEntry", null)
return

View file

@ -31,9 +31,15 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
init {
Log.i(LOG_TAG, "start listening to Media Store")
context.contentResolver.apply {
registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
try {
context.contentResolver.apply {
registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
}
} catch (e: SecurityException) {
// Trying to register an observer may yield a security exception with this message:
// "Failed to find provider media for user 0; expected to find a valid ContentProvider for this authority"
Log.w(LOG_TAG, "failed to register content observer", e)
}
}

View file

@ -19,12 +19,13 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
private lateinit var eventSink: EventSink
private lateinit var handler: Handler
private var knownEntries: Map<Long?, Int?>? = null
// knownEntries: map of contentId -> dateModifiedMillis
private var knownEntries: Map<Long?, Long?>? = null
private var directory: String? = null
init {
if (arguments is Map<*, *>) {
knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to (it.value as Number?)?.toLong() }?.toMap()
directory = arguments["directory"] as String?
}
}

View file

@ -7,6 +7,7 @@ import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.util.Log
import android.view.ViewConfiguration
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.EventChannel
@ -21,6 +22,7 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
private val contentObserver = object : ContentObserver(null) {
private var accelerometerRotation: Int = 0
private var transitionAnimationScale: Float = 1f
private var longPressTimeoutMillis: Int = 0
init {
update()
@ -36,6 +38,7 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
hashMapOf(
Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation,
Settings.Global.TRANSITION_ANIMATION_SCALE to transitionAnimationScale,
KEY_LONG_PRESS_TIMEOUT_MILLIS to longPressTimeoutMillis,
)
)
}
@ -54,6 +57,11 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
transitionAnimationScale = newTransitionAnimationScale
changed = true
}
val newLongPressTimeout = ViewConfiguration.getLongPressTimeout()
if (longPressTimeoutMillis != newLongPressTimeout) {
longPressTimeoutMillis = newLongPressTimeout
changed = true
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null)
}
@ -93,5 +101,8 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
companion object {
private val LOG_TAG = LogUtils.createTag<SettingsChangeStreamHandler>()
const val CHANNEL = "deckers.thibault/aves/settings_change"
// cf `Settings.Secure.LONG_PRESS_TIMEOUT`
const val KEY_LONG_PRESS_TIMEOUT_MILLIS = "long_press_timeout"
}
}

View file

@ -1,14 +1,30 @@
package deckers.thibault.aves.decoder
import android.content.Context
import android.net.Uri
import android.text.format.Formatter
import android.util.Log
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.ImageHeaderParser
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter
import com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool
import com.bumptech.glide.load.engine.cache.DiskCache
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.cache.LruResourceCache
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
import com.bumptech.glide.load.resource.bitmap.ExifInterfaceImageHeaderParser
import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.compatRemoveIf
@GlideModule
@ -16,6 +32,30 @@ class AvesAppGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
// hide noisy warning (e.g. for images that can't be decoded)
builder.setLogLevel(Log.ERROR)
// sizing
val memorySizeCalculator = MemorySizeCalculator.Builder(context).build()
builder.setMemorySizeCalculator(memorySizeCalculator)
val size: Int = memorySizeCalculator.bitmapPoolSize
if (size > 0) {
builder.setBitmapPool(LruBitmapPool(size.toLong()))
} else {
builder.setBitmapPool(BitmapPoolAdapter())
}
builder.setArrayPool(LruArrayPool(memorySizeCalculator.arrayPoolSizeInBytes))
builder.setMemoryCache(LruResourceCache(memorySizeCalculator.memoryCacheSize.toLong()))
val diskCacheSize = DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE
val internalCacheDiskCacheFactory = InternalCacheDiskCacheFactory(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize.toLong())
builder.setDiskCache(internalCacheDiskCacheFactory)
fun toMb(bytes: Int) = Formatter.formatFileSize(context, bytes.toLong())
Log.d(
LOG_TAG, "Glide disk cache size=${toMb(diskCacheSize)}" +
", memory cache size=${toMb(memorySizeCalculator.memoryCacheSize)}" +
", bitmap pool size=${toMb(memorySizeCalculator.bitmapPoolSize)}" +
", array pool size=${toMb(memorySizeCalculator.arrayPoolSizeInBytes)}"
)
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
@ -25,4 +65,28 @@ class AvesAppGlideModule : AppGlideModule() {
}
override fun isManifestParsingEnabled(): Boolean = false
companion object {
private val LOG_TAG = LogUtils.createTag<AvesAppGlideModule>()
// request a fresh image with the highest quality format
val uncachedFullImageOptions = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
fun getModel(context: Context, uri: Uri, mimeType: String, pageId: Int?, sizeBytes: Long? = null): Any {
return if (pageId != null && MultiPageImage.isSupported(mimeType)) {
MultiPageImage(context, uri, mimeType, pageId)
} else if (mimeType == MimeTypes.TIFF) {
TiffImage(context, uri, pageId)
} else if (mimeType == MimeTypes.SVG) {
SvgImage(context, uri)
} else if (isVideo(mimeType)) {
VideoThumbnail(context, uri)
} else {
StorageUtils.getGlideSafeUri(context, uri, mimeType, sizeBytes)
}
}
}
}

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import androidx.core.graphics.createBitmap
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.Registry
@ -68,7 +69,7 @@ internal class SvgFetcher(val model: SvgImage, val width: Int, val height: Int)
bitmapWidth = width
bitmapHeight = ceil(svgHeight * width / svgWidth).toInt()
}
val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
val bitmap = createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
svg.renderToCanvas(canvas)

View file

@ -3,6 +3,7 @@ package deckers.thibault.aves.decoder
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import androidx.core.graphics.scale
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.Registry
@ -82,7 +83,9 @@ internal class TiffFetcher(val model: TiffImage, val width: Int, val height: Int
inSampleSize = sampleSize
}
try {
val bitmap = TiffBitmapFactory.decodeFileDescriptor(fd, options)
val bitmap: Bitmap? = TiffBitmapFactory.decodeFileDescriptor(fd, options)
// calling `TiffBitmapFactory.closeFd(fd)` after decoding yields a segmentation fault
if (bitmap == null) {
callback.onLoadFailed(Exception("Decoding full TIFF yielded null bitmap"))
} else if (customSize) {
@ -96,7 +99,7 @@ internal class TiffFetcher(val model: TiffImage, val width: Int, val height: Int
dstWidth = width
dstHeight = (width / aspectRatio).toInt()
}
callback.onDataReady(Bitmap.createScaledBitmap(bitmap, dstWidth, dstHeight, true))
callback.onDataReady(bitmap.scale(dstWidth, dstHeight))
} else {
callback.onDataReady(bitmap)
}

View file

@ -2,6 +2,7 @@ package deckers.thibault.aves.decoder
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
@ -20,7 +21,6 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.module.LibraryGlideModule
import com.bumptech.glide.signature.ObjectKey
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.StorageUtils.openMetadataRetriever
import kotlinx.coroutines.CoroutineScope
@ -28,45 +28,54 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.io.IOException
import kotlin.math.ceil
import kotlin.math.roundToInt
@GlideModule
class VideoThumbnailGlideModule : LibraryGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry.append(VideoThumbnail::class.java, InputStream::class.java, VideoThumbnailLoader.Factory())
registry.append(VideoThumbnail::class.java, Bitmap::class.java, VideoThumbnailLoader.Factory())
}
}
class VideoThumbnail(val context: Context, val uri: Uri)
internal class VideoThumbnailLoader : ModelLoader<VideoThumbnail, InputStream> {
override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
internal class VideoThumbnailLoader : ModelLoader<VideoThumbnail, Bitmap> {
override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
return ModelLoader.LoadData(ObjectKey(model.uri), VideoThumbnailFetcher(model, width, height))
}
override fun handles(model: VideoThumbnail): Boolean = true
internal class Factory : ModelLoaderFactory<VideoThumbnail, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<VideoThumbnail, InputStream> = VideoThumbnailLoader()
internal class Factory : ModelLoaderFactory<VideoThumbnail, Bitmap> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<VideoThumbnail, Bitmap> = VideoThumbnailLoader()
override fun teardown() {}
}
}
internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val width: Int, val height: Int) : DataFetcher<InputStream> {
internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val width: Int, val height: Int) : DataFetcher<Bitmap> {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) {
override fun loadData(priority: Priority, callback: DataCallback<in Bitmap>) {
ioScope.launch {
val retriever = openMetadataRetriever(model.context, model.uri)
if (retriever == null) {
callback.onLoadFailed(Exception("failed to initialize MediaMetadataRetriever for uri=${model.uri}"))
} else {
try {
var bytes = retriever.embeddedPicture
if (bytes == null) {
var bitmap: Bitmap? = null
retriever.embeddedPicture?.let { bytes ->
try {
bitmap = BitmapFactory.decodeStream(ByteArrayInputStream(bytes))
} catch (e: IOException) {
// ignore
}
}
if (bitmap == null) {
// there is no consistent strategy across devices to match
// the thumbnails returned by the content resolver / Media Store
// so we derive one in an arbitrary way
@ -111,8 +120,9 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
}
// the returned frame is already rotated according to the video metadata
val frame = if (dstWidth > 0 && dstHeight > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
val targetBitmapSizeBytes: Long = FORMAT_BYTE_SIZE.toLong() * dstWidth * dstHeight
bitmap = if (dstWidth > 0 && dstHeight > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
val pixelCount = dstWidth * dstHeight
val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), getPreferredConfig())
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
throw Exception("not enough memory to allocate $targetBitmapSizeBytes bytes for the scaled frame at $dstWidth x $dstHeight")
}
@ -122,7 +132,8 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
retriever.getScaledFrameAtTime(timeMicros, option, dstWidth, dstHeight)
}
} else {
val targetBitmapSizeBytes: Long = (FORMAT_BYTE_SIZE.toLong() * videoWidth * videoHeight).toLong()
val pixelCount = videoWidth * videoHeight
val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), getPreferredConfig())
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
throw Exception("not enough memory to allocate $targetBitmapSizeBytes bytes for the full frame at $videoWidth x $videoHeight")
}
@ -132,13 +143,12 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
retriever.getFrameAtTime(timeMicros, option)
}
}
bytes = frame?.getBytes(canHaveAlpha = false, recycle = false)
}
if (bytes != null) {
callback.onDataReady(ByteArrayInputStream(bytes))
if (bitmap == null) {
callback.onLoadFailed(Exception("failed to get embedded picture or any frame for uri=${model.uri}"))
} else {
callback.onLoadFailed(Exception("failed to get embedded picture or any frame"))
callback.onDataReady(bitmap)
}
} catch (e: Exception) {
callback.onLoadFailed(e)
@ -151,13 +161,19 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
}
@RequiresApi(Build.VERSION_CODES.P)
private fun getBitmapParams() = MediaMetadataRetriever.BitmapParams().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
private fun getBitmapParams(): MediaMetadataRetriever.BitmapParams {
val params = MediaMetadataRetriever.BitmapParams()
params.preferredConfig = this.getPreferredConfig()
return params
}
private fun getPreferredConfig(): Bitmap.Config {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// improved precision with the same memory cost as `ARGB_8888` (4 bytes per pixel)
// for wide-gamut and HDR content which does not require alpha blending
setPreferredConfig(Bitmap.Config.RGBA_1010102)
Bitmap.Config.RGBA_1010102
} else {
setPreferredConfig(Bitmap.Config.ARGB_8888)
Bitmap.Config.ARGB_8888
}
}
@ -167,12 +183,7 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
// cannot cancel
override fun cancel() {}
override fun getDataClass(): Class<InputStream> = InputStream::class.java
override fun getDataClass(): Class<Bitmap> = Bitmap::class.java
override fun getDataSource(): DataSource = DataSource.LOCAL
companion object {
// same for either `ARGB_8888` or `RGBA_1010102`
private const val FORMAT_BYTE_SIZE = BitmapUtils.ARGB_8888_BYTE_SIZE
}
}

View file

@ -1,20 +1,24 @@
package deckers.thibault.aves.metadata
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.drew.lang.Rational
import com.drew.metadata.Directory
import com.drew.metadata.exif.*
import com.drew.metadata.exif.ExifDirectoryBase
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.exif.ExifThumbnailDirectory
import com.drew.metadata.exif.GpsDirectory
import com.drew.metadata.exif.PanasonicRawIFD0Directory
import com.drew.metadata.exif.makernotes.OlympusCameraSettingsMakernoteDirectory
import com.drew.metadata.exif.makernotes.OlympusImageProcessingMakernoteDirectory
import com.drew.metadata.exif.makernotes.OlympusMakernoteDirectory
import deckers.thibault.aves.utils.LogUtils
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import java.util.Locale
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.roundToLong
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
object ExifInterfaceHelper {
private val LOG_TAG = LogUtils.createTag<ExifInterfaceHelper>()
@ -22,7 +26,7 @@ object ExifInterfaceHelper {
val GPS_DATE_FORMAT = SimpleDateFormat("yyyy:MM:dd", Locale.ROOT)
val GPS_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.ROOT)
private const val precisionErrorTolerance = 1e-10
private const val PRECISION_ERROR_TOLERANCE = 1e-10
// ExifInterface always states it has the following attributes
// and returns "0" instead of "null" when they are actually missing
@ -220,7 +224,7 @@ object ExifInterfaceHelper {
// initialize metadata-extractor directories that we will fill
// by tags converted from the ExifInterface attributes
// so that we can rely on metadata-extractor descriptions
val dirs = DirType.values().associateWith { it.createDirectory() }
val dirs = DirType.entries.associateWith { it.createDirectory() }
// exclude Exif directory when it only includes image size
val isUselessExif = fun(it: Map<String, String>): Boolean {
@ -308,7 +312,7 @@ object ExifInterfaceHelper {
val numerator = 1L
val f = numerator / d
val denominator = f.roundToLong()
if (abs(f - denominator) < precisionErrorTolerance) {
if (abs(f - denominator) < PRECISION_ERROR_TOLERANCE) {
return Rational(numerator, denominator)
}
}

View file

@ -111,20 +111,25 @@ object MediaMetadataRetrieverHelper {
// format
MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION,
MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION -> "$value°"
MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT, MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH,
MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT, MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH -> "$value pixels"
MediaMetadataRetriever.METADATA_KEY_BITRATE -> {
val bitrate = value.toLongOrNull() ?: 0
if (bitrate > 0) formatBitrate(bitrate) else null
}
MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE -> {
val framerate = value.toDoubleOrNull() ?: 0.0
if (framerate > 0.0) "$framerate" else null
}
MediaMetadataRetriever.METADATA_KEY_DURATION -> {
val dateMillis = value.toLongOrNull() ?: 0
if (dateMillis > 0) durationFormat.format(Date(dateMillis)) else null
}
MediaMetadataRetriever.METADATA_KEY_COLOR_RANGE -> {
when (value.toIntOrNull()) {
MediaFormat.COLOR_RANGE_FULL -> "Full"
@ -132,6 +137,7 @@ object MediaMetadataRetrieverHelper {
else -> value
}
}
MediaMetadataRetriever.METADATA_KEY_COLOR_STANDARD -> {
when (value.toIntOrNull()) {
MediaFormat.COLOR_STANDARD_BT709 -> "BT.709"
@ -141,6 +147,7 @@ object MediaMetadataRetrieverHelper {
else -> value
}
}
MediaMetadataRetriever.METADATA_KEY_COLOR_TRANSFER -> {
when (value.toIntOrNull()) {
MediaFormat.COLOR_TRANSFER_LINEAR -> "Linear"
@ -154,6 +161,7 @@ object MediaMetadataRetrieverHelper {
MediaMetadataRetriever.METADATA_KEY_COMPILATION,
MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
MediaMetadataRetriever.METADATA_KEY_YEAR -> if (value != "0") value else null
MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER -> if (value != "0/0") value else null
MediaMetadataRetriever.METADATA_KEY_DATE -> {
val dateMillis = Metadata.parseVideoMetadataDate(value)
@ -168,4 +176,12 @@ object MediaMetadataRetrieverHelper {
}?.let { save(it) }
}
}
fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) {
if (this.containsKey(key)) save(this.getInteger(key))
}
fun MediaFormat.getSafeLong(key: String, save: (value: Long) -> Unit) {
if (this.containsKey(key)) save(this.getLong(key))
}
}

View file

@ -2,8 +2,9 @@ package deckers.thibault.aves.metadata
import android.content.Context
import android.net.Uri
import androidx.exifinterface.media.ExifInterface
import android.util.Log
import deckers.thibault.aves.utils.FileUtils.transferFrom
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
import java.io.File
@ -14,8 +15,11 @@ import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.regex.Pattern
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
object Metadata {
private val LOG_TAG = LogUtils.createTag<Metadata>()
const val IPTC_MARKER_BYTE: Byte = 0x1c
// Pattern to extract latitude & longitude from a video location tag (cf ISO 6709)
@ -120,7 +124,7 @@ object Metadata {
return date.time + parseSubSecond(subSecond)
}
// Opening large PSD/TIFF files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1),
// Opening some large files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1),
// so we define an arbitrary threshold to avoid a crash on launch.
// It is not clear whether it is because of the file itself or its metadata.
private const val FILE_SIZE_MAX = 100 * (1 shl 20) // MB
@ -134,15 +138,19 @@ object Metadata {
private val previewFiles = HashMap<Uri, File>()
private fun getSafeUri(context: Context, uri: Uri, mimeType: String, sizeBytes: Long?): Uri {
// formats known to yield OOM for large files
return when (mimeType) {
// formats known to yield OOM for large files
MimeTypes.DNG,
MimeTypes.DNG_ADOBE,
MimeTypes.HEIC,
MimeTypes.HEIF,
MimeTypes.MP4,
MimeTypes.PSD_VND,
MimeTypes.PSD_X,
MimeTypes.TIFF -> {
MimeTypes.TIFF ->
if (isDangerouslyLarge(sizeBytes)) {
Log.d(LOG_TAG, "Dangerously large file with uri=$uri, mimeType=$mimeType, size=$sizeBytes")
// make a preview from the beginning of the file,
// hoping the metadata is accessible in the copied chunk
var previewFile = previewFiles[uri]
@ -155,15 +163,18 @@ object Metadata {
// small enough to be safe as it is
uri
}
}
// *probably* safe
else -> uri
else ->
// *probably* safe
uri
}
}
fun createPreviewFile(context: Context, uri: Uri): File {
val size = PREVIEW_SIZE
Log.d(LOG_TAG, "create preview of size=$size for uri=$uri")
return StorageUtils.createTempFile(context).apply {
transferFrom(StorageUtils.openInputStream(context, uri), PREVIEW_SIZE)
transferFrom(StorageUtils.openInputStream(context, uri), size)
}
}

View file

@ -9,7 +9,11 @@ import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.toByteArray
import deckers.thibault.aves.utils.toHex
import org.mp4parser.*
import org.mp4parser.BasicContainer
import org.mp4parser.Box
import org.mp4parser.Container
import org.mp4parser.IsoFile
import org.mp4parser.PropertyBoxParserImpl
import org.mp4parser.boxes.UnknownBox
import org.mp4parser.boxes.UserBox
import org.mp4parser.boxes.apple.AppleCoverBox
@ -17,8 +21,18 @@ import org.mp4parser.boxes.apple.AppleGPSCoordinatesBox
import org.mp4parser.boxes.apple.AppleItemListBox
import org.mp4parser.boxes.apple.AppleVariableSignedIntegerBox
import org.mp4parser.boxes.apple.Utf8AppleDataBox
import org.mp4parser.boxes.iso14496.part12.*
import org.mp4parser.boxes.iso14496.part12.FreeBox
import org.mp4parser.boxes.iso14496.part12.HandlerBox
import org.mp4parser.boxes.iso14496.part12.MediaDataBox
import org.mp4parser.boxes.iso14496.part12.MetaBox
import org.mp4parser.boxes.iso14496.part12.MovieBox
import org.mp4parser.boxes.iso14496.part12.MovieFragmentBox
import org.mp4parser.boxes.iso14496.part12.SampleTableBox
import org.mp4parser.boxes.iso14496.part12.SegmentIndexBox
import org.mp4parser.boxes.iso14496.part12.TrackHeaderBox
import org.mp4parser.boxes.iso14496.part12.UserDataBox
import org.mp4parser.boxes.threegpp.ts26244.AuthorBox
import org.mp4parser.boxes.threegpp.ts26244.LocationInformationBox
import org.mp4parser.support.AbstractBox
import org.mp4parser.support.Matrix
import org.mp4parser.tools.Path
@ -32,6 +46,15 @@ object Mp4ParserHelper {
// arbitrary size to detect boxes that may yield an OOM
private const val BOX_SIZE_DANGER_THRESHOLD = 3 * (1 shl 20) // MB
const val SAMSUNG_MAKERNOTE_BOX_TYPE = "sefd"
const val SEFD_MOTION_PHOTO_NAME = "MotionPhoto_Data"
private val largerTypeWhitelist = listOf(
// HEIC motion photo may contain Samsung maker notes in `sefd` box,
// including a video larger than the danger threshold
SAMSUNG_MAKERNOTE_BOX_TYPE,
)
fun computeEdits(context: Context, uri: Uri, modifier: (isoFile: IsoFile) -> Unit): List<Pair<Long, ByteArray>> {
// we can skip uninteresting boxes with a seekable data source
val pfd = StorageUtils.openInputFileDescriptor(context, uri) ?: throw Exception("failed to open file descriptor for uri=$uri")
@ -120,6 +143,35 @@ object Mp4ParserHelper {
return false
}
// returns the offset and data of the Samsung maker notes box
fun getSamsungSefd(context: Context, uri: Uri): Pair<Long, ByteArray>? {
try {
// we can skip uninteresting boxes with a seekable data source
val pfd = StorageUtils.openInputFileDescriptor(context, uri) ?: throw Exception("failed to open file descriptor for uri=$uri")
pfd.use {
FileInputStream(it.fileDescriptor).use { stream ->
stream.channel.use { channel ->
IsoFile(channel, metadataBoxParser()).use { isoFile ->
var offset = 0L
for (box in isoFile.boxes) {
if (box is UnknownBox && box.type == SAMSUNG_MAKERNOTE_BOX_TYPE) {
if (!box.isParsed) {
box.parseDetails()
}
return Pair(offset + 8, box.data.toByteArray()) // skip 8 bytes for box header
}
offset += box.size
}
}
}
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to read sefd box", e)
}
return null
}
// extensions
fun IsoFile.updateLocation(locationIso6709: String?) {
@ -259,18 +311,18 @@ object Mp4ParserHelper {
)
setBoxSkipper { type, size ->
if (skippedTypes.contains(type)) return@setBoxSkipper true
if (size > BOX_SIZE_DANGER_THRESHOLD) throw Exception("box (type=$type size=$size) is too large")
if (size > BOX_SIZE_DANGER_THRESHOLD && !largerTypeWhitelist.contains(type)) throw Exception("box (type=$type size=$size) is too large")
false
}
}
fun getUserData(
fun getUserDataBox(
context: Context,
mimeType: String,
uri: Uri,
): MutableMap<String, String> {
val fields = HashMap<String, String>()
if (mimeType != MimeTypes.MP4) return fields
): UserDataBox? {
if (mimeType != MimeTypes.MP4) return null
try {
// we can skip uninteresting boxes with a seekable data source
val pfd = StorageUtils.openInputFileDescriptor(context, uri) ?: throw Exception("failed to open file descriptor for uri=$uri")
@ -279,10 +331,7 @@ object Mp4ParserHelper {
stream.channel.use { channel ->
// creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device`
IsoFile(channel, metadataBoxParser()).use { isoFile ->
val userDataBox = Path.getPath<UserDataBox>(isoFile.movieBox, UserDataBox.TYPE)
if (userDataBox != null) {
fields.putAll(extractBoxFields(userDataBox))
}
return Path.getPath(isoFile.movieBox, UserDataBox.TYPE)
}
}
}
@ -292,10 +341,10 @@ object Mp4ParserHelper {
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get User Data box by MP4 parser for mimeType=$mimeType uri=$uri", e)
}
return fields
return null
}
private fun extractBoxFields(container: Container): HashMap<String, String> {
fun extractBoxFields(container: Container): HashMap<String, String> {
val fields = HashMap<String, String>()
for (box in container.boxes) {
if (box is AbstractBox && !box.isParsed) {
@ -309,9 +358,20 @@ object Mp4ParserHelper {
is AppleGPSCoordinatesBox -> fields[key] = box.value
is AppleItemListBox -> fields.putAll(extractBoxFields(box))
is AppleVariableSignedIntegerBox -> fields[key] = box.value.toString()
is Utf8AppleDataBox -> fields[key] = box.value
is HandlerBox -> {}
is LocationInformationBox -> {
hashMapOf<String, String>(
"Language" to box.language,
"Name" to box.name,
"Role" to box.role.toString(),
"Longitude" to box.longitude.toString(),
"Latitude" to box.latitude.toString(),
"Altitude" to box.altitude.toString(),
"Astronomical Body" to box.astronomicalBody,
"Additional Notes" to box.additionalNotes,
).forEach { (k, v) -> fields["$key/$k"] = v }
}
is MetaBox -> {
val handlerBox = Path.getPath<HandlerBox>(box, HandlerBox.TYPE).apply { parseDetails() }
when (val handlerType = handlerBox?.handlerType ?: MetaBox.TYPE) {
@ -336,6 +396,8 @@ object Mp4ParserHelper {
}
}
is Utf8AppleDataBox -> fields[key] = box.value
else -> fields[key] = box.toString()
}
}
@ -348,6 +410,7 @@ object Mp4ParserHelper {
"catg" -> "Category"
"covr" -> "Cover Art"
"keyw" -> "Keyword"
"loci" -> "Location"
"mcvr" -> "Preview Image"
"pcst" -> "Podcast"
"SDLN" -> "Play Mode"

View file

@ -9,13 +9,14 @@ import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.adobe.internal.xmp.XMPMeta
import com.drew.imaging.jpeg.JpegSegmentType
import com.drew.metadata.exif.ExifDirectoryBase
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeInt
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeInt
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeLong
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.metadata.metadataextractor.Helper.getSafeInt
import deckers.thibault.aves.metadata.metadataextractor.mpf.MpEntry
@ -30,10 +31,14 @@ import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.indexOfBytes
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import java.io.DataInputStream
import java.io.EOFException
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
object MultiPage {
private val LOG_TAG = LogUtils.createTag<MultiPage>()
// TODO TLAD more generic support, (e.g. 0x00000014 + `ftyp` + `qt `)
// atom length (variable, e.g. `0x00000018`) + atom type (`ftyp`) + type (variable, e.g. `mp42`, `qt`)
private val heicMotionPhotoVideoStartIndicator = byteArrayOf(0x00, 0x00, 0x00, 0x18) + "ftypmp42".toByteArray()
// page info
@ -46,14 +51,6 @@ object MultiPage {
private const val KEY_ROTATION_DEGREES = "rotationDegrees"
fun getHeicTracks(context: Context, uri: Uri): ArrayList<FieldMap> {
fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) {
if (this.containsKey(key)) save(this.getInteger(key))
}
fun MediaFormat.getSafeLong(key: String, save: (value: Long) -> Unit) {
if (this.containsKey(key)) save(this.getLong(key))
}
val tracks = ArrayList<FieldMap>()
val extractor = MediaExtractor()
extractor.setDataSource(context, uri, null)
@ -89,6 +86,26 @@ object MultiPage {
return tracks
}
fun isHeicSefdMotionPhoto(context: Context, uri: Uri): Boolean {
return getHeicSefdMotionPhotoVideoSizing(context, uri) != null
}
private fun getHeicSefdMotionPhotoVideoSizing(context: Context, uri: Uri): Pair<Long, Long>? {
Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (sefdOffset, sefdBytes) ->
// we could properly parse each tag until we find the "embedded video" tag (0x0a30)
// but it seems that decoding the SEFT trailer is necessary for this,
// so we simply search for the "MotionPhoto_Data" sequence instead
val name = Mp4ParserHelper.SEFD_MOTION_PHOTO_NAME
val index = sefdBytes.indexOfBytes(name.toByteArray(Charsets.UTF_8))
if (index != -1) {
val videoOffset = sefdOffset + index + name.length
val videoSize = sefdBytes.size - (videoOffset - sefdOffset)
return Pair(videoOffset, videoSize)
}
}
return null
}
private fun getJpegMpfPrimaryRotation(context: Context, uri: Uri, sizeBytes: Long): Int {
val mimeType = MimeTypes.JPEG
var rotationDegrees = 0
@ -97,7 +114,7 @@ object MultiPage {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundExif = metadata.directories.any { it is ExifDirectoryBase && it.tagCount > 0 }
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
dir.getSafeInt(ExifDirectoryBase.TAG_ORIENTATION) {
@ -136,21 +153,31 @@ object MultiPage {
// starts after `[APP2 marker (1 byte)] [segment size (2 bytes)] [MPF marker (4 bytes)]`
fun getJpegMpfBaseOffset(context: Context, uri: Uri, sizeBytes: Long?): Int? {
val mimeType = MimeTypes.JPEG
val endMarker = 0xFF
val app2Marker = JpegSegmentType.APP2.byteValue
val mpfMarker = "MPF".toByteArray() + 0x00
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
var offset = 0
val marker = ByteArray(4)
while (true) {
do {
val b = input.read().toByte()
// look for APP2 marker (0xFFE2)
var found = false
while (!found) {
var i = input.read()
if (i == -1) throw EOFException()
offset++
} while (b != app2Marker)
if (i == endMarker) {
i = input.read()
if (i == -1) throw EOFException()
offset++
found = i.toByte() == app2Marker
}
}
// skip 2 bytes for segment size
input.skip(2)
offset += 2
val marker = ByteArray(4)
input.read(marker, 0, marker.size)
offset += 4
if (marker.contentEquals(mpfMarker)) {
@ -168,7 +195,7 @@ object MultiPage {
val mimeType = MimeTypes.JPEG
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
return metadata.getDirectoriesOfType(MpEntryDirectory::class.java).map { it.entry }
}
} catch (e: Exception) {
@ -239,70 +266,39 @@ object MultiPage {
}
fun getMotionPhotoPages(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): ArrayList<FieldMap> {
fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) {
if (this.containsKey(key)) save(this.getInteger(key))
}
fun MediaFormat.getSafeLong(key: String, save: (value: Long) -> Unit) {
if (this.containsKey(key)) save(this.getLong(key))
}
val pages = ArrayList<FieldMap>()
val extractor = MediaExtractor()
var pfd: ParcelFileDescriptor? = null
try {
getMotionPhotoOffset(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
val videoStartOffset = sizeBytes - videoSizeBytes
pfd = context.contentResolver.openFileDescriptor(uri, "r")
pfd?.fileDescriptor?.let { fd ->
extractor.setDataSource(fd, videoStartOffset, videoSizeBytes)
// set the original image as the first and default track
var pageIndex = 0
pages.add(
hashMapOf(
KEY_PAGE to pageIndex++,
KEY_MIME_TYPE to mimeType,
KEY_IS_DEFAULT to true,
)
getMotionPhotoVideoInfo(context, uri, mimeType, sizeBytes)?.let { videoInfo ->
// set the original image as the first and default track
var pageIndex = 0
pages.add(
hashMapOf(
KEY_PAGE to pageIndex++,
KEY_MIME_TYPE to mimeType,
KEY_IS_DEFAULT to true,
)
)
// add video tracks from the appended video
videoInfo.getString(MediaFormat.KEY_MIME)?.let { mime ->
if (MimeTypes.isVideo(mime)) {
val page: FieldMap = hashMapOf(
KEY_PAGE to pageIndex++,
KEY_MIME_TYPE to MimeTypes.MP4,
KEY_IS_DEFAULT to false,
)
// add video tracks from the appended video
if (extractor.trackCount > 0) {
// only consider the first track to represent the appended video
val trackIndex = 0
try {
val format = extractor.getTrackFormat(trackIndex)
format.getString(MediaFormat.KEY_MIME)?.let { mime ->
if (MimeTypes.isVideo(mime)) {
val page: FieldMap = hashMapOf(
KEY_PAGE to pageIndex++,
KEY_MIME_TYPE to MimeTypes.MP4,
KEY_IS_DEFAULT to false,
)
format.getSafeInt(MediaFormat.KEY_WIDTH) { page[KEY_WIDTH] = it }
format.getSafeInt(MediaFormat.KEY_HEIGHT) { page[KEY_HEIGHT] = it }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
format.getSafeInt(MediaFormat.KEY_ROTATION) { page[KEY_ROTATION_DEGREES] = it }
}
format.getSafeLong(MediaFormat.KEY_DURATION) { page[KEY_DURATION] = it / 1000 }
pages.add(page)
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get motion photo track information for uri=$uri, track num=$trackIndex", e)
}
videoInfo.getSafeInt(MediaFormat.KEY_WIDTH) { page[KEY_WIDTH] = it }
videoInfo.getSafeInt(MediaFormat.KEY_HEIGHT) { page[KEY_HEIGHT] = it }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
videoInfo.getSafeInt(MediaFormat.KEY_ROTATION) { page[KEY_ROTATION_DEGREES] = it }
}
videoInfo.getSafeLong(MediaFormat.KEY_DURATION) { page[KEY_DURATION] = it / 1000 }
pages.add(page)
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to open motion photo for uri=$uri", e)
} finally {
extractor.release()
pfd?.close()
}
return pages
}
fun getMotionPhotoOffset(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Long? {
fun getTrailerVideoSize(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Long? {
if (MimeTypes.isHeic(mimeType)) {
// XMP in HEIC motion photos (as taken with a Samsung Camera v12.0.01.50) indicates an `Item:Length` of 68 bytes for the video.
// This item does not contain the video itself, but only some kind of metadata (no doc, no spec),
@ -332,7 +328,7 @@ object MultiPage {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundXmp = metadata.directories.any { it is XmpDirectory && it.tagCount > 0 }
metadata.getDirectoriesOfType(XmpDirectory::class.java).map { it.xmpMeta }.forEach(::processXmp)
}
@ -349,6 +345,62 @@ object MultiPage {
return offsetFromEnd
}
private fun getMotionPhotoVideoInfo(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): MediaFormat? {
getMotionPhotoVideoSizing(context, uri, mimeType, sizeBytes)?.let { (videoOffset, videoSize) ->
return getEmbedVideoInfo(context, uri, videoOffset, videoSize)
}
return null
}
fun getTrailerVideoInfo(context: Context, uri: Uri, fileSize: Long, videoSize: Long): MediaFormat? {
return getEmbedVideoInfo(context, uri, videoOffset = fileSize - videoSize, videoSize = videoSize)
}
private fun getEmbedVideoInfo(context: Context, uri: Uri, videoOffset: Long, videoSize: Long): MediaFormat? {
val extractor = MediaExtractor()
var pfd: ParcelFileDescriptor? = null
try {
pfd = context.contentResolver.openFileDescriptor(uri, "r")
pfd?.fileDescriptor?.let { fd ->
extractor.setDataSource(fd, videoOffset, videoSize)
// video track may be after an audio track
for (trackIndex in 0 until extractor.trackCount) {
try {
val format = extractor.getTrackFormat(trackIndex)
format.getString(MediaFormat.KEY_MIME)?.let {
if (MimeTypes.isVideo(it)) {
return format
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get track information for uri=$uri, track num=$trackIndex", e)
}
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to open motion photo for uri=$uri", e)
} finally {
extractor.release()
pfd?.close()
}
return null
}
fun getMotionPhotoVideoSizing(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Pair<Long, Long>? {
// default to trailer videos
getTrailerVideoSize(context, uri, mimeType, sizeBytes)?.let { videoSize ->
val videoOffset = sizeBytes - videoSize
return Pair(videoOffset, videoSize)
}
if (MimeTypes.isHeic(mimeType)) {
// fallback to video within Samsung SEFD box
return getHeicSefdMotionPhotoVideoSizing(context, uri)
}
return null
}
fun getTiffPages(context: Context, uri: Uri): ArrayList<FieldMap> {
fun toMap(pageIndex: Int, options: TiffBitmapFactory.Options): FieldMap {
return hashMapOf(

View file

@ -26,7 +26,6 @@ import pixy.meta.string.XMLUtils
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.util.*
object PixyMetaHelper {
fun describe(input: InputStream): HashMap<String, String> {
@ -82,17 +81,18 @@ object PixyMetaHelper {
output: OutputStream,
iptcDataList: List<FieldMap>?,
) {
val iptc = iptcDataList?.flatMap {
val iptc: List<IPTCDataSet> = iptcDataList?.flatMap {
val record = it["record"] as Int
val tag = it["tag"] as Int
val values = it["values"] as List<*>
values.map { data -> IPTCDataSet(IPTCRecord.fromRecordNumber(record), tag, data as ByteArray) }
} ?: ArrayList<IPTCDataSet>()
} ?: ArrayList()
Metadata.insertIPTC(input, output, iptc)
}
fun getXmp(input: InputStream): XMP? = Metadata.readMetadata(input)[MetadataType.XMP] as XMP?
// PixyMeta may fail with just a log, and write nothing to the output
fun setXmp(
input: InputStream,
output: OutputStream,

View file

@ -3,7 +3,6 @@ package deckers.thibault.aves.metadata
import deckers.thibault.aves.utils.toHex
import java.math.BigInteger
import java.nio.charset.Charset
import java.util.*
class QuickTimeMetadataBlock(val type: String, val value: String, val language: String)

View file

@ -59,19 +59,21 @@ object Helper {
// e.g. "exif [...] 134 [...] 4578696600004949[...]"
private val PNG_RAW_PROFILE_PATTERN = Regex("^\\n(.*?)\\n\\s*(\\d+)\\n(.*)", RegexOption.DOT_MATCHES_ALL)
// providing the stream length is risky, as it may crash if it is incorrect
private const val safeReadStreamLength = -1L
fun readMimeType(input: InputStream): String? {
val bufferedInputStream = if (input is BufferedInputStream) input else BufferedInputStream(input)
return FileTypeDetector.detectFileType(bufferedInputStream).mimeType
}
@Throws(IOException::class, ImageProcessingException::class)
fun safeRead(input: InputStream): com.drew.metadata.Metadata {
fun safeRead(input: InputStream, @Suppress("unused_parameter") sizeBytes: Long?): com.drew.metadata.Metadata {
val inputStream = if (input is BufferedInputStream) input else BufferedInputStream(input)
val fileType = FileTypeDetector.detectFileType(inputStream)
// Providing the stream length is risky, as it may crash if it is incorrect.
// Not providing the stream length is also risky, as it may lead to OOM
// when `RandomAccessStreamReader` reads the entire stream to validate offsets.
val undefinedStreamLength = -1L
val metadata = when (fileType) {
FileType.Jpeg -> safeReadJpeg(inputStream)
FileType.Mp4 -> safeReadMp4(inputStream)
@ -82,9 +84,9 @@ object Helper {
FileType.Cr2,
FileType.Nef,
FileType.Orf,
FileType.Rw2 -> safeReadTiff(inputStream)
FileType.Rw2 -> safeReadTiff(inputStream, undefinedStreamLength)
else -> ImageMetadataReader.readMetadata(inputStream, safeReadStreamLength, fileType)
else -> ImageMetadataReader.readMetadata(inputStream, undefinedStreamLength, fileType)
}
metadata.addDirectory(FileTypeDirectory(fileType))
@ -115,8 +117,8 @@ object Helper {
}
@Throws(IOException::class, TiffProcessingException::class)
fun safeReadTiff(input: InputStream): com.drew.metadata.Metadata {
val reader = RandomAccessStreamReader(input, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, safeReadStreamLength)
fun safeReadTiff(input: InputStream, streamLength: Long): com.drew.metadata.Metadata {
val reader = RandomAccessStreamReader(input, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, streamLength)
val metadata = com.drew.metadata.Metadata()
val handler = SafeExifTiffHandler(metadata, null, 0)
TiffReader().processTiff(reader, handler, 0)
@ -294,9 +296,7 @@ object Helper {
if (!modelTiePoints && !modelTransformation) return false
val modelPixelScale = this.containsTag(ExifGeoTiffTags.TAG_MODEL_PIXEL_SCALE)
if ((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiePoints)) return false
return true
return !((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiePoints))
}
// TODO TLAD use `GeoTiffDirectory` from the Java version of `metadata-extractor` when available

View file

@ -31,6 +31,7 @@ import deckers.thibault.aves.utils.LogUtils
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.util.Locale
import java.util.zip.InflaterInputStream
import java.util.zip.ZipException
@ -42,7 +43,7 @@ object SafePngMetadataReader {
private val LOG_TAG = LogUtils.createTag<SafePngMetadataReader>()
// arbitrary size to detect chunks that may yield an OOM
private const val chunkSizeDangerThreshold = SafeXmpReader.SEGMENT_TYPE_SIZE_DANGER_THRESHOLD
private const val CHUNK_SIZE_DANGER_THRESHOLD = SafeXmpReader.SEGMENT_TYPE_SIZE_DANGER_THRESHOLD
private val latin1Encoding = Charsets.ISO_8859_1
private val utf8Encoding = Charsets.UTF_8
@ -85,7 +86,7 @@ object SafePngMetadataReader {
val bytes = chunk.bytes
// TLAD insert start
if (bytes.size > chunkSizeDangerThreshold) {
if (bytes.size > CHUNK_SIZE_DANGER_THRESHOLD) {
Log.w(LOG_TAG, "PNG chunk $chunkType is too large, with a size of ${bytes.size} B")
return
}
@ -290,11 +291,12 @@ object SafePngMetadataReader {
val second = reader.uInt8.toInt()
val directory = PngDirectory(PngChunkType.tIME)
if (DateUtil.isValidDate(year, month - 1, day) && DateUtil.isValidTime(hour, minute, second)) {
val dateString = String.format("%04d:%02d:%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
val dateString = String.format(Locale.ROOT, "%04d:%02d:%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
directory.setString(PngDirectory.TAG_LAST_MODIFICATION_TIME, dateString)
} else {
directory.addError(
String.format(
Locale.ROOT,
"PNG tIME data describes an invalid date/time: year=%d month=%d day=%d hour=%d minute=%d second=%d",
year, month, day, hour, minute, second
)

View file

@ -16,6 +16,7 @@ import com.drew.metadata.xmp.XmpDirectory
import com.drew.metadata.xmp.XmpReader
import deckers.thibault.aves.utils.LogUtils
import java.io.IOException
import java.util.Locale
class SafeXmpReader : XmpReader() {
// adapted from `XmpReader` to detect and skip large extended XMP
@ -133,7 +134,7 @@ class SafeXmpReader : XmpReader() {
System.arraycopy(segmentBytes, totalOffset, extendedXMPBuffer, chunkOffset, segmentLength - totalOffset)
} else {
val directory = XmpDirectory()
directory.addError(String.format("Inconsistent length for the Extended XMP buffer: %d instead of %d", fullLength, extendedXMPBuffer.size))
directory.addError(String.format(Locale.ROOT, "Inconsistent length for the Extended XMP buffer: %d instead of %d", fullLength, extendedXMPBuffer.size))
metadata.addDirectory(directory)
}
}

View file

@ -16,8 +16,6 @@ class MpfDirectory : Directory() {
return _tagNameMap
}
fun getNumberOfImages() = getInt(TAG_NUMBER_OF_IMAGES)
companion object {
const val TAG_MPF_VERSION = 0xb000
const val TAG_NUMBER_OF_IMAGES = 0xb001

View file

@ -183,7 +183,7 @@ object GoogleXMP {
return offsetFromEnd
}
fun updateTrailingVideoOffset(xmp: String, oldOffset: Int, newOffset: Int): String {
fun updateTrailingVideoOffset(xmp: String, oldOffset: Number, newOffset: Number): String {
return xmp.replace(
// GCamera motion photo
"${GCAMERA_VIDEO_OFFSET_PROP_NAME}=\"$oldOffset\"",
@ -195,7 +195,6 @@ object GoogleXMP {
)
}
fun getDeviceContainer(meta: XMPMeta): GoogleDeviceContainer? {
return if (meta.doesPropPathExist(listOf(GDEVICE_CONTAINER_PROP_NAME, GDEVICE_CONTAINER_DIRECTORY_PROP_NAME))) {
GoogleDeviceContainer().apply { findItems(meta) }

View file

@ -1,21 +1,22 @@
package deckers.thibault.aves.model
import android.net.Uri
import androidx.core.net.toUri
class AvesEntry(map: FieldMap) {
val uri: Uri = Uri.parse(map["uri"] as String) // content or file URI
val path = map["path"] as String? // best effort to get local path
val pageId = map["pageId"] as Int? // null means the main entry
val mimeType = map["mimeType"] as String
val width = map["width"] as Int
val height = map["height"] as Int
val rotationDegrees = map["rotationDegrees"] as Int
val isFlipped = map["isFlipped"] as Boolean
val sizeBytes = toLong(map["sizeBytes"])
val trashed = map["trashed"] as Boolean
val trashPath = map["trashPath"] as String?
val uri: Uri = (map[EntryFields.URI] as String).toUri() // content or file URI
val path = map[EntryFields.PATH] as String? // best effort to get local path
val pageId = map[EntryFields.PAGE_ID] as Int? // null means the main entry
val mimeType = map[EntryFields.MIME_TYPE] as String
val width = map[EntryFields.WIDTH] as Int
val height = map[EntryFields.HEIGHT] as Int
val rotationDegrees = map[EntryFields.ROTATION_DEGREES] as Int
val isFlipped = map[EntryFields.IS_FLIPPED] as Boolean
val sizeBytes = toLong(map[EntryFields.SIZE_BYTES])
val trashed = map[EntryFields.TRASHED] as Boolean
val trashPath = map[EntryFields.TRASH_PATH] as String?
val isRotated: Boolean
private val isRotated: Boolean
get() = rotationDegrees % 180 == 90
val displayWidth: Int

View file

@ -0,0 +1,29 @@
package deckers.thibault.aves.model
// entry fields exported and imported from/to the platform side
// should match `EntryFields` on Dart side
object EntryFields {
const val ORIGIN = "origin" // int
const val URI = "uri" // string
const val CONTENT_ID = "contentId" // long
const val PATH = "path" // string
const val PAGE_ID = "pageId" // int
const val SOURCE_MIME_TYPE = "sourceMimeType" // string
const val MIME_TYPE = "mimeType" // string
const val WIDTH = "width" // int
const val HEIGHT = "height" // int
const val SOURCE_ROTATION_DEGREES = "sourceRotationDegrees" // int
const val ROTATION_DEGREES = "rotationDegrees" // int
const val IS_FLIPPED = "isFlipped" // boolean
const val DATE_ADDED_SECS = "dateAddedSecs" // long
const val DATE_MODIFIED_MILLIS = "dateModifiedMillis" // long
const val SOURCE_DATE_TAKEN_MILLIS = "sourceDateTakenMillis" // long
const val DURATION_MILLIS = "durationMillis" // long
const val SIZE_BYTES = "sizeBytes" // long
const val TRASHED = "trashed" // boolean
const val TRASH_PATH = "trashPath" // string
const val TITLE = "title" // string
}

View file

@ -1,5 +1,7 @@
package deckers.thibault.aves.model
import java.io.File
enum class NameConflictStrategy {
RENAME, REPLACE, SKIP;
@ -9,4 +11,6 @@ enum class NameConflictStrategy {
return valueOf(name.uppercase())
}
}
}
}
class NameConflictResolution(var nameWithoutExtension: String?, var replacementFile: File?)

View file

@ -5,7 +5,7 @@ import android.content.Context
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import androidx.exifinterface.media.ExifInterface
import androidx.core.net.toUri
import com.drew.metadata.avi.AviDirectory
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.jpeg.JpegDirectory
@ -29,6 +29,7 @@ import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.UriUtils.tryParseId
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import java.io.IOException
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
class SourceEntry {
private val origin: Int
@ -41,7 +42,7 @@ class SourceEntry {
private var sourceRotationDegrees: Int? = null
private var sizeBytes: Long? = null
private var dateAddedSecs: Long? = null
private var dateModifiedSecs: Long? = null
private var dateModifiedMillis: Long? = null
private var sourceDateTakenMillis: Long? = null
private var durationMillis: Long? = null
@ -54,45 +55,45 @@ class SourceEntry {
}
constructor(map: FieldMap) {
origin = map["origin"] as Int
uri = Uri.parse(map["uri"] as String)
path = map["path"] as String?
sourceMimeType = map["sourceMimeType"] as String
width = map["width"] as Int?
height = map["height"] as Int?
sourceRotationDegrees = map["sourceRotationDegrees"] as Int?
sizeBytes = toLong(map["sizeBytes"])
title = map["title"] as String?
dateAddedSecs = toLong(map["dateAddedSecs"])
dateModifiedSecs = toLong(map["dateModifiedSecs"])
sourceDateTakenMillis = toLong(map["sourceDateTakenMillis"])
durationMillis = toLong(map["durationMillis"])
origin = map[EntryFields.ORIGIN] as Int
uri = (map[EntryFields.URI] as String).toUri()
path = map[EntryFields.PATH] as String?
sourceMimeType = map[EntryFields.SOURCE_MIME_TYPE] as String
width = map[EntryFields.WIDTH] as Int?
height = map[EntryFields.HEIGHT] as Int?
sourceRotationDegrees = map[EntryFields.SOURCE_ROTATION_DEGREES] as Int?
sizeBytes = toLong(map[EntryFields.SIZE_BYTES])
title = map[EntryFields.TITLE] as String?
dateAddedSecs = toLong(map[EntryFields.DATE_ADDED_SECS])
dateModifiedMillis = toLong(map[EntryFields.DATE_MODIFIED_MILLIS])
sourceDateTakenMillis = toLong(map[EntryFields.SOURCE_DATE_TAKEN_MILLIS])
durationMillis = toLong(map[EntryFields.DURATION_MILLIS])
}
fun initFromFile(path: String, title: String, sizeBytes: Long, dateModifiedSecs: Long) {
fun initFromFile(path: String, title: String, sizeBytes: Long, dateModifiedMillis: Long) {
this.path = path
this.title = title
this.sizeBytes = sizeBytes
this.dateModifiedSecs = dateModifiedSecs
this.dateModifiedMillis = dateModifiedMillis
}
fun toMap(): FieldMap {
return hashMapOf(
"origin" to origin,
"uri" to uri.toString(),
"path" to path,
"sourceMimeType" to sourceMimeType,
"width" to width,
"height" to height,
"sourceRotationDegrees" to (sourceRotationDegrees ?: 0),
"sizeBytes" to sizeBytes,
"title" to title,
"dateAddedSecs" to dateAddedSecs,
"dateModifiedSecs" to dateModifiedSecs,
"sourceDateTakenMillis" to sourceDateTakenMillis,
"durationMillis" to durationMillis,
EntryFields.ORIGIN to origin,
EntryFields.URI to uri.toString(),
EntryFields.PATH to path,
EntryFields.SOURCE_MIME_TYPE to sourceMimeType,
EntryFields.WIDTH to width,
EntryFields.HEIGHT to height,
EntryFields.SOURCE_ROTATION_DEGREES to (sourceRotationDegrees ?: 0),
EntryFields.SIZE_BYTES to sizeBytes,
EntryFields.TITLE to title,
EntryFields.DATE_ADDED_SECS to dateAddedSecs,
EntryFields.DATE_MODIFIED_MILLIS to dateModifiedMillis,
EntryFields.SOURCE_DATE_TAKEN_MILLIS to sourceDateTakenMillis,
EntryFields.DURATION_MILLIS to durationMillis,
// only for map export
"contentId" to contentId,
EntryFields.CONTENT_ID to contentId,
)
}
@ -163,7 +164,7 @@ class SourceEntry {
try {
Metadata.openSafeInputStream(context, uri, sourceMimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
// do not switch on specific MIME types, as the reported MIME type could be wrong
// (e.g. PNG registered as JPG)

View file

@ -6,13 +6,14 @@ import android.content.ContextWrapper
import android.net.Uri
import android.util.Log
import android.webkit.MimeTypeMap
import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.SourceEntry
import deckers.thibault.aves.utils.LogUtils
import java.io.File
internal class FileImageProvider : ImageProvider() {
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
var mimeType = sourceMimeType
if (mimeType == null) {
@ -45,16 +46,17 @@ internal class FileImageProvider : ImageProvider() {
path = path,
title = file.name,
sizeBytes = file.length(),
dateModifiedSecs = file.lastModified() / 1000,
dateModifiedMillis = file.lastModified(),
)
}
} catch (e: SecurityException) {
callback.onFailure(e)
return
}
}
entry.fillPreCatalogMetadata(context)
if (entry.isSized || entry.isSvg || entry.isVideo) {
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
callback.onSuccess(entry.toMap())
} else {
callback.onFailure(Exception("entry has no size"))
@ -87,9 +89,9 @@ internal class FileImageProvider : ImageProvider() {
}
return hashMapOf(
"uri" to Uri.fromFile(newFile).toString(),
"path" to newFile.path,
"dateModifiedSecs" to newFile.lastModified() / 1000,
EntryFields.URI to Uri.fromFile(newFile).toString(),
EntryFields.PATH to newFile.path,
EntryFields.DATE_MODIFIED_MILLIS to newFile.lastModified(),
)
}
@ -97,8 +99,8 @@ internal class FileImageProvider : ImageProvider() {
try {
val file = File(path)
if (file.exists()) {
newFields["dateModifiedSecs"] = file.lastModified() / 1000
newFields["sizeBytes"] = file.length()
newFields[EntryFields.DATE_MODIFIED_MILLIS] = file.lastModified()
newFields[EntryFields.SIZE_BYTES] = file.length()
}
callback.onSuccess(newFields)
} catch (e: SecurityException) {

View file

@ -11,17 +11,11 @@ import android.net.Uri
import android.os.Binder
import android.os.Build
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import androidx.fragment.app.FragmentActivity
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.FutureTarget
import com.bumptech.glide.request.RequestOptions
import com.commonsware.cwac.document.DocumentFileCompat
import deckers.thibault.aves.decoder.MultiPageImage
import deckers.thibault.aves.decoder.SvgImage
import deckers.thibault.aves.decoder.TiffImage
import deckers.thibault.aves.decoder.AvesAppGlideModule
import deckers.thibault.aves.metadata.ExifInterfaceHelper
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
import deckers.thibault.aves.metadata.Metadata.TYPE_EXIF
@ -39,8 +33,10 @@ import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.metadata.xmp.GoogleXMP
import deckers.thibault.aves.model.AvesEntry
import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.model.ExifOrientationOp
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.NameConflictResolution
import deckers.thibault.aves.model.NameConflictStrategy
import deckers.thibault.aves.model.SourceEntry
import deckers.thibault.aves.utils.BitmapUtils
@ -68,9 +64,10 @@ import java.nio.channels.Channels
import java.util.Date
import java.util.TimeZone
import kotlin.math.absoluteValue
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
abstract class ImageProvider {
open fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
open fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
callback.onFailure(UnsupportedOperationException("`fetchSingle` is not supported by this image provider"))
}
@ -78,10 +75,10 @@ abstract class ImageProvider {
return if (StorageUtils.isInVault(context, path)) {
val uri = Uri.fromFile(File(path))
hashMapOf(
"origin" to SourceEntry.ORIGIN_VAULT,
"uri" to uri.toString(),
"contentId" to null,
"path" to path,
EntryFields.ORIGIN to SourceEntry.ORIGIN_VAULT,
EntryFields.URI to uri.toString(),
EntryFields.CONTENT_ID to null,
EntryFields.PATH to path,
)
} else {
MediaStoreImageProvider().scanNewPathByMediaStore(context, path, mimeType)
@ -137,8 +134,7 @@ abstract class ImageProvider {
"success" to false,
)
// prevent naming with a `.` prefix as it would hide the file and remove it from the Media Store
if (sourcePath != null && !desiredName.startsWith('.')) {
if (sourcePath != null) {
try {
var newFields: FieldMap = skippedFieldMap
if (!isCancelledOp()) {
@ -146,15 +142,18 @@ abstract class ImageProvider {
val oldFile = File(sourcePath)
if (oldFile.nameWithoutExtension != desiredNameWithoutExtension) {
val defaultExtension = oldFile.extension
oldFile.parent?.let { dir ->
resolveTargetFileNameWithoutExtension(
val resolution = resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = dir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = mimeType,
defaultExtension = defaultExtension,
conflictStrategy = NameConflictStrategy.RENAME,
)?.let { targetNameWithoutExtension ->
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
)
resolution.nameWithoutExtension?.let { targetNameWithoutExtension ->
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType, defaultExtension)}"
val newFile = File(dir, targetFileName)
if (oldFile != newFile) {
newFields = renameSingle(
@ -194,7 +193,7 @@ abstract class ImageProvider {
}
suspend fun convertMultiple(
activity: FragmentActivity,
activity: Activity,
imageExportMimeType: String,
targetDir: String,
entries: List<AvesEntry>,
@ -208,6 +207,7 @@ abstract class ImageProvider {
) {
if (!supportedExportMimeTypes.contains(imageExportMimeType)) {
callback.onFailure(Exception("unsupported export MIME type=$imageExportMimeType"))
return
}
val targetDirDocFile = StorageUtils.createDirectoryDocIfAbsent(activity, targetDir)
@ -253,7 +253,7 @@ abstract class ImageProvider {
}
private suspend fun convertSingle(
activity: FragmentActivity,
activity: Activity,
sourceEntry: AvesEntry,
targetDir: String,
targetDirDocFile: DocumentFileCompat?,
@ -266,7 +266,7 @@ abstract class ImageProvider {
exportMimeType: String,
): FieldMap {
val sourceMimeType = sourceEntry.mimeType
val sourceUri = sourceEntry.uri
var sourceUri = sourceEntry.uri
val pageId = sourceEntry.pageId
var desiredNameWithoutExtension = if (sourceEntry.path != null) {
@ -279,13 +279,23 @@ abstract class ImageProvider {
val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId
desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}"
}
val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
// there is no benefit providing input extension
// for known output MIME type
val defaultExtension = null
val resolution = resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = targetDir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = exportMimeType,
defaultExtension = defaultExtension,
conflictStrategy = nameConflictStrategy,
) ?: return skippedFieldMap
)
val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
resolution.replacementFile?.let { file ->
sourceUri = Uri.fromFile(file)
}
val targetMimeType: String
val write: (OutputStream) -> Unit
@ -312,27 +322,12 @@ abstract class ImageProvider {
}
}
val model: Any = if (pageId != null && MultiPageImage.isSupported(sourceMimeType)) {
MultiPageImage(activity, sourceUri, sourceMimeType, pageId)
} else if (sourceMimeType == MimeTypes.TIFF) {
TiffImage(activity, sourceUri, pageId)
} else if (sourceMimeType == MimeTypes.SVG) {
SvgImage(activity, sourceUri)
} else {
StorageUtils.getGlideSafeUri(activity, sourceUri, sourceMimeType, sourceEntry.sizeBytes)
}
// request a fresh image with the highest quality format
val glideOptions = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
target = Glide.with(activity)
target = Glide.with(activity.applicationContext)
.asBitmap()
.apply(glideOptions)
.load(model)
.apply(AvesAppGlideModule.uncachedFullImageOptions)
.load(AvesAppGlideModule.getModel(activity, sourceUri, sourceMimeType, pageId, sourceEntry.sizeBytes))
.submit(targetWidthPx, targetHeightPx)
var bitmap = withContext(Dispatchers.IO) { target.get() }
if (MimeTypes.needRotationAfterGlide(sourceMimeType, pageId)) {
bitmap = BitmapUtils.applyExifOrientation(activity, bitmap, sourceEntry.rotationDegrees, sourceEntry.isFlipped)
@ -371,11 +366,12 @@ abstract class ImageProvider {
targetDir = targetDir,
targetDirDocFile = targetDirDocFile,
targetNameWithoutExtension = targetNameWithoutExtension,
defaultExtension = defaultExtension,
write = write,
)
val newFields = scanNewPath(activity, targetPath, exportMimeType)
val targetUri = Uri.parse(newFields["uri"] as String)
val targetUri = (newFields[EntryFields.URI] as String).toUri()
if (writeMetadata) {
copyMetadata(
context = activity,
@ -390,7 +386,9 @@ abstract class ImageProvider {
return newFields
} finally {
// clearing Glide target should happen after effectively writing the bitmap
Glide.with(activity).clear(target)
Glide.with(activity.applicationContext).clear(target)
resolution.replacementFile?.delete()
}
}
@ -470,12 +468,13 @@ abstract class ImageProvider {
}
val captureMimeType = MimeTypes.JPEG
val targetNameWithoutExtension = try {
val resolution = try {
resolveTargetFileNameWithoutExtension(
contextWrapper = contextWrapper,
dir = targetDir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = captureMimeType,
defaultExtension = null,
conflictStrategy = nameConflictStrategy,
)
} catch (e: Exception) {
@ -483,6 +482,7 @@ abstract class ImageProvider {
return
}
val targetNameWithoutExtension = resolution.nameWithoutExtension
if (targetNameWithoutExtension == null) {
// skip it
callback.onSuccess(skippedFieldMap)
@ -561,42 +561,65 @@ abstract class ImageProvider {
}
}
fun createTimeStampFileName() = Date().time.toString()
private fun sanitizeDesiredFileName(desiredName: String): String {
var name = desiredName
// prevent creating hidden files
while (name.isNotEmpty() && name.startsWith(".")) {
name = name.substring(1)
}
if (name.isEmpty()) {
name = createTimeStampFileName()
}
return name
}
// returns available name to use, or `null` to skip it
suspend fun resolveTargetFileNameWithoutExtension(
contextWrapper: ContextWrapper,
dir: String,
desiredNameWithoutExtension: String,
mimeType: String,
defaultExtension: String?,
conflictStrategy: NameConflictStrategy,
): String? {
val extension = extensionFor(mimeType)
val targetFile = File(dir, "$desiredNameWithoutExtension$extension")
return when (conflictStrategy) {
): NameConflictResolution {
val sanitizedNameWithoutExtension = sanitizeDesiredFileName(desiredNameWithoutExtension)
var resolvedName: String? = sanitizedNameWithoutExtension
var replacementFile: File? = null
val extension = extensionFor(mimeType, defaultExtension)
val targetFile = File(dir, "$sanitizedNameWithoutExtension$extension")
when (conflictStrategy) {
NameConflictStrategy.RENAME -> {
var nameWithoutExtension = desiredNameWithoutExtension
var nameWithoutExtension = sanitizedNameWithoutExtension
var i = 0
while (File(dir, "$nameWithoutExtension$extension").exists()) {
i++
nameWithoutExtension = "$desiredNameWithoutExtension ($i)"
nameWithoutExtension = "$sanitizedNameWithoutExtension ($i)"
}
nameWithoutExtension
resolvedName = nameWithoutExtension
}
NameConflictStrategy.REPLACE -> {
if (targetFile.exists()) {
// move replaced file to temp storage
// so that it can be used as a source for conversion or metadata copy
replacementFile = StorageUtils.createTempFile(contextWrapper).apply {
targetFile.transferTo(outputStream())
}
deletePath(contextWrapper, targetFile.path, mimeType)
}
desiredNameWithoutExtension
}
NameConflictStrategy.SKIP -> {
if (targetFile.exists()) {
null
} else {
desiredNameWithoutExtension
resolvedName = null
}
}
}
return NameConflictResolution(resolvedName, replacementFile)
}
// cf `MetadataFetchHandler.getCatalogMetadataByMetadataExtractor()` for a more thorough check
@ -634,19 +657,21 @@ abstract class ImageProvider {
}
val originalFileSize = File(path).length()
val videoSize = MultiPage.getMotionPhotoOffset(context, uri, mimeType, originalFileSize)?.let { it.toInt() + trailerDiff }
var videoBytes: ByteArray? = null
var trailerVideoBytes: ByteArray? = null
val editableFile = StorageUtils.createTempFile(context).apply {
val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)?.let { it + trailerDiff }
val isTrailerVideoValid = trailerVideoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, trailerVideoSize) != null
try {
if (videoSize != null) {
if (trailerVideoSize != null && isTrailerVideoValid) {
// handle motion photo and embedded video separately
val imageSize = (originalFileSize - videoSize).toInt()
videoBytes = ByteArray(videoSize)
val imageSize = (originalFileSize - trailerVideoSize).toInt()
val videoByteSize = trailerVideoSize.toInt()
trailerVideoBytes = ByteArray(videoByteSize)
StorageUtils.openInputStream(context, uri)?.let { input ->
val imageBytes = ByteArray(imageSize)
input.read(imageBytes, 0, imageSize)
input.read(videoBytes, 0, videoSize)
input.read(trailerVideoBytes, 0, videoByteSize)
// copy only the image to a temporary file for editing
// video will be appended after metadata modification
@ -666,30 +691,31 @@ abstract class ImageProvider {
try {
edit(ExifInterface(editableFile))
if (editableFile.length() == 0L) {
callback.onFailure(Exception("editing Exif yielded an empty file"))
return false
}
val editedMimeType = detectMimeType(context, Uri.fromFile(editableFile), mimeType)
if (editedMimeType != mimeType) {
throw Exception("editing Exif changes mimeType=$mimeType -> $editedMimeType for uri=$uri path=$path")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// 1) as of androidx.exifinterface:exifinterface:1.3.6, editing some specific WEBP
// makes them undecodable by some decoders (including Android's and Chrome's)
// even though `BitmapFactory` successfully decodes their bounds,
// editing may corrupt the file for various reasons,
// so we check whether decoding it throws an exception
// 2) some users have reported corruption when editing JPEG as well,
// but conditions are unknown (specific image, custom ROM, low storage, race condition, etc.)
ImageDecoder.decodeBitmap(ImageDecoder.createSource(editableFile))
}
if (videoBytes != null) {
if (trailerVideoBytes != null) {
// append trailer video, if any
editableFile.appendBytes(videoBytes!!)
editableFile.appendBytes(trailerVideoBytes!!)
}
// copy the edited temporary file back to the original
editableFile.transferTo(outputStream(context, mimeType, uri, path))
if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) {
if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, trailerVideoBytes?.size, editableFile, callback)) {
return false
}
editableFile.delete()
@ -717,19 +743,21 @@ abstract class ImageProvider {
}
val originalFileSize = File(path).length()
val videoSize = MultiPage.getMotionPhotoOffset(context, uri, mimeType, originalFileSize)?.let { it.toInt() + trailerDiff }
var videoBytes: ByteArray? = null
var trailerVideoBytes: ByteArray? = null
val editableFile = StorageUtils.createTempFile(context).apply {
val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)?.let { it + trailerDiff }
val isTrailerVideoValid = trailerVideoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, trailerVideoSize) != null
try {
if (videoSize != null) {
if (trailerVideoSize != null && isTrailerVideoValid) {
// handle motion photo and embedded video separately
val imageSize = (originalFileSize - videoSize).toInt()
videoBytes = ByteArray(videoSize)
val imageSize = (originalFileSize - trailerVideoSize).toInt()
val videoByteSize = trailerVideoSize.toInt()
trailerVideoBytes = ByteArray(videoByteSize)
StorageUtils.openInputStream(context, uri)?.let { input ->
val imageBytes = ByteArray(imageSize)
input.read(imageBytes, 0, imageSize)
input.read(videoBytes, 0, videoSize)
input.read(trailerVideoBytes, 0, videoByteSize)
// copy only the image to a temporary file for editing
// video will be appended after metadata modification
@ -765,15 +793,20 @@ abstract class ImageProvider {
}
}
if (videoBytes != null) {
if (editableFile.length() == 0L) {
callback.onFailure(Exception("editing IPTC yielded an empty file"))
return false
}
if (trailerVideoBytes != null) {
// append trailer video, if any
editableFile.appendBytes(videoBytes!!)
editableFile.appendBytes(trailerVideoBytes!!)
}
// copy the edited temporary file back to the original
editableFile.transferTo(outputStream(context, mimeType, uri, path))
if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) {
if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, trailerVideoBytes?.size, editableFile, callback)) {
return false
}
editableFile.delete()
@ -843,6 +876,7 @@ abstract class ImageProvider {
}
} catch (e: NoClassDefFoundError) {
callback.onFailure(e)
return false
} catch (e: Exception) {
callback.onFailure(e)
return false
@ -882,7 +916,7 @@ abstract class ImageProvider {
}
val originalFileSize = File(path).length()
val videoSize = MultiPage.getMotionPhotoOffset(context, uri, mimeType, originalFileSize)?.let { it.toInt() + trailerDiff }
val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)?.let { it.toInt() + trailerDiff }
val editableFile = StorageUtils.createTempFile(context).apply {
try {
editXmpWithPixy(
@ -900,11 +934,16 @@ abstract class ImageProvider {
}
}
if (editableFile.length() == 0L) {
callback.onFailure(Exception("editing XMP yielded an empty file"))
return false
}
try {
// copy the edited temporary file back to the original
editableFile.transferTo(outputStream(context, mimeType, uri, path))
if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) {
if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, trailerVideoSize, editableFile, callback)) {
return false
}
editableFile.delete()
@ -965,7 +1004,7 @@ abstract class ImageProvider {
path: String,
uri: Uri,
mimeType: String,
trailerOffset: Int?,
trailerOffset: Number?,
editedFile: File,
callback: ImageOpCallback,
): Boolean {
@ -980,7 +1019,7 @@ abstract class ImageProvider {
LOG_TAG, "Edited file length=$expectedLength does not match final document file length=$actualLength. " +
"We need to edit XMP to adjust trailer video offset by $diff bytes."
)
val newTrailerOffset = trailerOffset + diff
val newTrailerOffset = trailerOffset.toLong() + diff
return editXmp(context, path, uri, mimeType, callback, trailerDiff = diff, editCoreXmp = { xmp ->
GoogleXMP.updateTrailingVideoOffset(xmp, trailerOffset, newTrailerOffset)
})
@ -1026,7 +1065,7 @@ abstract class ImageProvider {
uri: Uri,
mimeType: String,
dateMillis: Long?,
shiftMinutes: Long?,
shiftSeconds: Long?,
fields: List<String>,
callback: ImageOpCallback,
) {
@ -1057,9 +1096,9 @@ abstract class ImageProvider {
}
}
shiftMinutes != null -> {
shiftSeconds != null -> {
// shift
val shiftMillis = shiftMinutes * 60000
val shiftMillis = shiftSeconds * 1000
listOf(
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_DATETIME_ORIGINAL,
@ -1245,17 +1284,23 @@ abstract class ImageProvider {
callback: ImageOpCallback,
) {
val originalFileSize = File(path).length()
val videoSize = MultiPage.getMotionPhotoOffset(context, uri, mimeType, originalFileSize)?.toInt()
if (videoSize == null) {
val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)
if (trailerVideoSize == null) {
callback.onFailure(Exception("failed to get trailer video size"))
return
}
val isTrailerVideoValid = MultiPage.getTrailerVideoInfo(context, uri, fileSize = originalFileSize, videoSize = trailerVideoSize) != null
if (!isTrailerVideoValid) {
callback.onFailure(Exception("failed to open trailer video with size=$trailerVideoSize"))
return
}
val editableFile = StorageUtils.createTempFile(context).apply {
try {
val inputStream = StorageUtils.openInputStream(context, uri)
// partial copy
transferFrom(inputStream, originalFileSize - videoSize)
transferFrom(inputStream, originalFileSize - trailerVideoSize)
} catch (e: Exception) {
Log.d(LOG_TAG, "failed to remove trailer video", e)
callback.onFailure(e)
@ -1290,7 +1335,8 @@ abstract class ImageProvider {
}
val originalFileSize = File(path).length()
val videoSize = MultiPage.getMotionPhotoOffset(context, uri, mimeType, originalFileSize)?.toInt()
val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)
val isTrailerVideoValid = trailerVideoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, trailerVideoSize) != null
val editableFile = StorageUtils.createTempFile(context).apply {
try {
outputStream().use { output ->
@ -1306,11 +1352,16 @@ abstract class ImageProvider {
}
}
if (editableFile.length() == 0L) {
callback.onFailure(Exception("removing metadata yielded an empty file"))
return
}
try {
// copy the edited temporary file back to the original
editableFile.transferTo(outputStream(context, mimeType, uri, path))
if (!types.contains(TYPE_XMP) && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) {
if (!types.contains(TYPE_XMP) && isTrailerVideoValid && !checkTrailerOffset(context, path, uri, mimeType, trailerVideoSize, editableFile, callback)) {
return
}
editableFile.delete()

View file

@ -12,6 +12,7 @@ import android.graphics.BitmapFactory
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.RequiresApi
@ -19,6 +20,7 @@ import com.commonsware.cwac.document.DocumentFileCompat
import deckers.thibault.aves.MainActivity
import deckers.thibault.aves.MainActivity.Companion.DELETE_SINGLE_PERMISSION_REQUEST
import deckers.thibault.aves.model.AvesEntry
import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.NameConflictStrategy
import deckers.thibault.aves.model.SourceEntry
@ -49,13 +51,14 @@ import kotlin.coroutines.suspendCoroutine
class MediaStoreImageProvider : ImageProvider() {
fun fetchAll(
context: Context,
knownEntries: Map<Long?, Int?>,
knownEntries: Map<Long?, Long?>,
directory: String?,
handleNewEntry: NewEntryHandler,
) {
val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
Log.d(LOG_TAG, "fetching all media store items for ${knownEntries.size} known entries, directory=$directory")
val isModified = fun(contentId: Long, dateModifiedMillis: Long): Boolean {
val knownDate = knownEntries[contentId]
return knownDate == null || knownDate < dateModifiedSecs
return knownDate == null || knownDate < dateModifiedMillis
}
val handleNew: NewEntryHandler
var selection: String? = null
@ -74,7 +77,7 @@ class MediaStoreImageProvider : ImageProvider() {
val parentCheckDirectory = removeTrailingSeparator(directory)
handleNew = { entry ->
// skip entries in subfolders
val path = entry["path"] as String?
val path = entry[EntryFields.PATH] as String?
if (path != null && File(path).parent == parentCheckDirectory) {
handleNewEntry(entry)
}
@ -89,11 +92,11 @@ class MediaStoreImageProvider : ImageProvider() {
// the provided URI can point to the wrong media collection,
// e.g. a GIF image with the URI `content://media/external/video/media/[ID]`
// so the effective entry URI may not match the provided URI
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
var found = false
val fetched = arrayListOf<FieldMap>()
val id = uri.tryParseId()
val alwaysValid: NewEntryChecker = fun(_: Long, _: Int): Boolean = true
val alwaysValid: NewEntryChecker = fun(_: Long, _: Long): Boolean = true
val onSuccess: NewEntryHandler = fun(entry: FieldMap) { fetched.add(entry) }
if (id != null) {
if (sourceMimeType == null || isImage(sourceMimeType)) {
@ -224,8 +227,8 @@ class MediaStoreImageProvider : ImageProvider() {
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)
val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
val dateAddedSecsColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
val dateModifiedSecsColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
val dateTakenColumn = cursor.getColumnIndex(MediaColumns.DATE_TAKEN)
// image & video for API >=29, only for images for API <29
@ -237,8 +240,8 @@ class MediaStoreImageProvider : ImageProvider() {
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val dateModifiedSecs = cursor.getInt(dateModifiedColumn)
if (isValidEntry(id, dateModifiedSecs)) {
val dateModifiedMillis = cursor.getInt(dateModifiedSecsColumn) * 1000L
if (isValidEntry(id, dateModifiedMillis)) {
// for multiple items, `contentUri` is the root without ID,
// but for single items, `contentUri` already contains the ID
val itemUri = if (contentUriContainsId) contentUri else ContentUris.withAppendedId(contentUri, id)
@ -252,21 +255,22 @@ class MediaStoreImageProvider : ImageProvider() {
if (mimeType == null) {
Log.w(LOG_TAG, "failed to make entry from uri=$itemUri because of null MIME type")
} else {
var entryMap: FieldMap = hashMapOf(
"origin" to SourceEntry.ORIGIN_MEDIA_STORE_CONTENT,
"uri" to itemUri.toString(),
"path" to cursor.getString(pathColumn),
"sourceMimeType" to mimeType,
"width" to width,
"height" to height,
"sourceRotationDegrees" to if (orientationColumn != -1) cursor.getInt(orientationColumn) else 0,
"sizeBytes" to cursor.getLong(sizeColumn),
"dateAddedSecs" to cursor.getInt(dateAddedColumn),
"dateModifiedSecs" to dateModifiedSecs,
"sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
"durationMillis" to durationMillis,
val path = cursor.getString(pathColumn)
var entryFields: FieldMap = hashMapOf(
EntryFields.ORIGIN to SourceEntry.ORIGIN_MEDIA_STORE_CONTENT,
EntryFields.URI to itemUri.toString(),
EntryFields.PATH to path,
EntryFields.SOURCE_MIME_TYPE to mimeType,
EntryFields.WIDTH to width,
EntryFields.HEIGHT to height,
EntryFields.SOURCE_ROTATION_DEGREES to if (orientationColumn != -1) cursor.getInt(orientationColumn) else 0,
EntryFields.SIZE_BYTES to cursor.getLong(sizeColumn),
EntryFields.DATE_ADDED_SECS to cursor.getInt(dateAddedSecsColumn),
EntryFields.DATE_MODIFIED_MILLIS to dateModifiedMillis,
EntryFields.SOURCE_DATE_TAKEN_MILLIS to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
EntryFields.DURATION_MILLIS to durationMillis,
// only for map export
"contentId" to id,
EntryFields.CONTENT_ID to id,
)
if (MimeTypes.isHeic(mimeType)) {
@ -282,8 +286,8 @@ class MediaStoreImageProvider : ImageProvider() {
if (outWidth > 0 && outHeight > 0) {
width = outWidth
height = outHeight
entryMap["width"] = width
entryMap["height"] = height
entryFields[EntryFields.WIDTH] = width
entryFields[EntryFields.HEIGHT] = height
}
}
} catch (e: IOException) {
@ -299,11 +303,13 @@ class MediaStoreImageProvider : ImageProvider() {
// missing some attributes such as width, height, orientation.
// Also, the reported size of raw images is inconsistent across devices
// and Android versions (sometimes the raw size, sometimes the decoded size).
val entry = SourceEntry(entryMap).fillPreCatalogMetadata(context)
entryMap = entry.toMap()
val entry = SourceEntry(entryFields).fillPreCatalogMetadata(context)
entryFields = entry.toMap()
}
handleNewEntry(entryMap)
getFileModifiedDateMillis(path)?.let { entryFields[EntryFields.DATE_MODIFIED_MILLIS] = it }
handleNewEntry(entryFields)
found = true
}
}
@ -451,10 +457,8 @@ class MediaStoreImageProvider : ImageProvider() {
effectiveTargetDir = targetDir
targetDirDocFile = StorageUtils.createDirectoryDocIfAbsent(activity, targetDir)
if (!File(targetDir).exists()) {
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
// download subdirectories can be created later by Media Store insertion
if (!isDownloadSubdir) {
if (!isDownloadSubdir(activity, targetDir)) {
callback.onFailure(Exception("failed to create directory at path=$targetDir"))
return
}
@ -478,64 +482,62 @@ class MediaStoreImageProvider : ImageProvider() {
"success" to false,
)
if (sourcePath != null) {
// on API 30 we cannot get access granted directly to a volume root from its document tree,
// but it is still less constraining to use tree document files than to rely on the Media Store
//
// Relying on `DocumentFile`, we can create an item via `DocumentFile.createFile()`, but:
// - we need to scan the file to get the Media Store content URI
// - the underlying document provider controls the new file name
//
// Relying on the Media Store, we can create an item via `ContentResolver.insert()`
// with a path, and retrieve its content URI, but:
// - the Media Store isolates content by storage volume (e.g. `MediaStore.Images.Media.getContentUri(volumeName)`)
// - the volume name should be lower case, not exactly as the `StorageVolume` UUID
// cf new method in API 30 `StorageVolume.getMediaStoreVolumeName()`
// - inserting on a removable volume works on API 29, but not on API 25 nor 26 (on which API/devices does it work?)
// - there is no documentation regarding support for usage with removable storage
// - the Media Store only allows inserting in specific primary directories ("DCIM", "Pictures") when using scoped storage
try {
val appDir = when {
toBin -> StorageUtils.trashDirFor(activity, sourcePath)
toVault -> File(targetDir)
else -> null
}
if (appDir != null) {
effectiveTargetDir = ensureTrailingSeparator(appDir.path)
targetDirDocFile = DocumentFileCompat.fromFile(appDir)
if (toVault) {
appDir.mkdirs()
}
}
if (effectiveTargetDir != null) {
val newFields = if (isCancelledOp()) skippedFieldMap else {
val sourceFile = File(sourcePath)
if (!sourceFile.exists() && toBin) {
delete(activity, sourceUri, sourcePath, mimeType = mimeType)
deletedFieldMap
} else {
moveSingle(
activity = activity,
sourceFile = sourceFile,
sourceUri = sourceUri,
targetDir = effectiveTargetDir,
targetDirDocFile = targetDirDocFile,
desiredName = desiredName ?: sourceFile.name,
nameConflictStrategy = nameConflictStrategy,
mimeType = mimeType,
copy = copy,
toBin = toBin,
)
}
}
result["newFields"] = newFields
result["success"] = true
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to move to targetDir=$targetDir entry with sourcePath=$sourcePath", e)
// on API 30 we cannot get access granted directly to a volume root from its document tree,
// but it is still less constraining to use tree document files than to rely on the Media Store
//
// Relying on `DocumentFile`, we can create an item via `DocumentFile.createFile()`, but:
// - we need to scan the file to get the Media Store content URI
// - the underlying document provider controls the new file name
//
// Relying on the Media Store, we can create an item via `ContentResolver.insert()`
// with a path, and retrieve its content URI, but:
// - the Media Store isolates content by storage volume (e.g. `MediaStore.Images.Media.getContentUri(volumeName)`)
// - the volume name should be lower case, not exactly as the `StorageVolume` UUID
// cf new method in API 30 `StorageVolume.getMediaStoreVolumeName()`
// - inserting on a removable volume works on API 29, but not on API 25 nor 26 (on which API/devices does it work?)
// - there is no documentation regarding support for usage with removable storage
// - the Media Store only allows inserting in specific primary directories ("DCIM", "Pictures") when using scoped storage
try {
val appDir = when {
toBin -> StorageUtils.trashDirFor(activity, sourcePath ?: StorageUtils.getPrimaryVolumePath(activity))
toVault -> File(targetDir)
else -> null
}
if (appDir != null) {
effectiveTargetDir = ensureTrailingSeparator(appDir.path)
targetDirDocFile = DocumentFileCompat.fromFile(appDir)
if (toVault) {
appDir.mkdirs()
}
}
if (effectiveTargetDir != null) {
val newFields = if (isCancelledOp()) skippedFieldMap else {
val sourceFile = if (sourcePath != null) File(sourcePath) else null
if (sourceFile != null && !sourceFile.exists() && toBin) {
delete(activity, sourceUri, sourcePath, mimeType = mimeType)
deletedFieldMap
} else {
moveSingle(
activity = activity,
sourceFile = sourceFile,
sourceUri = sourceUri,
targetDir = effectiveTargetDir,
targetDirDocFile = targetDirDocFile,
desiredName = desiredName ?: sourceFile?.name ?: sourceUri.lastPathSegment ?: createTimeStampFileName(),
nameConflictStrategy = nameConflictStrategy,
mimeType = mimeType,
copy = copy,
toBin = toBin,
)
}
}
result["newFields"] = newFields
result["success"] = true
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to move to targetDir=$targetDir entry with sourcePath=$sourcePath", e)
}
callback.onSuccess(result)
}
@ -544,7 +546,7 @@ class MediaStoreImageProvider : ImageProvider() {
private suspend fun moveSingle(
activity: Activity,
sourceFile: File,
sourceFile: File?,
sourceUri: Uri,
targetDir: String,
targetDirDocFile: DocumentFileCompat?,
@ -554,21 +556,24 @@ class MediaStoreImageProvider : ImageProvider() {
copy: Boolean,
toBin: Boolean,
): FieldMap {
val sourcePath = sourceFile.path
val sourceDir = sourceFile.parent?.let { ensureTrailingSeparator(it) }
val sourcePath = sourceFile?.path
val sourceExtension = sourceFile?.extension
val sourceDir = sourceFile?.parent?.let { ensureTrailingSeparator(it) }
if (sourceDir == targetDir && !(copy && nameConflictStrategy == NameConflictStrategy.RENAME)) {
// nothing to do unless it's a renamed copy
return skippedFieldMap
}
val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")
val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
val resolution = resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = targetDir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = mimeType,
defaultExtension = sourceExtension,
conflictStrategy = nameConflictStrategy,
) ?: return skippedFieldMap
)
val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
val sourceDocFile = DocumentFileCompat.fromSingleUri(activity, sourceUri)
val targetPath = createSingle(
@ -577,6 +582,7 @@ class MediaStoreImageProvider : ImageProvider() {
targetDir = targetDir,
targetDirDocFile = targetDirDocFile,
targetNameWithoutExtension = targetNameWithoutExtension,
defaultExtension = sourceExtension,
) { output: OutputStream ->
try {
sourceDocFile.copyTo(output)
@ -598,8 +604,8 @@ class MediaStoreImageProvider : ImageProvider() {
}
return if (toBin) {
hashMapOf(
"trashed" to true,
"trashPath" to targetPath,
EntryFields.TRASHED to true,
EntryFields.TRASH_PATH to targetPath,
)
} else {
scanNewPath(activity, targetPath, mimeType)
@ -612,24 +618,23 @@ class MediaStoreImageProvider : ImageProvider() {
targetDir: String,
targetDirDocFile: DocumentFileCompat?,
targetNameWithoutExtension: String,
defaultExtension: String?,
write: (OutputStream) -> Unit,
): String {
if (StorageUtils.isInVault(activity, targetDir)) {
return insertByFile(
targetDir = targetDir,
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}",
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType, defaultExtension)}",
write = write,
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
if (isDownloadSubdir) {
if (isDownloadSubdir(activity, targetDir)) {
return insertByMediaStore(
activity = activity,
targetDir = targetDir,
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}",
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType, defaultExtension)}",
write = write,
)
}
@ -641,10 +646,18 @@ class MediaStoreImageProvider : ImageProvider() {
targetDir = targetDir,
targetDirDocFile = targetDirDocFile,
targetNameWithoutExtension = targetNameWithoutExtension,
defaultExtension = defaultExtension,
write = write,
)
}
private fun isDownloadSubdir(context: Context, dir: String): Boolean {
val volumePath = StorageUtils.getVolumePath(context, dir) ?: return false
val downloadDirPath = ensureTrailingSeparator(File(volumePath, Environment.DIRECTORY_DOWNLOADS).path)
// effective download path may have a different case
return dir.lowercase().startsWith(downloadDirPath.lowercase())
}
private fun insertByFile(
targetDir: String,
targetFileName: String,
@ -692,6 +705,7 @@ class MediaStoreImageProvider : ImageProvider() {
targetDir: String,
targetDirDocFile: DocumentFileCompat?,
targetNameWithoutExtension: String,
defaultExtension: String?,
write: (OutputStream) -> Unit,
): String {
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
@ -700,8 +714,22 @@ class MediaStoreImageProvider : ImageProvider() {
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
// through a document URI, not a tree URI
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
val targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
val targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
var targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
var targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
// providing a display name and a MIME type does not guarantee
// that the created document will be backed by a file with a valid media extension,
// but having an extension is essential for media detection by Android,
// so we retry with a display name that includes the extension
if ((targetDocFile.extension == null || targetDocFile.extension.isEmpty() || targetDocFile.extension == "bin") && defaultExtension != null) {
if (targetDocFile.exists()) {
targetDocFile.delete()
}
val extension = if (defaultExtension.startsWith(".")) defaultExtension else ".$defaultExtension"
targetTreeFile = targetDirDocFile.createFile(mimeType, "$targetNameWithoutExtension$extension")
targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
}
try {
targetDocFile.openOutputStream().use(write)
@ -818,18 +846,32 @@ class MediaStoreImageProvider : ImageProvider() {
try {
val cursor = context.contentResolver.query(uri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.SIZE).let { if (it != -1) newFields["sizeBytes"] = cursor.getLong(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields[EntryFields.DATE_MODIFIED_MILLIS] = cursor.getInt(it) * 1000 }
cursor.getColumnIndex(MediaStore.MediaColumns.SIZE).let { if (it != -1) newFields[EntryFields.SIZE_BYTES] = cursor.getLong(it) }
cursor.close()
}
} catch (e: Exception) {
callback.onFailure(e)
return@scanFile
}
getFileModifiedDateMillis(path)?.let { newFields[EntryFields.DATE_MODIFIED_MILLIS] = it }
callback.onSuccess(newFields)
}
}
// try to fetch the modified date from the file,
// as it is more precise than the one from the Media Store
private fun getFileModifiedDateMillis(path: String?): Long? {
if (path != null) {
try {
return File(path).lastModified()
} catch (securityException: SecurityException) {
// ignore
}
}
return null
}
private fun scanObsoletePath(context: Context, uri: Uri, path: String, mimeType: String) {
val file = File(path)
val delayMillis = 500L
@ -907,14 +949,15 @@ class MediaStoreImageProvider : ImageProvider() {
val cursor = context.contentResolver.query(uri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
val newFields = hashMapOf<String, Any?>(
"origin" to SourceEntry.ORIGIN_MEDIA_STORE_CONTENT,
"uri" to uri.toString(),
"contentId" to uri.tryParseId(),
"path" to path,
EntryFields.ORIGIN to SourceEntry.ORIGIN_MEDIA_STORE_CONTENT,
EntryFields.URI to uri.toString(),
EntryFields.CONTENT_ID to uri.tryParseId(),
EntryFields.PATH to path,
)
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED).let { if (it != -1) newFields["dateAddedSecs"] = cursor.getInt(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED).let { if (it != -1) newFields[EntryFields.DATE_ADDED_SECS] = cursor.getInt(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields[EntryFields.DATE_MODIFIED_MILLIS] = cursor.getInt(it) * 1000 }
cursor.close()
getFileModifiedDateMillis(path)?.let { newFields[EntryFields.DATE_MODIFIED_MILLIS] = it }
return newFields
}
} catch (e: Exception) {
@ -1025,4 +1068,4 @@ object MediaColumns {
typealias NewEntryHandler = (entry: FieldMap) -> Unit
private typealias NewEntryChecker = (contentId: Long, dateModifiedSecs: Int) -> Boolean
private typealias NewEntryChecker = (contentId: Long, dateModifiedMillis: Long) -> Boolean

View file

@ -7,6 +7,7 @@ import android.provider.OpenableColumns
import android.util.Log
import deckers.thibault.aves.metadata.Metadata
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.SourceEntry
import deckers.thibault.aves.utils.LogUtils
@ -17,7 +18,7 @@ open class UnknownContentProvider : ImageProvider() {
open val reliableProviderMimeType: Boolean
get() = false
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
var mimeType = sourceMimeType
if (sourceMimeType == null || !reliableProviderMimeType) {
// source MIME type may be incorrect, so we get a second opinion if possible
@ -43,9 +44,9 @@ open class UnknownContentProvider : ImageProvider() {
}
val fields: FieldMap = hashMapOf(
"origin" to SourceEntry.ORIGIN_UNKNOWN_CONTENT,
"uri" to uri.toString(),
"sourceMimeType" to mimeType,
EntryFields.ORIGIN to SourceEntry.ORIGIN_UNKNOWN_CONTENT,
EntryFields.URI to uri.toString(),
EntryFields.SOURCE_MIME_TYPE to mimeType,
)
try {
// some providers do not provide the mandatory `OpenableColumns`
@ -53,11 +54,11 @@ open class UnknownContentProvider : ImageProvider() {
// e.g. `content://mms/part/[id]` on Android KitKat
val cursor = context.contentResolver.query(uri, null, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME).let { if (it != -1) fields["title"] = cursor.getString(it) }
cursor.getColumnIndex(OpenableColumns.SIZE).let { if (it != -1) fields["sizeBytes"] = cursor.getLong(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DATA).let { if (it != -1) fields["path"] = cursor.getString(it) }
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME).let { if (it != -1) fields[EntryFields.TITLE] = cursor.getString(it) }
cursor.getColumnIndex(OpenableColumns.SIZE).let { if (it != -1) fields[EntryFields.SIZE_BYTES] = cursor.getLong(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DATA).let { if (it != -1) fields[EntryFields.PATH] = cursor.getString(it) }
// mime type fallback if it was not provided and not found via `metadata-extractor`
cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE).let { if (it != -1 && mimeType == null) fields["sourceMimeType"] = cursor.getString(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE).let { if (it != -1 && mimeType == null) fields[EntryFields.SOURCE_MIME_TYPE] = cursor.getString(it) }
cursor.close()
}
} catch (e: Exception) {
@ -65,13 +66,13 @@ open class UnknownContentProvider : ImageProvider() {
return
}
if (fields["sourceMimeType"] == null) {
if (fields[EntryFields.SOURCE_MIME_TYPE] == null) {
callback.onFailure(Exception("Failed to find MIME type for uri=$uri"))
return
}
val entry = SourceEntry(fields).fillPreCatalogMetadata(context)
if (entry.isSized || entry.isSvg || entry.isVideo) {
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
callback.onSuccess(entry.toMap())
} else {
callback.onFailure(Exception("entry has no size"))

View file

@ -2,27 +2,121 @@ package deckers.thibault.aves.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.ColorSpace
import android.os.Build
import android.util.Half
import android.util.Log
import androidx.annotation.RequiresApi
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import deckers.thibault.aves.metadata.Metadata.getExifCode
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
object BitmapUtils {
private val LOG_TAG = LogUtils.createTag<BitmapUtils>()
private const val INITIAL_BUFFER_SIZE = 2 shl 17 // 256kB
// arbitrary size to detect buffer that may yield an OOM
private const val BUFFER_SIZE_DANGER_THRESHOLD = 3 * (1 shl 20) // MB
private val freeBaos = ArrayList<ByteArrayOutputStream>()
private val mutex = Mutex()
const val ARGB_8888_BYTE_SIZE = 4
private const val INT_BYTE_SIZE = 4
private const val MAX_2_BITS_FLOAT = 0x3.toFloat()
private const val MAX_8_BITS_FLOAT = 0xff.toFloat()
private const val MAX_10_BITS_FLOAT = 0x3ff.toFloat()
private const val RAW_BYTES_TRAILER_LENGTH = INT_BYTE_SIZE * 2
// bytes per pixel with different bitmap config
private const val BPP_ALPHA_8 = 1
private const val BPP_RGB_565 = 2
private const val BPP_ARGB_8888 = 4
private const val BPP_RGBA_1010102 = 4
private const val BPP_RGBA_F16 = 8
private fun getBytePerPixel(config: Bitmap.Config?): Int {
return when (config) {
Bitmap.Config.ALPHA_8 -> BPP_ALPHA_8
Bitmap.Config.RGB_565 -> BPP_RGB_565
Bitmap.Config.ARGB_8888 -> BPP_ARGB_8888
else -> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && config == Bitmap.Config.RGBA_F16) {
BPP_RGBA_F16
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && config == Bitmap.Config.RGBA_1010102) {
BPP_RGBA_1010102
} else {
// default
BPP_ARGB_8888
}
}
}
}
fun getExpectedImageSize(pixelCount: Long, config: Bitmap.Config?): Long {
return pixelCount * getBytePerPixel(config)
}
fun getRawBytes(bitmap: Bitmap?, recycle: Boolean): ByteArray? {
bitmap ?: return null
val byteCount = bitmap.byteCount
val width = bitmap.width
val height = bitmap.height
val config = bitmap.config
val colorSpace = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) bitmap.colorSpace else null
if (!MemoryUtils.canAllocate(byteCount)) {
throw Exception("bitmap buffer is $byteCount bytes, which cannot be allocated to a new byte array")
}
try {
// `ByteBuffer` initial order is always `BIG_ENDIAN`
var bytes = ByteBuffer.allocate(byteCount + RAW_BYTES_TRAILER_LENGTH).apply {
bitmap.copyPixelsToBuffer(this)
}.array()
// do not access bitmap after recycling
if (recycle) bitmap.recycle()
// convert pixel format and color space, if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
colorSpace?.let { srcColorSpace ->
val dstColorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
val connector = ColorSpace.connect(srcColorSpace, dstColorSpace)
if (config == Bitmap.Config.ARGB_8888) {
if (srcColorSpace != dstColorSpace) {
argb8888ToArgb8888(bytes, connector, end = byteCount)
}
} else if (config == Bitmap.Config.RGBA_F16) {
rgbaf16ToArgb8888(bytes, connector, end = byteCount)
val newConfigByteCount = byteCount / (BPP_RGBA_F16 / BPP_ARGB_8888)
bytes = bytes.sliceArray(0..<newConfigByteCount + RAW_BYTES_TRAILER_LENGTH)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && config == Bitmap.Config.RGBA_1010102) {
rgba1010102ToArgb8888(bytes, connector, end = byteCount)
}
}
}
// append bitmap size for use by the caller to interpret the raw bytes
val trailerOffset = bytes.size - RAW_BYTES_TRAILER_LENGTH
bytes = ByteBuffer.wrap(bytes).apply {
position(trailerOffset)
putInt(width)
putInt(height)
}.array()
return bytes
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to get bytes from bitmap", e)
}
return null
}
suspend fun getEncodedBytes(bitmap: Bitmap?, canHaveAlpha: Boolean = false, quality: Int = 100, recycle: Boolean): ByteArray? {
bitmap ?: return null
suspend fun Bitmap.getBytes(canHaveAlpha: Boolean = false, quality: Int = 100, recycle: Boolean): ByteArray? {
val stream: ByteArrayOutputStream
mutex.withLock {
// this method is called a lot, so we try and reuse output streams
@ -34,19 +128,17 @@ object BitmapUtils {
}
}
try {
// the Bitmap raw bytes are not decodable by Flutter
// we need to format them (compress, or add a BMP header) before sending them
// `Bitmap.CompressFormat.PNG` is slower than `JPEG`, but it allows transparency
// the BMP format allows an alpha channel, but Android decoding seems to ignore it
if (canHaveAlpha && hasAlpha()) {
this.compress(Bitmap.CompressFormat.PNG, quality, stream)
if (canHaveAlpha && bitmap.hasAlpha()) {
bitmap.compress(Bitmap.CompressFormat.PNG, quality, stream)
} else {
this.compress(Bitmap.CompressFormat.JPEG, quality, stream)
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream)
}
if (recycle) this.recycle()
if (recycle) bitmap.recycle()
val bufferSize = stream.size()
if (bufferSize > BUFFER_SIZE_DANGER_THRESHOLD && !MemoryUtils.canAllocate(bufferSize)) {
if (!MemoryUtils.canAllocate(bufferSize)) {
throw Exception("bitmap compressed to $bufferSize bytes, which cannot be allocated to a new byte array")
}
@ -62,6 +154,107 @@ object BitmapUtils {
return null
}
// convert bytes, without reallocation:
// - from original color space to sRGB.
@RequiresApi(Build.VERSION_CODES.O)
private fun argb8888ToArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) {
// unpacking from ARGB_8888 and packing to ARGB_8888
// stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR]
for (i in start..<end step BPP_ARGB_8888) {
// mask with `0xff` to yield values in [0, 255], instead of [-128, 127]
val iB = bytes[i + 2].toInt() and 0xff
val iG = bytes[i + 1].toInt() and 0xff
val iR = bytes[i].toInt() and 0xff
// components as floats in sRGB
val srgbFloats = connector.transform(iR / MAX_8_BITS_FLOAT, iG / MAX_8_BITS_FLOAT, iB / MAX_8_BITS_FLOAT)
val srgbR = (srgbFloats[0] * 255.0f + 0.5f).toInt()
val srgbG = (srgbFloats[1] * 255.0f + 0.5f).toInt()
val srgbB = (srgbFloats[2] * 255.0f + 0.5f).toInt()
// keep alpha as it is, in `bytes[i + 3]`
bytes[i + 2] = srgbB.toByte()
bytes[i + 1] = srgbG.toByte()
bytes[i] = srgbR.toByte()
}
}
// convert bytes, without reallocation:
// - from config RGBA_F16 to ARGB_8888,
// - from original color space to sRGB.
@RequiresApi(Build.VERSION_CODES.O)
private fun rgbaf16ToArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) {
val indexDivider = BPP_RGBA_F16 / BPP_ARGB_8888
for (i in start..<end step BPP_RGBA_F16) {
// unpacking from RGBA_F16
// stored as [7,6,5,4,3,2,1,0] -> [AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB GGGGGGGG GGGGGGGG RRRRRRRR RRRRRRRR]
val i7 = bytes[i + 7].toInt()
val i6 = bytes[i + 6].toInt()
val i5 = bytes[i + 5].toInt()
val i4 = bytes[i + 4].toInt()
val i3 = bytes[i + 3].toInt()
val i2 = bytes[i + 2].toInt()
val i1 = bytes[i + 1].toInt()
val i0 = bytes[i].toInt()
val hA = Half((((i7 and 0xff) shl 8) or (i6 and 0xff)).toShort())
val hB = Half((((i5 and 0xff) shl 8) or (i4 and 0xff)).toShort())
val hG = Half((((i3 and 0xff) shl 8) or (i2 and 0xff)).toShort())
val hR = Half((((i1 and 0xff) shl 8) or (i0 and 0xff)).toShort())
// components as floats in sRGB
val srgbFloats = connector.transform(hR.toFloat(), hG.toFloat(), hB.toFloat())
val srgbR = (srgbFloats[0] * 255.0f + 0.5f).toInt()
val srgbG = (srgbFloats[1] * 255.0f + 0.5f).toInt()
val srgbB = (srgbFloats[2] * 255.0f + 0.5f).toInt()
val alpha = (hA.toFloat() * 255.0f + 0.5f).toInt()
// packing to ARGB_8888
// stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR]
val dstI = i / indexDivider
bytes[dstI + 3] = alpha.toByte()
bytes[dstI + 2] = srgbB.toByte()
bytes[dstI + 1] = srgbG.toByte()
bytes[dstI] = srgbR.toByte()
}
}
// convert bytes, without reallocation:
// - from config RGBA_1010102 to ARGB_8888,
// - from original color space to sRGB.
@RequiresApi(Build.VERSION_CODES.O)
private fun rgba1010102ToArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) {
val alphaFactor = 255.0f / MAX_2_BITS_FLOAT
for (i in start..<end step BPP_RGBA_1010102) {
// unpacking from RGBA_1010102
// stored as [3,2,1,0] -> [AABBBBBB BBBBGGGG GGGGGGRR RRRRRRRR]
val i3 = bytes[i + 3].toInt()
val i2 = bytes[i + 2].toInt()
val i1 = bytes[i + 1].toInt()
val i0 = bytes[i].toInt()
val iA = ((i3 and 0xc0) shr 6)
val iB = ((i3 and 0x3f) shl 4) or ((i2 and 0xf0) shr 4)
val iG = ((i2 and 0x0f) shl 6) or ((i1 and 0xfc) shr 2)
val iR = ((i1 and 0x03) shl 8) or ((i0 and 0xff) shr 0)
// components as floats in sRGB
val srgbFloats = connector.transform(iR / MAX_10_BITS_FLOAT, iG / MAX_10_BITS_FLOAT, iB / MAX_10_BITS_FLOAT)
val srgbR = (srgbFloats[0] * 255.0f + 0.5f).toInt()
val srgbG = (srgbFloats[1] * 255.0f + 0.5f).toInt()
val srgbB = (srgbFloats[2] * 255.0f + 0.5f).toInt()
val alpha = (iA * alphaFactor + 0.5f).toInt()
// packing to ARGB_8888
// stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR]
bytes[i + 3] = alpha.toByte()
bytes[i + 2] = srgbB.toByte()
bytes[i + 1] = srgbG.toByte()
bytes[i] = srgbR.toByte()
}
}
fun applyExifOrientation(context: Context, bitmap: Bitmap?, rotationDegrees: Int?, isFlipped: Boolean?): Bitmap? {
if (bitmap == null || rotationDegrees == null || isFlipped == null) return bitmap
if (rotationDegrees == 0 && !isFlipped) return bitmap

View file

@ -90,12 +90,7 @@ object BmpWriter {
var column = 0
while (column < biWidth) {
/*
alpha: (value shr 24 and 0xFF).toByte()
red: (value shr 16 and 0xFF).toByte()
green: (value shr 8 and 0xFF).toByte()
blue: (value and 0xFF).toByte()
*/
// non-premultiplied ARGB values in the sRGB color space
value = pixels[column]
// blue: [0], green: [1], red: [2]
rgb[0] = (value and 0xFF).toByte()

View file

@ -8,6 +8,8 @@ fun ByteBuffer.toByteArray(): ByteArray {
return bytes
}
fun Int.toHex(): String = "0x${byteArrayOf(shr(8).toByte(), toByte()).toHex()}"
fun ByteArray.toHex(): String = joinToString(separator = "") { it.toHex() }
fun Byte.toHex(): String = "%02x".format(this)

View file

@ -20,6 +20,7 @@ fun <E> MutableList<E>.compatRemoveIf(filter: (t: E) -> Boolean): Boolean {
}
// Boyer-Moore algorithm for pattern searching
// Returns: an index of the first occurrence of the pattern or -1 if none is found.
fun ByteArray.indexOfBytes(pattern: ByteArray, start: Int = 0): Int {
val n: Int = this.size
val m: Int = pattern.size

View file

@ -56,13 +56,7 @@ fun Geocoder.getFromLocationCompat(
onError: (errorCode: String, errorMessage: String?, errorDetails: Any?) -> Unit,
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getFromLocation(latitude, longitude, maxResults, object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: List<Address?>) = processAddresses(addresses.filterNotNull())
override fun onError(errorMessage: String?) {
onError("getAddress-asyncerror", "failed to get address", errorMessage)
}
})
Compat33.geocoderGetFromLocation(this, latitude, longitude, maxResults, processAddresses, onError)
} else {
try {
@Suppress("deprecation")

View file

@ -0,0 +1,30 @@
package deckers.thibault.aves.utils
import android.location.Address
import android.location.Geocoder
import android.os.Build
import androidx.annotation.RequiresApi
/**
* Compatibility layer in a separate object to avoid class loading issues on older Android versions.
* e.g. `ClassNotFoundException` for `android.location.Geocoder$GeocodeListener`
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
object Compat33 {
fun geocoderGetFromLocation(
geocoder: Geocoder,
latitude: Double,
longitude: Double,
maxResults: Int,
processAddresses: (addresses: List<Address>) -> Unit,
onError: (errorCode: String, errorMessage: String?, errorDetails: Any?) -> Unit,
) {
geocoder.getFromLocation(latitude, longitude, maxResults, object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: List<Address?>) = processAddresses(addresses.filterNotNull())
override fun onError(errorMessage: String?) {
onError("getAddress-asyncerror", "failed to get address", errorMessage)
}
})
}
}

View file

@ -0,0 +1,10 @@
package deckers.thibault.aves.utils
inline fun <reified T : Throwable> Exception.anyCauseIs(): Boolean {
var cause: Throwable? = this
while (cause != null) {
if (cause is T) return true
cause = cause.cause
}
return false
}

View file

@ -1,8 +1,6 @@
package deckers.thibault.aves.utils
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
@ -71,29 +69,4 @@ object FlutterUtils {
r.run()
}
}
fun Intent.enableSoftwareRendering() {
putExtra("enable-software-rendering", true)
Log.i(LOG_TAG, "Enable software rendering")
}
fun isSoftwareRenderingRequired() = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT && isEmulator
private val isEmulator: Boolean
get() = (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")
|| Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.HARDWARE.contains("goldfish")
|| Build.HARDWARE.contains("ranchu")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| Build.PRODUCT.contains("sdk_google")
|| Build.PRODUCT.contains("google_sdk")
|| Build.PRODUCT.contains("sdk")
|| Build.PRODUCT.contains("sdk_x86")
|| Build.PRODUCT.contains("vbox86p")
|| Build.PRODUCT.contains("emulator")
|| Build.PRODUCT.contains("simulator"))
}

View file

@ -0,0 +1,9 @@
package deckers.thibault.aves.utils
import kotlin.math.log2
import kotlin.math.pow
object MathUtils {
fun highestPowerOf2(x: Int): Int = highestPowerOf2(x.toDouble())
fun highestPowerOf2(x: Double): Int = if (x < 1) 0 else 2.toDouble().pow(log2(x).toInt()).toInt()
}

View file

@ -1,8 +1,8 @@
package deckers.thibault.aves.utils
import android.webkit.MimeTypeMap
import androidx.exifinterface.media.ExifInterface
import deckers.thibault.aves.decoder.MultiPageImage
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
object MimeTypes {
const val ANY = "*/*"
@ -28,7 +28,8 @@ object MimeTypes {
private const val CR2 = "image/x-canon-cr2"
private const val CRW = "image/x-canon-crw"
private const val DCR = "image/x-kodak-dcr"
const val DNG = "image/x-adobe-dng"
const val DNG = "image/dng"
const val DNG_ADOBE = "image/x-adobe-dng"
private const val ERF = "image/x-epson-erf"
private const val K25 = "image/x-kodak-k25"
private const val KDC = "image/x-kodak-kdc"
@ -71,7 +72,7 @@ object MimeTypes {
fun isRaw(mimeType: String): Boolean {
return when (mimeType) {
ARW, CR2, CRW, DCR, DNG, ERF, K25, KDC, MRW, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SR2, SRF, SRW, X3F -> true
ARW, CR2, CRW, DCR, DNG, DNG_ADOBE, ERF, K25, KDC, MRW, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SR2, SRF, SRW, X3F -> true
else -> false
}
}
@ -83,11 +84,11 @@ object MimeTypes {
else -> false
}
// as of Flutter v3.16.4, with additional custom handling for SVG
fun canDecodeWithFlutter(mimeType: String, pageId: Int?, rotationDegrees: Int?, isFlipped: Boolean?) = when (mimeType) {
// as of Flutter v3.16.4, with additional custom handling for SVG in Dart,
// while handling still PNG and JPEG on Android for color space and config conversion
fun canDecodeWithFlutter(mimeType: String, isAnimated: Boolean) = when (mimeType) {
GIF, WEBP, BMP, WBMP, ICO, SVG -> true
JPEG -> (pageId ?: 0) == 0
PNG -> (rotationDegrees ?: 0) == 0 && !(isFlipped ?: false)
JPEG, PNG -> isAnimated
else -> false
}
@ -98,9 +99,12 @@ object MimeTypes {
else -> true
}
// as of `ExifInterface` v1.3.1, `isSupportedMimeType` reports
// no support for TIFF images, but it can actually open them (maybe other formats too)
fun canReadWithExifInterface(mimeType: String, strict: Boolean = true) = ExifInterface.isSupportedMimeType(mimeType) || !strict
// as of `ExifInterface` v1.4.0-alpha01, `isSupportedMimeType` reports
// no support for AVIF/TIFF images, but it can actually open them (maybe other formats too)
fun canReadWithExifInterface(mimeType: String, strict: Boolean = true): Boolean {
if (!strict) return true
return ExifInterface.isSupportedMimeType(mimeType) || mimeType == AVIF
}
// as of latest PixyMeta
fun canReadWithPixyMeta(mimeType: String) = when (mimeType) {
@ -142,7 +146,7 @@ object MimeTypes {
return if (pageId != null && MultiPageImage.isSupported(mimeType)) {
true
} else when (mimeType) {
DNG, HEIC, HEIF, PNG, WEBP -> true
AVIF, DNG, DNG_ADOBE, HEIC, HEIF, PNG, WEBP -> true
else -> false
}
}
@ -151,7 +155,7 @@ object MimeTypes {
// according to EXIF orientation when decoding images of known formats
// but we need to rotate the decoded bitmap for the other formats
fun needRotationAfterContentResolverThumbnail(mimeType: String) = when (mimeType) {
DNG, PNG -> true
DNG, DNG_ADOBE, PNG -> true
else -> false
}
@ -159,12 +163,24 @@ object MimeTypes {
// among other refs:
// - https://android.googlesource.com/platform/external/mime-support/+/refs/heads/master/mime.types
fun extensionFor(mimeType: String): String? = when (mimeType) {
fun extensionFor(mimeType: String, defaultExtension: String?): String = when (mimeType) {
AVI, AVI_VND -> ".avi"
DNG, DNG_ADOBE -> ".dng"
HEIC, HEIF -> ".heif"
MP2T, MP2TS -> ".m2ts"
PSD_VND, PSD_X -> ".psd"
else -> MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.let { ".$it" }
else -> {
val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: defaultExtension
if (ext != null) {
// fallback to provided extension when available,
// typically the original file extension when moving/renaming
if (ext.startsWith(".")) ext else ".$ext"
} else {
// fallback to generic extensions,
// as incorrect file extensions are better than none for media detection
if (isVideo(mimeType)) ".mp4" else ".jpg"
}
}
}
val TIFF_EXTENSION_PATTERN = Regex(".*\\.tiff?", RegexOption.IGNORE_CASE)

View file

@ -17,6 +17,7 @@ import deckers.thibault.aves.PendingStorageAccessResultHandler
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.StorageUtils.PathSegments
import java.io.File
import java.util.Locale
import java.util.concurrent.CompletableFuture
object PermissionManager {
@ -28,7 +29,6 @@ object PermissionManager {
Environment.DIRECTORY_PICTURES,
)
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
suspend fun requestDirectoryAccess(activity: Activity, path: String, onGranted: (uri: Uri) -> Unit, onDenied: () -> Unit) {
Log.i(LOG_TAG, "request user to select and grant access permission to path=$path")
@ -87,6 +87,7 @@ object PermissionManager {
fun getInaccessibleDirectories(context: Context, dirPaths: List<String>): List<Map<String, String>> {
val concreteDirPaths = dirPaths.filter { it != StorageUtils.TRASH_PATH_PLACEHOLDER }
val accessibleDirs = getAccessibleDirs(context)
val restrictedPrimaryDirectoriesLower = getRestrictedPrimaryDirectories().map { it.lowercase(Locale.ROOT) }
// find set of inaccessible directories for each volume
val dirsPerVolume = HashMap<String, MutableSet<String>>()
@ -97,12 +98,12 @@ object PermissionManager {
segments.volumePath?.let { volumePath ->
val dirSet = dirsPerVolume[volumePath] ?: HashSet()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// request primary directory on volume from Android 11
// request primary directory on volume from Android 11 (API 30)
val relativeDir = segments.relativeDir
if (relativeDir != null) {
val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() }
val primaryDir = dirSegments.firstOrNull()
if (getRestrictedPrimaryDirectories().contains(primaryDir) && dirSegments.size > 1) {
if (dirSegments.size > 1 && restrictedPrimaryDirectoriesLower.contains(primaryDir?.lowercase(Locale.ROOT))) {
// request secondary directory (if any) for restricted primary directory
val dir = dirSegments.take(2).joinToString(File.separator)
// only register directories that exist on storage, so they can be selected for access grant
@ -141,17 +142,17 @@ object PermissionManager {
fun canInsertByMediaStore(directories: List<FieldMap>): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val insertionDirsLower = MEDIA_STORE_INSERTION_PRIMARY_DIRS.map { it.lowercase(Locale.ROOT) }
directories.all {
val relativeDir = it["relativeDir"] as String
val segments = relativeDir.split(File.separator)
segments.isNotEmpty() && MEDIA_STORE_INSERTION_PRIMARY_DIRS.contains(segments.first())
segments.isNotEmpty() && insertionDirsLower.contains(segments.first().lowercase(Locale.ROOT))
}
} else {
true
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun revokeDirectoryAccess(context: Context, path: String): Boolean {
return StorageUtils.convertDirPathToTreeDocumentUri(context, path)?.let {
releaseUriPermission(context, it)
@ -162,11 +163,9 @@ object PermissionManager {
// returns paths matching directory URIs granted by the user
fun getGrantedDirs(context: Context): Set<String> {
val grantedDirs = HashSet<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
for (uriPermission in context.contentResolver.persistedUriPermissions) {
val dirPath = StorageUtils.convertTreeDocumentUriToDirPath(context, uriPermission.uri)
dirPath?.let { grantedDirs.add(it) }
}
for (uriPermission in context.contentResolver.persistedUriPermissions) {
val dirPath = StorageUtils.convertTreeDocumentUriToDirPath(context, uriPermission.uri)
dirPath?.let { grantedDirs.add(it) }
}
return grantedDirs
}
@ -176,7 +175,6 @@ object PermissionManager {
val accessibleDirs = HashSet(getGrantedDirs(context))
accessibleDirs.addAll(context.getExternalFilesDirs(null).filterNotNull().map { it.path })
// from API 19 / Android 4.4 / KitKat, removable storage requires access permission, at the file level
// from API 21 / Android 5.0 / Lollipop, removable storage requires access permission, but directory access grant is possible
// from API 30 / Android 11 / R, any storage requires access permission
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
@ -216,19 +214,6 @@ object PermissionManager {
)
})
}
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT_WATCH
) {
// removable storage requires access permission, at the file level
// without directory access, we consider the whole volume restricted
val primaryVolume = StorageUtils.getPrimaryVolumePath(context)
val nonPrimaryVolumes = StorageUtils.getVolumePaths(context).filter { it != primaryVolume }
dirs.addAll(nonPrimaryVolumes.map {
hashMapOf(
"volumePath" to it,
"relativeDir" to "",
)
})
}
return dirs
}
@ -236,7 +221,6 @@ object PermissionManager {
// As of Android 11, `MediaStore.getDocumentUri` fails if any of the persisted
// URI permissions we hold points to a folder that no longer exists,
// so we should remove these obsolete URIs before proceeding.
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun sanitizePersistedUriPermissions(context: Context) {
try {
for (uriPermission in context.contentResolver.persistedUriPermissions) {
@ -252,7 +236,6 @@ object PermissionManager {
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun releaseUriPermission(context: Context, it: Uri) {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context.contentResolver.releasePersistableUriPermission(it, flags)

View file

@ -9,14 +9,14 @@ import android.content.pm.PackageManager
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.ParcelFileDescriptor
import android.os.storage.StorageManager
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import androidx.core.text.isDigitsOnly
import com.commonsware.cwac.document.DocumentFileCompat
import deckers.thibault.aves.model.provider.ImageProvider
import deckers.thibault.aves.utils.FileUtils.transferFrom
@ -29,7 +29,7 @@ import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
import java.util.Locale
import java.util.regex.Pattern
object StorageUtils {
@ -82,7 +82,8 @@ object StorageUtils {
return null
}
val trashDir = File(externalFilesDir, "trash")
if (!trashDir.exists() && !trashDir.mkdirs()) {
trashDir.mkdirs()
if (!trashDir.exists()) {
Log.e(LOG_TAG, "failed to create directories at path=$trashDir")
return null
}
@ -121,10 +122,6 @@ object StorageUtils {
return getVolumePaths(context).firstOrNull { anyPath.startsWith(it) }
}
fun getDownloadDirPath(context: Context, anyPath: String): String? {
return getVolumePath(context, anyPath)?.let { volumePath -> ensureTrailingSeparator(File(volumePath, Environment.DIRECTORY_DOWNLOADS).path) }
}
private fun getPathStepIterator(context: Context, anyPath: String, root: String?): Iterator<String?>? {
val rootLength = (root ?: getVolumePath(context, anyPath))?.length ?: return null
@ -233,7 +230,7 @@ object StorageUtils {
// Device has emulated storage; external storage paths should have userId burned into them.
// /storage/emulated/[0,1,2,...]/
val path = getPrimaryVolumePath(context)
val rawUserId = path.split(File.separator).lastOrNull(String::isNotEmpty)?.takeIf { TextUtils.isDigitsOnly(it) } ?: ""
val rawUserId = path.split(File.separator).lastOrNull(String::isNotEmpty)?.takeIf { it.isDigitsOnly() } ?: ""
if (rawUserId.isEmpty()) {
paths.add(rawEmulatedStorageTarget)
} else {
@ -381,7 +378,6 @@ object StorageUtils {
// e.g.
// /storage/emulated/0/ -> content://com.android.externalstorage.documents/tree/primary%3A
// /storage/10F9-3F13/Pictures/ -> content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun convertDirPathToTreeDocumentUri(context: Context, dirPath: String): Uri? {
val uuid = getVolumeUuidForDocumentUri(context, dirPath)
if (uuid != null) {
@ -446,7 +442,7 @@ object StorageUtils {
fun getDocumentFile(context: Context, anyPath: String, mediaUri: Uri): DocumentFileCompat? {
try {
if (requireAccessPermission(context, anyPath) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (requireAccessPermission(context, anyPath)) {
// need a document URI (not a media content URI) to open a `DocumentFile` output stream
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isMediaStoreContentUri(mediaUri)) {
// cleanest API to get it
@ -485,7 +481,7 @@ object StorageUtils {
fun createDirectoryDocIfAbsent(context: Context, dirPath: String): DocumentFileCompat? {
try {
val cleanDirPath = ensureTrailingSeparator(dirPath)
return if (requireAccessPermission(context, cleanDirPath) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return if (requireAccessPermission(context, cleanDirPath)) {
val grantedDir = getGrantedDirForPath(context, cleanDirPath) ?: return null
val rootTreeDocumentUri = convertDirPathToTreeDocumentUri(context, grantedDir) ?: return null
var parentFile: DocumentFileCompat? = DocumentFileCompat.fromTreeUri(context, rootTreeDocumentUri) ?: return null
@ -505,7 +501,8 @@ object StorageUtils {
parentFile
} else {
val directory = File(cleanDirPath)
if (!directory.exists() && !directory.mkdirs()) {
directory.mkdirs()
if (!directory.exists()) {
Log.e(LOG_TAG, "failed to create directories at path=$cleanDirPath")
return null
}
@ -567,7 +564,7 @@ object StorageUtils {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isMediaStoreContentUri(uri)) {
val path = uri.path
path ?: return uri
// from Android 11, accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
// from Android 11 (API 30), accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
if (path.startsWith(IMAGE_PATH_ROOT) || path.startsWith(VIDEO_PATH_ROOT)) {
// "Caller must hold ACCESS_MEDIA_LOCATION permission to access original"
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {
@ -642,7 +639,7 @@ object StorageUtils {
// strip user info, if any
// e.g. `content://0@media/...`
private fun stripMediaUriUserInfo(uri: Uri) = Uri.parse(uri.toString().replaceFirst("${uri.userInfo}@", ""))
private fun stripMediaUriUserInfo(uri: Uri) = uri.toString().replaceFirst("${uri.userInfo}@", "").toUri()
fun openInputStream(context: Context, uri: Uri): InputStream? {
val effectiveUri = getOriginalUri(context, uri)
@ -718,7 +715,8 @@ object StorageUtils {
fun createTempFile(context: Context, extension: String? = null): File {
val directory = getTempDirectory(context)
if (!directory.exists() && !directory.mkdirs()) {
directory.mkdirs()
if (!directory.exists()) {
throw IOException("failed to create directories at path=$directory")
}
val tempFile = File.createTempFile("aves", extension, directory)

View file

@ -1,16 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@color/ic_shortcut_background"
android:pathData="M0,24 A1,1 0 1,1 48,24 A1,1 0 1,1 0,24" />
<group
android:translateX="12"
android:translateY="12">
<path
android:fillColor="@color/ic_shortcut_foreground"
android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10C22,6.48 17.52,2 12,2zM19.46,9.12l-2.78,1.15c-0.51,-1.36 -1.58,-2.44 -2.95,-2.94l1.15,-2.78C16.98,5.35 18.65,7.02 19.46,9.12zM12,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3s3,1.34 3,3S13.66,15 12,15zM9.13,4.54l1.17,2.78c-1.38,0.5 -2.47,1.59 -2.98,2.97L4.54,9.13C5.35,7.02 7.02,5.35 9.13,4.54zM4.54,14.87l2.78,-1.15c0.51,1.38 1.59,2.46 2.97,2.96l-1.17,2.78C7.02,18.65 5.35,16.98 4.54,14.87zM14.88,19.46l-1.15,-2.78c1.37,-0.51 2.45,-1.59 2.95,-2.97l2.78,1.17C18.65,16.98 16.98,18.65 14.88,19.46z" />
</group>
</vector>

View file

@ -1,16 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:tint="@color/ic_shortcut_foreground"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="1.7226"
android:scaleY="1.7226"
android:translateX="33.3288"
android:translateY="33.3288">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10C22,6.48 17.52,2 12,2zM19.46,9.12l-2.78,1.15c-0.51,-1.36 -1.58,-2.44 -2.95,-2.94l1.15,-2.78C16.98,5.35 18.65,7.02 19.46,9.12zM12,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3s3,1.34 3,3S13.66,15 12,15zM9.13,4.54l1.17,2.78c-1.38,0.5 -2.47,1.59 -2.98,2.97L4.54,9.13C5.35,7.02 7.02,5.35 9.13,4.54zM4.54,14.87l2.78,-1.15c0.51,1.38 1.59,2.46 2.97,2.96l-1.17,2.78C7.02,18.65 5.35,16.98 4.54,14.87zM14.88,19.46l-1.15,-2.78c1.37,-0.51 2.45,-1.59 2.95,-2.97l2.78,1.17C18.65,16.98 16.98,18.65 14.88,19.46z" />
</group>
</vector>

View file

@ -6,32 +6,32 @@
<path
android:fillColor="#ef435a"
android:fillType="evenOdd"
android:pathData="m40.44,57.44 l8.74,8.74a1.54,1.54 0,0 1,0 2.18l-4.18,4.18a7.99,7.99 0,0 1,-11.3 0l-4.18,-4.18a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
android:strokeWidth="1.61863"
android:pathData="m41.03,57.12 l8.31,8.31a1.47,1.47 0,0 1,0 2.08l-3.97,3.97a7.6,7.6 0,0 1,-10.75 0l-3.97,-3.97a1.47,1.47 0,0 1,0 -2.08l8.31,-8.31a1.47,1.47 0,0 1,2.08 0z"
android:strokeWidth="1.53903"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#e0e0e0"
android:fillType="evenOdd"
android:pathData="m53.48,44.4 l8.74,8.74a1.54,1.54 0,0 1,-0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-8.74,-8.74a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
android:strokeWidth="1.61862"
android:pathData="m53.43,44.71 l8.31,8.31a1.46,1.46 0,0 1,-0 2.08l-8.31,8.31a1.46,1.46 0,0 1,-2.08 0l-8.31,-8.31a1.46,1.46 0,0 1,0 -2.08l8.31,-8.31a1.46,1.46 0,0 1,2.08 0z"
android:strokeWidth="1.53902"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#ffc11f"
android:fillType="evenOdd"
android:pathData="m55.61,40.09a1.54,1.54 0,0 0,0 2.18l8.74,8.74a1.54,1.54 0,0 0,2.18 0l8.74,-8.74a1.54,1.54 0,0 0,0 -2.18l-4.34,-4.34a7.77,7.77 0,0 0,-10.98 0zM64.23,39.98a1.71,1.71 0,0 1,2.41 0,1.71 1.71,0 0,1 0,2.41 1.71,1.71 0,0 1,-2.41 0,1.71 1.71,0 0,1 0,-2.41z"
android:strokeWidth="1.61862"
android:pathData="m55.45,40.62a1.47,1.47 0,0 0,0 2.08l8.31,8.31a1.47,1.47 0,0 0,2.08 0l8.31,-8.31a1.47,1.47 0,0 0,0 -2.08l-4.12,-4.12a7.39,7.39 0,0 0,-10.44 0zM63.65,40.51a1.62,1.62 0,0 1,2.29 0,1.62 1.62,0 0,1 0,2.29 1.62,1.62 0,0 1,-2.29 0,1.62 1.62,0 0,1 0,-2.29z"
android:strokeWidth="1.53902"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="@color/ic_launcher_flavour"
android:fillType="evenOdd"
android:pathData="m36.47,27.39 l12.76,12.76a1.54,1.54 0,0 1,0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-5.67,-5.67a12.06,12.06 0,0 1,0 -17.06l0.95,-0.95a2.04,2.04 0,0 1,2.88 0z"
android:strokeWidth="1.61863"
android:pathData="m37.26,28.54 l12.13,12.13a1.47,1.47 0,0 1,0 2.08l-8.31,8.31a1.47,1.47 0,0 1,-2.08 0l-5.39,-5.39a11.47,11.47 0,0 1,0 -16.22l0.9,-0.9a1.94,1.94 0,0 1,2.74 0z"
android:strokeWidth="1.53903"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />

View file

@ -6,32 +6,32 @@
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="m40.44,57.44 l8.74,8.74a1.54,1.54 0,0 1,0 2.18l-4.18,4.18a7.99,7.99 0,0 1,-11.3 0l-4.18,-4.18a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
android:strokeWidth="1.61863"
android:pathData="m41.03,57.12 l8.31,8.31a1.47,1.47 0,0 1,0 2.08l-3.97,3.97a7.6,7.6 0,0 1,-10.75 0l-3.97,-3.97a1.47,1.47 0,0 1,0 -2.08l8.3,-8.3a1.47,1.47 0,0 1,2.08 0z"
android:strokeWidth="1.53871"
android:strokeColor="#00000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="m53.48,44.4 l8.74,8.74a1.54,1.54 0,0 1,-0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-8.74,-8.74a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
android:strokeWidth="1.61862"
android:pathData="m53.43,44.72 l8.31,8.31a1.46,1.46 0,0 1,-0 2.08l-8.31,8.31a1.46,1.46 0,0 1,-2.08 0l-8.31,-8.31a1.46,1.46 0,0 1,0 -2.08L51.35,44.72a1.46,1.46 0,0 1,2.08 0z"
android:strokeWidth="1.5387"
android:strokeColor="#00000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="m55.61,40.09a1.54,1.54 0,0 0,0 2.18l8.74,8.74a1.54,1.54 0,0 0,2.18 0l8.74,-8.74a1.54,1.54 0,0 0,0 -2.18l-4.34,-4.34a7.77,7.77 0,0 0,-10.98 0zM64.23,39.98a1.71,1.71 0,0 1,2.41 0,1.71 1.71,0 0,1 0,2.41 1.71,1.71 0,0 1,-2.41 0,1.71 1.71,0 0,1 0,-2.41z"
android:strokeWidth="1.61862"
android:pathData="m55.45,40.62a1.47,1.47 0,0 0,0 2.08l8.31,8.3a1.47,1.47 0,0 0,2.08 0l8.3,-8.3a1.47,1.47 0,0 0,0 -2.08l-4.12,-4.12a7.38,7.38 0,0 0,-10.44 0zM63.65,40.51a1.62,1.62 0,0 1,2.29 0,1.62 1.62,0 0,1 0,2.29 1.62,1.62 0,0 1,-2.29 0,1.62 1.62,0 0,1 0,-2.29z"
android:strokeWidth="1.5387"
android:strokeColor="#00000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="m36.47,27.39 l12.76,12.76a1.54,1.54 0,0 1,0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-5.67,-5.67a12.06,12.06 0,0 1,0 -17.06l0.95,-0.95a2.04,2.04 0,0 1,2.88 0z"
android:strokeWidth="1.61863"
android:pathData="m37.26,28.54 l12.13,12.13a1.47,1.47 0,0 1,0 2.08l-8.31,8.3a1.47,1.47 0,0 1,-2.08 0L33.62,45.67a11.47,11.47 0,0 1,0 -16.22l0.9,-0.9a1.94,1.94 0,0 1,2.74 0z"
android:strokeWidth="1.53871"
android:strokeColor="#00000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@color/ic_shortcut_background"
android:pathData="M0,24 A1,1 0 1,1 48,24 A1,1 0 1,1 0,24" />
<group
android:translateX="12"
android:translateY="12">
<path
android:fillColor="@color/ic_shortcut_foreground"
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z" />
</group>
</vector>

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:tint="@color/ic_shortcut_foreground"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="1.7226"
android:scaleY="1.7226"
android:translateX="33.3288"
android:translateY="33.3288">
<path
android:fillColor="@android:color/white"
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z" />
</group>
</vector>

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