Compare commits

...

358 Commits
13.6 ... main

Author SHA1 Message Date
Alex Baker 97a3f074d0 Update alarms after completion transaction 1 day ago
Alex Baker 86ecd3cf81 Synchronize alarms before saving 1 day ago
Alex Baker 07a2eda5ea Cancel notifications in TaskCompleter 1 day ago
renovate[bot] 09ffbdd036 fix(deps): update dependency com.google.firebase:firebase-crashlytics-gradle to v3 1 day ago
renovate[bot] 60f22146ca fix(deps): update dependency androidx.fragment:fragment-ktx to v1.7.1 1 day ago
renovate[bot] c11225abaf fix(deps): update kotlin 1 day ago
dependabot[bot] 133ea493e3 Bump rexml from 3.2.6 to 3.2.8
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.6 to 3.2.8.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.6...v3.2.8)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
1 day ago
Alex Baker 0ba901be69 Remove livedata from data module 5 days ago
Alex Baker ebe5e5c009 Replace gson with kotlin serialization 6 days ago
Alex Baker d556863fda Use kotlin serialization for backups 6 days ago
Alex Baker 55adbc2025 Reorganized data module 6 days ago
Alex Baker 06c4255886 Remove androidx.core from data module 6 days ago
renovate[bot] 4734a99bae fix(deps): update mockito monorepo to v5.12.0 7 days ago
Alex Baker a6a8cac8e4 Update dependencies 1 week ago
Alex Baker c3fc9a57cc Replace now with currentTimeMillis 1 week ago
Alex Baker 6e14d07d0c Move Room to data module 1 week ago
Alex Baker 6118121698 Moving some code out of TimerPlugin 1 week ago
Alex Baker 6bf3bd4d08 Update version and changelog 1 week ago
Alex Baker 065be79355 Update notification work logic 1 week ago
Alex Baker f8f8ba3c51 Don't adjust random reminder time 1 week ago
Alex Baker 89465f36b3 Update version and changelog 1 week ago
Alex Baker 1380a34ffa Fix alarm test 1 week ago
Alex Baker 10af5280a3 Fix random reminders 1 week ago
Alex Baker 8c0f7b952d ForegroundInfo for expedited work on Android 11- 1 week ago
Alex Baker 65362b203f Update version and changelog 1 week ago
Alex Baker 3327f97a17 Revert change to not delete evicted notifications 1 week ago
Alex Baker c9fc02a42e Enable room kotlin codegen 1 week ago
renovate[bot] 93670bb9e4 fix(deps): update dependency com.google.firebase:firebase-bom to v33 1 week ago
Alex Baker 1fc6a50d0b Update version and changelog 1 week ago
Alex Baker e1ef924909 Revert "Load initial data in task edit view model"
This reverts commit b2efb42d55.
1 week ago
Alex Baker 686cb5d346 Add empty filter 1 week ago
renovate[bot] ebec25c4cb fix(deps): update dependency com.google.android.material:material to v1.12.0 1 week ago
renovate[bot] c140f7e673
fix(deps): update dependency com.squareup.leakcanary:leakcanary-android to v2.14 2 weeks ago
renovate[bot] efbcf11a4a fix(deps): update dependency com.google.apis:google-api-services-drive to v3-rev20240327-2.0.0 2 weeks ago
renovate[bot] 6adee85a37 fix(deps): update dependency com.google.apis:google-api-services-tasks to v1-rev20240423-2.0.0 2 weeks ago
Alex Baker 5c8643110b Update back press and intent handling 2 weeks ago
Alex Baker abd13aeb75 Exclude META-INF/INDEX.LIST 2 weeks ago
Alex Baker c210fe1893 Fix finishing recurrence 2 weeks ago
Alex Baker 26aa916c20 Fix widget crash 2 weeks ago
renovate[bot] 1eff2d1cd5 fix(deps): update dependency androidx.fragment:fragment-ktx to v1.7.0 2 weeks ago
islam2hamy c90e683ea3
Translated using Weblate (Arabic)
Currently translated at 94.1% (626 of 665 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/ar/
2 weeks ago
Alex Baker 3cd0295b71 Refactor notification scheduling
* Remove foreground service
* Use expedited work to trigger notifications
* Remove miscellaneous notification channel
2 weeks ago
Alex Baker 95c351e9fd Remove midnight refresh worker 2 weeks ago
renovate[bot] 4ddb7816f1 fix(deps): update dependency androidx.compose:compose-bom to v2024.05.00 2 weeks ago
renovate[bot] 91c30f7bbf chore(deps): update dependency gradle to v8.7 2 weeks ago
renovate[bot] 3f4398b6e0 chore(deps): update dependency fastlane to v2.220.0 2 weeks ago
renovate[bot] c822e989a3 fix(deps): update dependency androidx.compose.compiler:compiler to v1.5.13 2 weeks ago
renovate[bot] da146723e5 chore(deps): update dependency ruby to v3.3.1 2 weeks ago
109247019824 931626c84a Translated using Weblate (Bulgarian)
Currently translated at 100.0% (665 of 665 strings)

Co-authored-by: 109247019824 <stoyan@gmx.com>
Translate-URL: https://hosted.weblate.org/projects/tasks/android/bg/
Translation: Tasks.org/Android
2 weeks ago
Alex Baker c534632c52 Pass uuid to TaskAdapter.onCompletedTask 2 weeks ago
Alex Baker c1347a7455 Update version and changelog 2 weeks ago
renovate[bot] 9544909a58 Update dependency androidx.activity:activity-compose to v1.9.0 2 weeks ago
Yurt Page 5c10dce2b9 fastlane: i18n ru
Signed-off-by: Yurt Page <yurtpage@gmail.com>
2 weeks ago
Alex Baker 584d4a5cbb Move after update work inside transaction 2 weeks ago
Alex Baker 7c68a7fa59 AGP 8.4.0 2 weeks ago
purushottamyadavbattula 215cc838ef Sending local broadcast refresh event for refreshing nav drawer menu to communicate about update events 2 weeks ago
Alex Baker d60472d1bc Remove RefreshScheduler 3 weeks ago
Alex Baker f84a37a60a Revert "Replace refresh work with coroutines"
Widgets 😢
3 weeks ago
Alex Baker 7fb85b6da1 Replace refresh work with coroutines 3 weeks ago
Alex Baker dc90e583e4 Fix hiding empty items in drawer 3 weeks ago
Don Zouras 0eac5f61eb Translated using Weblate (Esperanto)
Currently translated at 100.0% (665 of 665 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 weeks ago
Milo Ivir c686ce883d Translated using Weblate (Croatian)
Currently translated at 100.0% (665 of 665 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/hr/
4 weeks ago
大王叫我来巡山 ab25398cd0 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (665 of 665 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/zh_Hans/
4 weeks ago
renovate[bot] 3b1c133d22 Update kotlin 4 weeks ago
renovate[bot] 3bfd0ab4f8 Update dependency com.google.firebase:firebase-bom to v32.8.1 4 weeks ago
Liz de Sartiges ffc0113d7f Initial support for z flip 5 cover screen
see : https://developer.samsung.com/galaxy-z/flex_window.html
4 weeks ago
Don Zouras 9de9718ad5 Translated using Weblate (Esperanto)
Currently translated at 100.0% (665 of 665 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 weeks ago
Oğuz Ersen a7d2c9c406 Translated using Weblate (Turkish)
Currently translated at 100.0% (665 of 665 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/tr/
4 weeks ago
gallegonovato b3006b9ac2 Translated using Weblate (Spanish)
Currently translated at 100.0% (665 of 665 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/es/
4 weeks ago
Don Zouras de3ef1f9c9 Translated using Weblate (Esperanto)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 weeks ago
Alex Baker ce9e722a3f Delete more unused tag picker code 4 weeks ago
Alex Baker 4b892a0eb1 Rename to TagPickerActivity
Delete some more unused code
4 weeks ago
Hady e6e275834a
Tag picker compose (#2849)
TagPickerActivity refactoring to Compose

1. state of the SearchBar moved to the viewModel
2. viewModel used as parameter to @Composables instead of number of separate ones
3. Import of TagPickerActivity is replaced by TagPickerActivityCompose through the project
4 weeks ago
Alex Baker 782f4d6d7c Fix swipe to snooze time 4 weeks ago
elmuffo a1da71d3e1
Swipe to snooze (#2839) 4 weeks ago
Alex Baker c793a300cc Add preference summary 4 weeks ago
Ilya Bizyaev bf84bf9e82 [Feature] Add an option to allow adding tasks without unlock
I often find myself picking up the phone just to write down a task, so
I've added a notification drawer quick setting to speed things up.
However, when I use this button from the lock screen, I have to unlock
my device first, which is annoying. I would like to be able to add (not
view) tasks without the need to unlock my phone.

This PR adds such an optional feature for devices running Android 8.1+.
Note that I am not an Android developer, so the implementation is
probably not perfect. However, from my testing on an emulator, this
code seems to do just what I want.
4 weeks ago
SC 363b29babb
Translated using Weblate (Portuguese)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt/
1 month ago
min7-i c1ff953f5c Translated using Weblate (German)
Currently translated at 99.3% (651 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/de/
1 month ago
Alex Baker 63482e5db9 AGP 8.3.2 1 month ago
Emin Tufan Çetin 2f7dc0c7f1
Translated using Weblate (Turkish)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/tr/
1 month ago
Lionel HANNEQUIN d672507fae
Translated using Weblate (French)
Currently translated at 99.8% (654 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fr/
1 month ago
Jonatan Nyberg ce2a3c8a3f
Translated using Weblate (Swedish)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/sv/
1 month ago
sorifukobexomajepasiricupuva33 9cd114d68b
Translated using Weblate (German)
Currently translated at 99.2% (650 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/de/
2 months ago
Patrick V. Leguizamon 0e663f0e08
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt_BR/
2 months ago
Mayhm 1d1efd008d
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt_BR/
2 months ago
Alex Baker 26ab3d5866 Exclude past snooze times from Snooze Filter
This should exclude tasks that were completed before their snooze time
lapsed
2 months ago
Mayhm 9a4fcbbd39
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt_BR/
2 months ago
Alex Baker 72bfda9224 Fix subtasks row for new tasks 2 months ago
Alex Baker 1067de4183 Emit SectionedDataSource from TaskListViewModel 2 months ago
Alex Baker d686b8c7e0 Add TasksMenu composable 2 months ago
Alex Baker b2efb42d55 Load initial data in task edit view model 2 months ago
Fabio Parri 3448808c94 Translated using Weblate (Portuguese)
Currently translated at 99.3% (651 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt/
2 months ago
Alex Baker 06a9626052 Update version and changelog 2 months ago
Alex Baker e92ab7f7e1 Update to latest ModalBottomSheet 2 months ago
Alex Baker 4ff7b18c0f Fix cloning google tasks 2 months ago
Alex Baker 91887f6b17 Fix backup import dropping some tasks 2 months ago
Alex Baker cf30b56098 Update version and changelog 2 months ago
Alex Baker 9bcadaab5a Fix astrid manual ordering crash in widget 2 months ago
Alex Baker be766074b0 Fix activity crash 2 months ago
Ihor Hordiichuk 64a42a3f61 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/uk/
2 months ago
Mayhm 7b65ba6f06 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.6% (646 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt_BR/
2 months ago
109247019824 ac2b270e9e Translated using Weblate (Bulgarian)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/bg/
2 months ago
Alex Baker db2ea0a039 Fix import crash on missing remoteId 2 months ago
renovate[bot] 08b78fe9f4 Update dependency androidx.compose:compose-bom to v2024.03.00 2 months ago
Alex Baker 1a1301ae3e Update version and changelog 2 months ago
Milo Ivir d00061aa7f Translated using Weblate (Croatian)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/hr/
2 months ago
大王叫我来巡山 45add6ab32 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/zh_Hans/
2 months ago
Pierfrancesco Passerini af43737c4e Translated using Weblate (Italian)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/it/
2 months ago
macpac59 dd40e59b17 Translated using Weblate (German)
Currently translated at 98.9% (648 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/de/
2 months ago
gallegonovato 13f3248a01 Translated using Weblate (Spanish)
Currently translated at 100.0% (655 of 655 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/es/
2 months ago
renovate[bot] f6972e3e30 Update dependency com.android.tools.build:gradle to v8.3.1 2 months ago
Alex Baker 83cf48a836 Don't pass filter to remoteviews service
This was working on emulators but crashing in the wild
2 months ago
Alex Baker b7b4747a04 Update translation credits
Was in a rush to get a bug fix out!
2 months ago
Alex Baker 6bec2ceef0 Update version and changelog 2 months ago
Milo Ivir d1e60d6512 Translated using Weblate (Croatian)
Currently translated at 100.0% (654 of 654 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/hr/
2 months ago
bittin1ddc447d824349b2 2b85089d3a Translated using Weblate (Swedish)
Currently translated at 100.0% (654 of 654 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/sv/
2 months ago
ferranpujolcamins 2a0ef9feb6 Translated using Weblate (Catalan)
Currently translated at 34.7% (227 of 654 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/ca/
2 months ago
109247019824 33adbbd884 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (654 of 654 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/bg/
2 months ago
Alex Baker c25eb2e0c5 Fix crash on earlier Android versions 2 months ago
Alex Baker 14026356eb Fix widget arrow color 2 months ago
Alex Baker b328651dd4
Run tests on generic flavor (#2808) 2 months ago
Alex Baker a0e9bfabeb Update version and changelog 2 months ago
大王叫我来巡山 a1ad421b33 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/zh_Hans/
2 months ago
Mayhm 3488a08af1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.4% (648 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt_BR/
2 months ago
gallegonovato b71d1af516 Translated using Weblate (Spanish)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/es/
2 months ago
Alex Baker 041dce8617 Add dynamic widget theme 2 months ago
Alex Baker 3d92ca78dd Remove unused attrs 2 months ago
Alex Baker a32fce2d8b Remove widget_title_* layouts 2 months ago
Alex Baker 4fb3cda173 Fix loading selected filter on startup 2 months ago
Alex Baker f33cc896dd Refactor widget 2 months ago
Alex Baker 4d1d6a06a8 Fix repeat until crash 2 months ago
Alex Baker 2202516688 Update isOverdue logic 2 months ago
Alex Baker d4a5008ecb Update CI 2 months ago
Alex Baker 08189e10f1 Don't use gradle managed devices in CI 2 months ago
Anonymous d3e4c066d8 Translated using Weblate (Sinhala)
Currently translated at 92.0% (606 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/si/
2 months ago
Anonymous bbc5ae4d6d Translated using Weblate (Tamil)
Currently translated at 68.2% (449 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/ta/
2 months ago
Anonymous c6cc00cf07 Translated using Weblate (Thai)
Currently translated at 89.5% (589 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/th/
2 months ago
Anonymous 22e8720021 Translated using Weblate (Hebrew)
Currently translated at 89.5% (589 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/he/
2 months ago
Anonymous a3ce98f0ea Translated using Weblate (Danish)
Currently translated at 95.8% (631 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/da/
2 months ago
macpac59 258f607d52 Translated using Weblate (German)
Currently translated at 99.6% (656 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/de/
2 months ago
ngocanhtve 927acae7e4 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/vi/
2 months ago
Odweta 49ad9bafe3 Translated using Weblate (Czech)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/cs/
2 months ago
Alex Baker 6df616d9ce Use gradle managed devices 2 months ago
Alex Baker 157668e35a Fix tests 2 months ago
Aslam Karachiwala efdf343869 For English (en), replaced "until" with "ends on" in 'repeats_*' keys. 2 months ago
renovate[bot] 5606df17c5 Update flipper to v0.250.0 2 months ago
renovate[bot] fc3b4971f4 Update flipper to v0.249.0 2 months ago
renovate[bot] 6a1699bb33 Update dependency androidx.compose:compose-bom to v2024.02.02 2 months ago
renovate[bot] e49303d5ca Update dependency com.google.firebase:firebase-bom to v32.7.4 2 months ago
renovate[bot] 4b55569b51 Update mockito monorepo to v5.11.0 3 months ago
renovate[bot] 2d7145cde3 Update plugin com.google.devtools.ksp to v1.9.22-1.0.18 3 months ago
renovate[bot] f2ab8bed95 Update dependency com.google.firebase:firebase-bom to v32.7.3 3 months ago
renovate[bot] a5bc4cf536 Update dependency com.android.tools.build:gradle to v8.3.0 3 months ago
renovate[bot] 1b35372b3a Update dependency com.google.apis:google-api-services-tasks to v1-rev20240225-2.0.0 3 months ago
Alex Baker c0fd4bf66a Convert LocalBroadcastManager to Kotlin 3 months ago
renovate[bot] 5d366f0d61 Update dependency io.coil-kt:coil-gif to v2.6.0 3 months ago
renovate[bot] d0635ac6f3 Update hilt to v1.2.0 3 months ago
renovate[bot] 8d4cf4daa5 Update dependency androidx.compose.compiler:compiler to v1.5.10 3 months ago
renovate[bot] d1e439e70e Update dependency androidx.compose:compose-bom to v2024.02.01 3 months ago
renovate[bot] 4d4c3e5193 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-test to v1.8.0 3 months ago
Alex Baker 20f87061fd Convert WidgetPreferences to Kotlin 3 months ago
renovate[bot] c03e3747c6 Update dependency com.google.gms:google-services to v4.4.1 3 months ago
renovate[bot] 925b1b9124 Update dependency com.google.firebase:firebase-bom to v32.7.2 3 months ago
Alex Baker 43db712f64 Update version and changelog 3 months ago
Alex Baker 9d33a73ee6 Fix drawer highlighting 3 months ago
renovate[bot] 391c600ce2 Update flipper to v0.247.0 3 months ago
renovate[bot] ee4ae94817 Update dependency androidx.compose:compose-bom to v2024.02.00 3 months ago
renovate[bot] 70b4be1447
Update dependency androidx.compose.compiler:compiler to v1.5.9 3 months ago
Don Zouras bc54d92789 Translated using Weblate (Esperanto)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
3 months ago
Сергій 2f34724b95 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/uk/
3 months ago
Alex Baker 940fdc28dd Strip markdown from repeat snackbar 3 months ago
Alex Baker 68542fce38 Fix repeat task toast displaying old due date 3 months ago
renovate[bot] 7ba2977100 Update dependency gradle to v8.6 4 months ago
Don Zouras cb242539f0 Translated using Weblate (Esperanto)
Currently translated at 98.4% (648 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
bittin1ddc447d824349b2 304841f2c3 Translated using Weblate (Swedish)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/sv/
4 months ago
Don Zouras 819ea797e6 Translated using Weblate (Esperanto)
Currently translated at 98.4% (648 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
abc0922001 2dbea57262 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/zh_Hant/
4 months ago
Don Zouras 516a916fd5 Translated using Weblate (Esperanto)
Currently translated at 96.9% (638 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
109247019824 3bd52efc80 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/bg/
4 months ago
renovate[bot] 64af955ea7 Update flipper to v0.246.0 4 months ago
Milo Ivir 4cc5ec9639 Translated using Weblate (Croatian)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/hr/
4 months ago
大王叫我来巡山 0d9292e53a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/zh_Hans/
4 months ago
Oğuz Ersen 732ccf1913 Translated using Weblate (Turkish)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/tr/
4 months ago
gallegonovato a2852bdbbf Translated using Weblate (Spanish)
Currently translated at 100.0% (658 of 658 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/es/
4 months ago
Don Zouras 68790ad401 Translated using Weblate (Esperanto)
Currently translated at 91.7% (603 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
Alex Baker e9afacb595 Include hidden subtasks when clearing completed 4 months ago
Alex Baker cf182aceab Display number of tasks to be cleared 4 months ago
Alex Baker db889d233a Remove AfterSaveWork 4 months ago
Alex Baker 457b89c092 Remove cleanup work 4 months ago
Don Zouras ad53af1b6a Translated using Weblate (Esperanto)
Currently translated at 87.0% (572 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
renovate[bot] 2c32b08c97 Update dependency androidx.compose:compose-bom to v2024 4 months ago
renovate[bot] a2fcf57c9e Update mockito monorepo to v5.10.0 4 months ago
renovate[bot] 59a61325f2 Update dependency org.osmdroid:osmdroid-android to v6.1.18 4 months ago
vulewuxe86 38a6064677 Reverted code
Reverted Code involving the action bar search function
4 months ago
renovate[bot] 67daccf3e8 Update lifecycle to v2.7.0 4 months ago
renovate[bot] dfe829d2a1 Update dependency com.google.android.gms:play-services-location to v21.1.0 4 months ago
renovate[bot] 23c64f4d28 Update dependency com.google.apis:google-api-services-drive to v3-rev20240123-2.0.0 4 months ago
renovate[bot] e4b8f694f3 Update dependency com.google.firebase:firebase-bom to v32.7.1 4 months ago
renovate[bot] e667c80731 Update kotlin 4 months ago
renovate[bot] 909b077e25 Update dependency com.android.tools.build:gradle to v8.2.2 4 months ago
Don Zouras e6fab9ad45 Translated using Weblate (Esperanto)
Currently translated at 82.1% (540 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
raulmagdalena 9474f5b7af Translated using Weblate (Catalan)
Currently translated at 34.5% (227 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/ca/
4 months ago
Don Zouras 1ee051d768 Translated using Weblate (Esperanto)
Currently translated at 75.1% (494 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
Don Zouras f42edaa158 Translated using Weblate (Esperanto)
Currently translated at 73.8% (485 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/eo/
4 months ago
RayBB b97eade59c fix typos 4 months ago
renovate[bot] 41aa1ca65f Update flipper to v0.245.0 4 months ago
renovate[bot] 3e9a13ea14 Update mockito monorepo to v5.9.0 4 months ago
renovate[bot] d966e8a12b Update dependency fastlane to v2.219.0 4 months ago
renovate[bot] 8ba4e64994 Update dependency com.android.tools.build:gradle to v8.2.1 4 months ago
109247019824 ee792f1ceb Translated using Weblate (Bulgarian)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/bg/
4 months ago
renovate[bot] caa09163a1 Update dependency ruby to v3.3.0 4 months ago
renovate[bot] d270abf5b3 Update dependency com.facebook.soloader:soloader to v0.11.0 4 months ago
renovate[bot] 1ef530abad Update dependency com.squareup.leakcanary:leakcanary-android to v2.13 4 months ago
renovate[bot] df26a6dbb9 Update flipper to v0.244.0 4 months ago
renovate[bot] 1882c3b7e0 Update dependency androidx.activity:activity-compose to v1.8.2 5 months ago
renovate[bot] cb53a0ca9f Update dependency com.google.android.material:material to v1.11.0 5 months ago
renovate[bot] b2fdef1ae7 Update dagger.hilt to v2.50 5 months ago
renovate[bot] defb16ce95 Update kotlin 5 months ago
renovate[bot] 823f99b28a Update flipper to v0.243.0 5 months ago
renovate[bot] 6df872b1a1 Update actions/upload-artifact action to v4 5 months ago
renovate[bot] 133b960583 Update flipper to v0.242.0 5 months ago
renovate[bot] 2e6753faec Update dependency com.google.apis:google-api-services-drive to v3-rev20231128-2.0.0 5 months ago
renovate[bot] cb07c2c267 Update dependency com.google.firebase:firebase-bom to v32.7.0 5 months ago
renovate[bot] 23757ab320 Update kotlin 5 months ago
Alex Baker 1b6ce0e48e Ignore empty rrule 5 months ago
Kakaeo 5af012068f Translated using Weblate (Persian)
Currently translated at 29.9% (197 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fa/
5 months ago
Alex Baker 6c9ffa57d7 Fix reparenting task to another list 6 months ago
Alex Baker 52c54b1eac Fix excessive querying (again) 6 months ago
Alex Baker c8d81b44b6 Fix excessive querying 6 months ago
renovate[bot] ef27a50e42 Update mockito monorepo to v5.8.0 6 months ago
Alex Baker bde1356e7f Add task to MainActivityViewModel state 6 months ago
Alex Baker 6c031925ba Replace some setter usage with constructors 6 months ago
Alex Baker 8058414137 Use release build for compose metrics 6 months ago
Alex Baker 3e37ea50f0 Update compose-compiler to v1.5.5 6 months ago
renovate[bot] 62f5a9c492 Update actions/setup-java action to v4 6 months ago
renovate[bot] a84fd65722 Update dependency androidx.room:room-ktx to v2.6.1 6 months ago
renovate[bot] 517b2d8f1b Update dependency gradle to v8.5 6 months ago
renovate[bot] 90942bf0be Update dependency com.google.dagger:hilt-android to v2.49 6 months ago
Alex Baker 83c3d1c4ba AGP 8.2.0 6 months ago
Software In Interlingua 6362ece569 Translated using Weblate (Interlingua)
Currently translated at 5.6% (37 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/ia/
6 months ago
renovate[bot] 8df85041b8 Update flipper to v0.240.0 6 months ago
ngocanhtve 6d85af4c34 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/vi/
6 months ago
Olli 63f001dd72 Translated using Weblate (Finnish)
Currently translated at 99.0% (651 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fi/
6 months ago
renovate[bot] de49a50944 Update dependency fastlane to v2.217.0 6 months ago
renovate[bot] df20d2f593 Update dependency com.android.billingclient:billing-ktx to v6.1.0 6 months ago
renovate[bot] fd16772236 Update dependency androidx.activity:activity-compose to v1.8.1 6 months ago
renovate[bot] b77caac255 Update dependency com.google.firebase:firebase-bom to v32.6.0 6 months ago
renovate[bot] ad058ed09b Update kotlin 6 months ago
Alex Baker 8312113d7b Merge branch '13.6.3' 6 months ago
Alex Baker ee21cc660e Update version and changelog 6 months ago
Alex Baker 5edc481ffe Fix etag check for DecSync 6 months ago
Alex Baker d0360a4862 Revert "Update timestamp on edits"
This reverts commit b477623524.
6 months ago
Alex Baker ac35002408 Revert "Update modification timestamp logic (#2585)"
This reverts commit 775289b058.
6 months ago
Subham Jena 582ebad0f0 Translated using Weblate (Odia)
Currently translated at 20.7% (136 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/or/
6 months ago
Shaban Mamedov 684c47184a Added translation using Weblate (Azerbaijani) 6 months ago
ngocanhtve ac7a519e4e Translated using Weblate (Vietnamese)
Currently translated at 99.6% (655 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/vi/
6 months ago
renovate[bot] 5c2b41af9d Update flipper to v0.238.0 6 months ago
renovate[bot] 13986cf380 Update flipper to v0.237.0 6 months ago
CennoxX c4f0b404e9 Translated using Weblate (German)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/de/
6 months ago
Alex Baker 145b5afbc6 AGP 8.2.0-rc03 6 months ago
elig0n 0b87a206fe Translated using Weblate (Hebrew)
Currently translated at 90.2% (593 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/he/
6 months ago
mm4c d0e70ceea8 Translated using Weblate (Dutch)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/nl/
6 months ago
J. Lavoie bf3546a878 Translated using Weblate (French)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fr/
6 months ago
Alex Baker 8895acbf6b Prevent flashing empty inbox when switching lists 6 months ago
Alex Baker a52b1200f5 Fix menu expansion 6 months ago
renovate[bot] 23964e807a
Update flipper to v0.236.0 (#2617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
Alex Baker 287b106dd4 Highlight selected list in drawer 7 months ago
renovate[bot] 33bab626e0
Update mockito monorepo to v5.7.0 (#2613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
renovate[bot] a980cd75cc Update hilt to v1.1.0 7 months ago
renovate[bot] 7eac4ac223 Update dependency androidx.fragment:fragment-ktx to v1.6.2 7 months ago
renovate[bot] 82cb2f7d3f
Update dependency com.android.tools:desugar_jdk_libs to v2.0.4 7 months ago
renovate[bot] da2646597c
Update flipper to v0.235.0 (#2608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
renovate[bot] 495855133c Update dependency com.google.firebase:firebase-bom to v32.5.0 7 months ago
renovate[bot] 242cb61662 Update coil to v2.5.0 7 months ago
renovate[bot] ab8886f3dc Update flipper to v0.234.0 7 months ago
Alex Baker e48e92d2e6 Fix dates 🤦 7 months ago
Alex Baker 5f22f5cd38 AGP 8.2.0-rc02 7 months ago
Alex Baker 8a47cc2934 Don't set local only notifications on Android 14+ 7 months ago
Alex Baker 0d94729d37 Merge branch '13.6.2' 7 months ago
Alex Baker 14599eb3c0 Update version and changelog 7 months ago
Alex Baker b477623524 Update timestamp on edits
Fix bugs introduced by 775289b05
7 months ago
Alex Baker c8bfb67b50 Allow multi-select for gallery picker 7 months ago
Alex Baker 0a36e58525 Allow multi-select in storage picker 7 months ago
Alex Baker 94a719cb66 Improve menu dismissal
Copy M3 ModalBottomSheet to add 'skipPartiallyCollapsed' support 😕
7 months ago
Alex Baker b5748aa8e6 New drawer 7 months ago
Alex Baker 7fd5647cb8 Exclude hidden and completed from snoozed filter 7 months ago
Alex Baker 2545832d67 Update version and changelog 7 months ago
Alex Baker 738bf435db Fix some back handlers 7 months ago
renovate[bot] ab02323f29
Update flipper to v0.233.0 (#2599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
Alex Baker d73a9d2795 Update version and changelog 7 months ago
Alex Baker ebe67354b6 Add tests for recurrence without intervals 7 months ago
Alex Baker 58edc6b4d8 Fix basic hourly and weekly recurrence 7 months ago
Weblate (bot) 78b2cdac06
Translations update from Hosted Weblate (#2596)
* Translated using Weblate (French)

Currently translated at 99.5% (654 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fr/

* Translated using Weblate (French)

Currently translated at 99.5% (654 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fr/

---------

Co-authored-by: Bruno Duyé <brunetton@gmail.com>
Co-authored-by: Lionel HANNEQUIN <Lionel-HANNEQUIN@users.noreply.hosted.weblate.org>
7 months ago
renovate[bot] c3d7db0087
Update flipper to v0.232.0 (#2597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
renovate[bot] d7b1770b85
Update flipper to v0.231.0 (#2591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
Weblate (bot) bebb3165a5
Translations update from Hosted Weblate (#2589)
Translated using Weblate (French)

Currently translated at 99.5% (654 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fr/

Co-authored-by: Lionel HANNEQUIN <Lionel-Fr-56@users.noreply.hosted.weblate.org>
7 months ago
Alex Baker ad1198aace Don't require network for OpenTasks sync 7 months ago
Alex Baker 7ae77a81e1 Don't need background sync for OpenTasks
Background sync handled by sync apps
7 months ago
Weblate (bot) 3e79dd5190
Translations update from Hosted Weblate (#2587)
Translated using Weblate (French)

Currently translated at 99.6% (655 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fr/

Co-authored-by: Lionel HANNEQUIN <Lionel-Fr-56@users.noreply.hosted.weblate.org>
7 months ago
renovate[bot] 9d57a849bf
Update flipper to v0.230.0 (#2586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
Alex Baker 82103eb477 Make chips skippable 7 months ago
renovate[bot] 11fa9a2bbd Update dependency com.google.firebase:firebase-bom to v32.4.0 7 months ago
renovate[bot] b65831120f Update room to v2.6.0 7 months ago
renovate[bot] f26a90a4f9 Update hilt to v1.1.0-rc01 7 months ago
renovate[bot] dd3aa20485 Update dependency androidx.recyclerview:recyclerview to v1.3.2 7 months ago
renovate[bot] 8c84e1af50 Update dependency com.google.android.gms:play-services-maps to v18.2.0 7 months ago
renovate[bot] dc1eac23b9 Update dependency androidx.compose:compose-bom to v2023.10.01 7 months ago
renovate[bot] 5883952883
Update flipper to v0.229.0 (#2579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 months ago
Alex Baker 775289b058
Update modification timestamp logic (#2585) 7 months ago
Alex Baker ee500c24b1
Convert filters to data classes (#2569) 7 months ago
Igor Sorocean 68fd36b14d Translated using Weblate (Romanian)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/ro/
7 months ago
renovate[bot] b8f265fa36 Update okhttp to v4.12.0 7 months ago
Alex Baker cf4e6c1273 Update AGP to 8.2.0-rc01 7 months ago
renovate[bot] 0dcc577497 Update flipper to v0.227.0 7 months ago
kmj-99 b525e8cab3 Refactor: Change deprecated code in Fragment onAttach 7 months ago
renovate[bot] db0ad280eb Update dependency com.google.auth:google-auth-library-oauth2-http to v1.20.0 7 months ago
Alex Baker 5092f80dcc Update billing to v6.0.1 7 months ago
Kazushi Hayama 6bc42363dd Translated using Weblate (Japanese)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/ja/
7 months ago
Michal Šmahel 115461c7b0 Translated using Weblate (Czech)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/cs/
7 months ago
renovate[bot] 369c508890 Update flipper to v0.226.0 7 months ago
bittin1ddc447d824349b2 a432cc33cc Translated using Weblate (Swedish)
Currently translated at 100.0% (657 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/sv/
7 months ago
Alex Baker e5b51150cb Start sync if enqueued when app is backgrounded 7 months ago
renovate[bot] d43639556e Update dependency com.google.android.material:material to v1.10.0 7 months ago
Alex Baker ef2dd8f202 Merge branch '13.6' 7 months ago
renovate[bot] 6a73f6745c Update flipper to v0.225.0 7 months ago
renovate[bot] 5185c14e44 Update mockito monorepo to v5.6.0 7 months ago
Olli aa7ff0fa16 Translated using Weblate (Finnish)
Currently translated at 99.0% (651 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/fi/
7 months ago
renovate[bot] 12b979d363 Update flipper to v0.224.0 7 months ago
Alex Baker 082f741983 Convert Filter to data class 8 months ago
Alex Baker 0bdd83988f Fix lint errors 8 months ago
Alex Baker 60784c10b5 Update filter fields 8 months ago
Alex Baker da8467ac56 Remove constructor 8 months ago
renovate[bot] 434d067822 Update dependency gradle to v8.4 8 months ago
renovate[bot] 04af310285 Update hilt to v1.1.0-beta01 8 months ago
renovate[bot] 5555771f45 Update dependency com.google.dagger:hilt-android-testing to v2.48.1 8 months ago
renovate[bot] 35b60df0ff Update dependency androidx.activity:activity-compose to v1.8.0 8 months ago
renovate[bot] fef19b4995 Update dependency androidx.compose:compose-bom to v2023.10.00 8 months ago
Alex Baker 4c25b81a4d Move Parcelable 8 months ago
Alex Baker 0f37f4859e Update compose reports 8 months ago
Alex Baker ee3d3fa4f5 Convert FilterListItem to interface 8 months ago
Alex Baker a32d35720a Refresh after changing sort mode 8 months ago
Alex Baker bf6fe02fe3 Convert FilterListItems to Kotlin 8 months ago
Alex Baker 6664defc16 Minor refactoring 8 months ago
renovate[bot] b318b930a5 Update flipper to v0.223.0 8 months ago
Loucura 91d18fd675 Translated using Weblate (Portuguese)
Currently translated at 99.8% (656 of 657 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/pt/
8 months ago
Alex Baker 94b6d7569b Move search to viewmodel 8 months ago
Alex Baker e70f5f3b24 Move query constants 8 months ago
renovate[bot] 68c21c4b1f Update dependency androidx.compose:compose-bom to v2023.09.02 8 months ago
renovate[bot] cbcc7f9bee Update dependency com.android.tools.build:gradle to v8.2.0-beta06 8 months ago
Milo Ivir ba394b9db4
Translated using Weblate (Croatian)
Currently translated at 100.0% (668 of 668 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/hr/
8 months ago
Eric 13298aa3be
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (668 of 668 strings)

Translation: Tasks.org/Android
Translate-URL: https://hosted.weblate.org/projects/tasks/android/zh_Hans/
8 months ago
Alex Baker 993c41b197 Remove RecurringIntervalIntentService 8 months ago
Alex Baker 2bfc46f32b Remove CalendarReminderActivity 8 months ago
Alex Baker 4c61353411 Remove TimerControlSetCallback 8 months ago
Alex Baker f8d3985e97 Add hideKeyboard extension methods 8 months ago
Alex Baker c2a9d21f01 Make review request from edit fragment 8 months ago
Alex Baker 20c81417a0 Handle sort result in task list fragment 8 months ago

@ -24,7 +24,7 @@ jobs:
with:
bundler-cache: true
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
@ -41,7 +41,7 @@ jobs:
GOOGLE_KEY: ${{ secrets.GOOGLE_KEY }}
run: bundle exec fastlane bundle
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: release
path: app/build/outputs/**

@ -16,7 +16,7 @@ jobs:
with:
bundler-cache: true
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
@ -26,31 +26,39 @@ jobs:
- name: Lint checks
run: bundle exec fastlane lint
- name: Archive lint reports
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: lint-reports
path: app/build/reports/*.html
test:
runs-on: macos-latest
runs-on: ubuntu-latest
strategy:
matrix:
flavor: [Googleplay, Generic]
steps:
- name: checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew -Pcoverage app:testGoogleplayDebugUnitTest app:connectedGoogleplayDebugAndroidTest
script: ./gradlew -Pcoverage app:test${{ matrix.flavor }}DebugUnitTest app:connected${{ matrix.flavor }}DebugAndroidTest
- name: Upload test reports
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: test-reports
name: test-reports-${{ matrix.flavor }}
path: app/build/reports/**

@ -1 +1 @@
3.2.2
3.3.1

@ -1,3 +1,132 @@
### 13.9.4 (2024-05-09)
* Fix widget crash [#2873](https://github.com/tasks/tasks/issues/2873)
* Fix recurrence unable to finish [#2874](https://github.com/tasks/tasks/issues/2874)
* Fix edit screen being cleared when reopening app [#2857](https://github.com/tasks/tasks/issues/2857)
* Fix performance regressions
* Simplified internal alarm scheduling logic
* Update translations
* Arabic - @islam2hamy
* Bulgarian - @StoyanDimitrov
### 13.9 (2024-05-01)
* @elmuffo: Add swipe-to-snooze [#2839](https://github.com/tasks/tasks/pull/2839)
* @IlyaBizyaev: Add option to use quick tile without unlocking device [#2847](https://github.com/tasks/tasks/pull/2847)
* @liz-desartiges: Add support for Z Flip 5 cover screen [#2843](https://github.com/tasks/tasks/pull/2843)
* @purushyb: Fix drawer not updating after editing items [#2855](https://github.com/tasks/tasks/pull/2855)
* @hady-exc: Migrate tag picker screen to Compose [#2849](https://github.com/tasks/tasks/pull/2849)
* @yurtpage: Add Russian app store description [#2848](https://github.com/tasks/tasks/pull/2848)
* Fix duplicate notifications [#2835](https://github.com/tasks/tasks/issues/2835)
* Fix adding '(Completed)' to calendar entries [#2832](https://github.com/tasks/tasks/issues/2832)
* Fix hiding empty items from drawer [#2831](https://github.com/tasks/tasks/issues/2831)
* Exclude old snoozed tasks from snoozed task filter
* Update translations
* Brazilian Portuguese - @mayhmemo, @gorgonun
* Chinese (Simplified) - 大王叫我来巡山
* Croatian - @milotype
* Esperanto - Don Zouras
* French - Lionel HANNEQUIN
* German - sorifukobexomajepasiricupuva33, min7-i
* Portuguese - @fparri, @laralem
* Spanish - gallegonovato
* Swedish - @JonatanWick
* Turkish - @emintufan, @oersen
### 13.8.1 (2024-03-24)
* Fix copy causing duplicate Google Tasks
* Fix navigation drawer crash
* Fix backup import dropping tasks
### 13.8 (2024-03-22)
* Dynamic widget theme (name-your-price subscription required)
* Replace 'until' with 'ends on' for repeating tasks [#2797](https://github.com/tasks/tasks/pull/2797) - @akwala
* Fix loading selected list on startup [#2777](https://github.com/tasks/tasks/issues/2777)
* Fix repeating tasks ending one day early
* Fix repeating task crash
* Fix backup import crash
* Fix Astrid manual ordering crash in widget
* Update translations
* Brazilian Portuguese - @mayhmemo
* Bulgarian - @StoyanDimitrov
* Catalan - @ferranpujolcamins
* Chinese (Simplified) - 大王叫我来巡山
* Croatian - @milotype
* Czech - Odweta
* German - @macpac59
* Italian - @ppasserini
* Spanish - gallegonovato
* Swedish - @bittin
* Ukrainian - @IhorHordiichuk
* Vietnamese - @ngocanhtve
### 13.7 (2024-02-07)
* Fix returning to previous filter after search [#2700](https://github.com/tasks/tasks/pull/2700)
* Fix wearable notifications on Android 14+
* Fix issue causing repeating tasks to not repeat
* Fix dragging a task into a subtask in another list
* Rewrote navigation drawer in Jetpack Compose
* Internal changes to navigation
* Enable multi-select when adding attachments
* Show count of tasks to be deleted when clearing completed
* Include hidden subtasks when clearing completed [#2724](https://github.com/tasks/tasks/issues/2724)
* Don't show hidden or completed tasks in snoozed filter
* Remove markdown from repeating task snackbar
* Update translations
* Azerbaijani - Shaban Mamedov
* Bulgarian - @StoyanDimitrov
* Catalan - raulmagdalena
* Chinese (Simplified) - 大王叫我来巡山
* Chinese (Traditional) - @abc0922001
* Croatian - @milotype
* Dutch - @mm4c
* Esperanto - Don Zouras
* Finnish - @millerii
* French - J. Lavoie
* German - @CennoxX
* Hebrew - @elig0n
* Interlingua - @softinterlingua
* Odia - @SubhamJena
* Persian - @Monirzadeh
* Spanish - gallegonovato
* Swedish - @bittin
* Turkish - @oersen
* Ukrainian - Сергій
* Vietnamese - @ngocanhtve
### 13.6.3 (2023-11-25)
* Revert "Preserve modification times on initial sync" [#2460](https://github.com/tasks/tasks/issues/2640)
* Fix unnecessary DecSync work
### 13.6.2 (2023-10-30)
* Fix updating modification timestamp on edits
### 13.6.1 (2023-10-27)
* Push pending changes when app is backgrounded
* Don't require internet connection for DAVx5/EteSync/DecSync sync
* Don't perform background sync for DAVx5/EteSync/DecSync
* Background sync is performed by the sync app
* Preserve modification times on initial sync [#2496](https://github.com/tasks/tasks/issues/2496)
* Replace deprecated method call [#2547](https://github.com/tasks/tasks/pull/2547) - @kmj-99
* Improve task list scrolling performance
* Fix hourly recurrence bug
* Update translations
* Chinese (Simplified) - Eric
* Croatian - @milotype
* Czech - @ceskyDJ
* Finnish - @millerii
* French - Lionel HANNEQUIN, Bruno Duyé
* Japanese - Kazushi Hayama
* Portuguese - @loucurapt
* Romanian - @ygorigor
* Swedish - @bittin
### 13.6 (2023-10-07)
* Change priority with multi-select [#2257](https://github.com/tasks/tasks/pull/2452) - @vulewuxe86

@ -1,29 +1,32 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
CFPropertyList (3.0.7)
base64
nkf
rexml
addressable (2.8.5)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.824.0)
aws-sdk-core (3.181.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-eventstream (1.3.0)
aws-partitions (1.923.0)
aws-sdk-core (3.194.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sdk-kms (1.80.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.134.0)
aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-s3 (1.149.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
@ -32,11 +35,10 @@ GEM
declarative (0.0.20)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.103.0)
excon (0.110.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@ -65,15 +67,15 @@ GEM
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastlane (2.216.0)
fastimage (2.3.1)
fastlane (2.220.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
@ -85,6 +87,7 @@ GEM
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.0.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
@ -93,10 +96,10 @@ GEM
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
@ -105,11 +108,11 @@ GEM
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.49.0)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.1)
google-apis-core (0.11.3)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@ -117,28 +120,27 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.7.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.1)
google-cloud-storage (1.44.0)
google-cloud-errors (1.4.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-apis-storage_v1 (~> 0.31.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.8.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
@ -149,30 +151,33 @@ GEM
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.1)
json (2.7.2)
jwt (2.8.1)
base64
mini_magick (4.12.0)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.3.0)
multipart-post (2.4.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
nkf (0.2.0)
optparse (0.5.0)
os (1.1.4)
plist (3.7.0)
public_suffix (5.0.3)
rake (13.0.6)
plist (3.7.1)
public_suffix (5.0.5)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.6)
rexml (3.2.8)
strscan (>= 3.0.9)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.18.0)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
@ -180,22 +185,19 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.4.2)
webrick (1.8.1)
unicode-display_width (2.5.0)
word_wrap (1.0.0)
xcodeproj (1.22.0)
xcodeproj (1.24.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)

@ -11,6 +11,7 @@ plugins {
id("com.google.android.gms.oss-licenses-plugin")
id("kotlin-parcelize")
id("com.google.devtools.ksp")
kotlin("plugin.serialization") version "1.9.24"
}
repositories {
@ -55,16 +56,11 @@ android {
defaultConfig {
testApplicationId = "org.tasks.test"
applicationId = "org.tasks"
versionCode = 130601
versionName = "13.6"
versionCode = 130904
versionName = "13.9.4"
targetSdk = 33
minSdk = 24
testInstrumentationRunner = "org.tasks.TestRunner"
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
}
}
signingConfigs {
@ -139,9 +135,21 @@ android {
dimension = "store"
}
}
packagingOptions {
packaging {
resources {
excludes += setOf("META-INF/*.kotlin_module")
excludes += setOf("META-INF/*.kotlin_module", "META-INF/INDEX.LIST")
}
}
testOptions {
managedDevices {
localDevices {
create("pixel2api30") {
device = "Pixel 2"
apiLevel = 30
systemImageSource = "aosp-atd"
}
}
}
}
@ -162,6 +170,7 @@ val genericImplementation by configurations
val googleplayImplementation by configurations
dependencies {
implementation(project(":data"))
coreLibraryDesugaring(libs.desugar.jdk.libs)
implementation(libs.bitfire.dav4jvm) {
exclude(group = "junit")
@ -190,7 +199,6 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.room)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.appcompat)
implementation(libs.markwon)
implementation(libs.markwon.editor)
@ -207,9 +215,10 @@ dependencies {
debugImplementation(libs.kotlin.reflect)
implementation(libs.kotlin.jdk8)
implementation(libs.kotlin.immutable)
implementation(libs.kotlinx.serialization)
implementation(libs.okhttp)
implementation(libs.persistent.cookiejar)
implementation(libs.gson)
implementation(libs.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.constraintlayout)

@ -4,7 +4,7 @@ import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskMover
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
@ -15,9 +15,9 @@ import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.injection.InjectingTestCase

@ -12,6 +12,9 @@ import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.data.*
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.entity.CaldavTask
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskContainerMaker.PARENT

@ -4,7 +4,7 @@ import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskMover
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
@ -14,8 +14,8 @@ import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.injection.InjectingTestCase

@ -6,7 +6,7 @@ import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.core.BuiltInFilterExposer
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskMover
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
@ -15,8 +15,8 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.injection.InjectingTestCase

@ -4,7 +4,7 @@ import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.core.BuiltInFilterExposer
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking

@ -7,19 +7,18 @@ import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
import org.tasks.data.Alarm.Companion.TYPE_RANDOM
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.Alarm.Companion.whenDue
import org.tasks.data.Alarm.Companion.whenOverdue
import org.tasks.data.AlarmDao
import org.tasks.data.TaskDao
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.Alarm.Companion.TYPE_DATE_TIME
import org.tasks.data.entity.Alarm.Companion.TYPE_RANDOM
import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.entity.Alarm.Companion.whenDue
import org.tasks.data.entity.Alarm.Companion.whenOverdue
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.TaskDao
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.jobs.AlarmEntry
import org.tasks.jobs.NotificationQueue
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.DELETION_TIME
import org.tasks.makers.TaskMaker.DUE_DATE
@ -34,7 +33,6 @@ import javax.inject.Inject
class AlarmJobServiceTest : InjectingTestCase() {
@Inject lateinit var alarmDao: AlarmDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var jobs: NotificationQueue
@Inject lateinit var alarmService: AlarmService
@Test
@ -42,7 +40,7 @@ class AlarmJobServiceTest : InjectingTestCase() {
val task = taskDao.createNew(newTask())
val alarm = insertAlarm(Alarm(task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME))
verify(AlarmEntry(alarm, task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME))
verify(overdue = listOf(AlarmEntry(alarm, task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME)))
}
@Test
@ -90,7 +88,7 @@ class AlarmJobServiceTest : InjectingTestCase() {
alarmDao.insert(Alarm(task, DateUtilities.ONE_HOUR, TYPE_RANDOM))
val alarm = alarmDao.insert(Alarm(task, now.plusMonths(12).millis, TYPE_SNOOZE))
verify(AlarmEntry(alarm, task, now.plusMonths(12).millis, TYPE_SNOOZE))
verify(future = listOf(AlarmEntry(alarm, task, now.plusMonths(12).millis, TYPE_SNOOZE)))
}
private suspend fun insertAlarm(alarm: Alarm): Long {
@ -98,9 +96,13 @@ class AlarmJobServiceTest : InjectingTestCase() {
return alarm.id
}
private suspend fun verify(vararg alarms: AlarmEntry) {
alarmService.scheduleAllAlarms()
private suspend fun verify(
overdue: List<AlarmEntry> = emptyList(),
future: List<AlarmEntry> = emptyList(),
) {
val (actualOverdue, actualFuture) = alarmService.getAlarms()
assertEquals(alarms.toList(), jobs.getJobs())
assertEquals(overdue, actualOverdue)
assertEquals(future, actualFuture)
}
}

@ -5,8 +5,7 @@
*/
package com.todoroo.astrid.dao
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
@ -15,6 +14,7 @@ import org.junit.Assert.*
import org.junit.Test
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -75,23 +75,23 @@ class TaskDaoTests : InjectingTestCase() {
// create hidden task
task = Task()
task.title = "hidden"
task.hideUntil = DateUtilities.now() + 10000
task.hideUntil = currentTimeMillis() + 10000
taskDao.createNew(task)
// create task with deadlines
task = Task()
task.title = "deadlineInFuture"
task.dueDate = DateUtilities.now() + 10000
task.dueDate = currentTimeMillis() + 10000
taskDao.createNew(task)
task = Task()
task.title = "deadlineInPast"
task.dueDate = DateUtilities.now() - 10000
task.dueDate = currentTimeMillis() - 10000
taskDao.createNew(task)
// create completed task
task = Task()
task.title = "completed"
task.completionDate = DateUtilities.now() - 10000
task.completionDate = currentTimeMillis() - 10000
taskDao.createNew(task)
// check is active

@ -11,9 +11,9 @@ import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskListDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavCalendarMaker.ID
@ -90,10 +90,10 @@ class GtasksListServiceTest : InjectingTestCase() {
}
private suspend fun setLists(vararg list: TaskList) {
val account = CaldavAccount().apply {
username = "account"
uuid = "account"
}
val account = CaldavAccount(
username = "account",
uuid = "account",
)
caldavDao.insert(account)
gtasksListService.updateLists(account, listOf(*list))
}

@ -1,7 +1,7 @@
package com.todoroo.astrid.model
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -10,7 +10,7 @@ import org.junit.Test
import org.tasks.SuspendFreeze.Companion.freezeClock
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.time.DateTimeUtils
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -23,7 +23,7 @@ class TaskTest : InjectingTestCase() {
freezeClock {
val task = Task()
taskDao.createNew(task)
assertEquals(DateTimeUtils.currentTimeMillis(), task.creationDate)
assertEquals(currentTimeMillis(), task.creationDate)
}
}

@ -1,50 +1,68 @@
package com.todoroo.astrid.repeats
import com.natpryce.makeiteasy.MakeItEasy.with
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskCompleter
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.TaskDao
import org.tasks.data.dao.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.RECUR
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class RepeatWithSubtasksTests : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var repeat: RepeatTaskHelper
@Inject lateinit var taskCompleter: TaskCompleter
@Test
fun uncompleteGrandchildren() = runBlocking {
val grandparent = taskDao.createNew(newTask(with(RECUR, "RRULE:FREQ=DAILY")))
val parent = taskDao.createNew(newTask(with(PARENT, grandparent)))
val child = taskDao.createNew(newTask(
with(PARENT, parent),
with(COMPLETION_TIME, DateTime())
))
val grandparent = taskDao.createNew(
Task(
recurrence = "RRULE:FREQ=DAILY"
)
)
val parent = taskDao.createNew(
Task(
parent = grandparent
)
)
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
repeat.handleRepeat(taskDao.fetch(grandparent)!!)
assertTrue(taskDao.fetch(child)!!.isCompleted)
taskCompleter.setComplete(grandparent)
assertFalse(taskDao.fetch(child)!!.isCompleted)
}
@Test
fun uncompleteGoogleTaskChildren() = runBlocking {
val parent = taskDao.createNew(newTask(with(RECUR, "RRULE:FREQ=DAILY")))
val child = taskDao.createNew(newTask(
with(PARENT, parent),
with(COMPLETION_TIME, DateTime())
))
val parent = taskDao.createNew(
Task(
recurrence = "RRULE:FREQ=DAILY"
)
)
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
assertTrue(taskDao.fetch(child)!!.isCompleted)
repeat.handleRepeat(taskDao.fetch(parent)!!)
taskCompleter.setComplete(parent)
assertFalse(taskDao.fetch(child)!!.isCompleted)
}

@ -5,14 +5,14 @@
*/
package com.todoroo.astrid.service
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.utility.TitleParser
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import org.tasks.data.TagDataDao
import org.tasks.data.dao.TagDataDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import java.util.*

@ -1,10 +1,12 @@
package com.todoroo.astrid.service
import com.todoroo.astrid.api.PermaSql.*
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.DUE_DATE
import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL
import com.todoroo.astrid.data.Task.Companion.URGENCY_SPECIFIC_DAY
import com.todoroo.astrid.api.PermaSql.VALUE_EOD
import com.todoroo.astrid.api.PermaSql.VALUE_EOD_NEXT_WEEK
import com.todoroo.astrid.api.PermaSql.VALUE_EOD_TOMORROW
import org.tasks.data.entity.Task
import org.tasks.data.entity.Task.Companion.DUE_DATE
import org.tasks.data.entity.Task.Companion.HIDE_UNTIL
import org.tasks.data.entity.Task.Companion.URGENCY_SPECIFIC_DAY
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -12,6 +14,7 @@ import org.junit.Assert.assertEquals
import org.junit.Test
import org.tasks.R
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.data.createDueDate
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.preferences.Preferences
@ -35,7 +38,7 @@ class TaskCreatorTest : InjectingTestCase() {
assertEquals(DateTime(2021, 2, 4).millis, task.hideUntil)
assertEquals(
Task.createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
task.dueDate
)
}
@ -63,7 +66,7 @@ class TaskCreatorTest : InjectingTestCase() {
assertEquals(DateTime(2021, 2, 4).millis, task.hideUntil)
assertEquals(
Task.createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 4).millis),
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 4).millis),
task.dueDate
)
}
@ -93,7 +96,7 @@ class TaskCreatorTest : InjectingTestCase() {
}
assertEquals(
Task.createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
task.dueDate
)
}

@ -1,22 +1,15 @@
package com.todoroo.astrid.service
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.core.BuiltInFilterExposer.Companion.getMyTasksFilter
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskDao
import org.tasks.data.dao.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.RECUR
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -24,75 +17,26 @@ import javax.inject.Inject
class TaskDeleterTest : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Test
fun clearCompletedTask() = runBlocking {
val task = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
fun markTaskAsDeleted() = runBlocking {
val task = Task()
taskDao.createNew(task)
clearCompleted()
taskDeleter.markDeleted(task)
assertTrue(taskDao.fetch(task)!!.isDeleted)
assertTrue(taskDao.fetch(task.id)!!.isDeleted)
}
@Test
fun dontDeleteTaskWithRecurringParent() = runBlocking {
val parent = taskDao.createNew(newTask(with(RECUR, "RRULE:FREQ=DAILY;INTERVAL=1")))
val child = taskDao.createNew(newTask(
with(PARENT, parent),
with(COMPLETION_TIME, DateTime())
))
fun dontDeleteReadOnlyTasks() = runBlocking {
val task = Task(
readOnly = true
)
taskDao.createNew(task)
clearCompleted()
taskDeleter.markDeleted(task)
assertFalse(taskDao.fetch(child)!!.isDeleted)
assertFalse(taskDao.fetch(task.id)!!.isDeleted)
}
@Test
fun dontDeleteTaskWithRecurringGrandparent() = runBlocking {
val grandparent = taskDao.createNew(newTask(with(RECUR, "RRULE:FREQ=DAILY;INTERVAL=1")))
val parent = taskDao.createNew(newTask(with(PARENT, grandparent)))
val child = taskDao.createNew(newTask(
with(PARENT, parent),
with(COMPLETION_TIME, DateTime())
))
clearCompleted()
assertFalse(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearGrandchildWithNoRecurringAncestors() = runBlocking {
val grandparent = taskDao.createNew(newTask())
val parent = taskDao.createNew(newTask(with(PARENT, grandparent)))
val child = taskDao.createNew(newTask(
with(PARENT, parent),
with(COMPLETION_TIME, DateTime())
))
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearGrandchildWithCompletedRecurringAncestor() = runBlocking {
val grandparent = taskDao.createNew(newTask(
with(RECUR, "RRULE:FREQ=DAILY;INTERVAL=1"),
with(COMPLETION_TIME, DateTime())
))
val parent = taskDao.createNew(newTask(with(PARENT, grandparent)))
val child = taskDao.createNew(newTask(
with(PARENT, parent),
with(COMPLETION_TIME, DateTime())
))
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
private suspend fun clearCompleted() =
taskDeleter.clearCompleted(getMyTasksFilter(context.resources))
}
}

@ -11,17 +11,15 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.jobs.WorkManager
import org.tasks.makers.CaldavAccountMaker
import org.tasks.makers.CaldavAccountMaker.ACCOUNT_TYPE
import org.tasks.makers.CaldavAccountMaker.newCaldavAccount
import org.tasks.makers.CaldavCalendarMaker
import org.tasks.makers.CaldavCalendarMaker.ACCOUNT
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
@ -319,6 +317,11 @@ class TaskMoverTest : InjectingTestCase() {
}
private suspend fun setAccountType(account: String, type: Int) {
caldavDao.insert(newCaldavAccount(with(CaldavAccountMaker.UUID, account), with(ACCOUNT_TYPE, type)))
caldavDao.insert(
CaldavAccount(
uuid = account,
accountType = type,
)
)
}
}

@ -5,7 +5,7 @@
*/
package com.todoroo.astrid.service
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.utility.TitleParser
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
@ -16,7 +16,7 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.tasks.R
import org.tasks.data.TagDataDao
import org.tasks.data.dao.TagDataDao
import org.tasks.date.DateTimeUtils
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule

@ -3,7 +3,7 @@
package com.todoroo.astrid.service
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -12,9 +12,9 @@ import org.junit.Test
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.TestUtilities.assertEquals
import org.tasks.caldav.VtodoCache
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.TaskDao
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar

@ -1,13 +1,13 @@
package com.todoroo.astrid.subtasks
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.tasks.data.TaskListMetadata
import org.tasks.data.entity.TaskListMetadata
import org.tasks.injection.ProductionModule
@UninstallModules(ProductionModule::class)

@ -1,12 +1,12 @@
package com.todoroo.astrid.subtasks
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.tasks.data.TaskListMetadata
import org.tasks.data.entity.TaskListMetadata
import org.tasks.injection.ProductionModule
@UninstallModules(ProductionModule::class)

@ -1,20 +1,20 @@
package com.todoroo.astrid.subtasks
import androidx.test.InstrumentationRegistry
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.core.BuiltInFilterExposer
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.tasks.data.TaskListMetadataDao
import org.tasks.data.dao.TaskListMetadataDao
import org.tasks.injection.InjectingTestCase
import org.tasks.preferences.Preferences
import javax.inject.Inject
abstract class SubtasksTestCase : InjectingTestCase() {
lateinit var updater: SubtasksFilterUpdater
lateinit var filter: Filter
lateinit var filter: AstridOrderingFilter
@Inject lateinit var taskListMetadataDao: TaskListMetadataDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences

@ -1,9 +1,9 @@
package com.todoroo.astrid.sync
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.data.entity.Task
import org.tasks.data.entity.TagData
import org.tasks.data.dao.TagDataDao
import org.tasks.injection.InjectingTestCase
import javax.inject.Inject
@ -12,9 +12,10 @@ open class NewSyncTestCase : InjectingTestCase() {
@Inject lateinit var tagDataDao: TagDataDao
suspend fun createTask(): Task {
val task = Task()
task.title = SYNC_TASK_TITLE
task.priority = SYNC_TASK_IMPORTANCE
val task = Task(
title = SYNC_TASK_TITLE,
priority = SYNC_TASK_IMPORTANCE,
)
taskDao.createNew(task)
return task
}

@ -1,6 +1,6 @@
package com.todoroo.astrid.sync
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking

@ -1,15 +1,15 @@
package org.tasks.caldav
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.ETAG
@ -25,12 +25,13 @@ class CaldavSynchronizerTest : CaldavTest() {
@Before
override fun setUp() = runBlocking {
super.setUp()
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = "username"
password = encryption.encrypt("password")
url = server.url("/remote.php/dav/calendars/user1/").toString()
id = caldavDao.insert(this)
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = "username",
password = encryption.encrypt("password"),
url = server.url("/remote.php/dav/calendars/user1/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
}
}
@ -45,11 +46,13 @@ class CaldavSynchronizerTest : CaldavTest() {
@Test
fun dontFetchCalendarIfCtagMatches() = runBlocking {
caldavDao.insert(CaldavCalendar(
account = this@CaldavSynchronizerTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@CaldavSynchronizerTest.account.url}test-shared/",
))
caldavDao.insert(
CaldavCalendar(
account = this@CaldavSynchronizerTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@CaldavSynchronizerTest.account.url}test-shared/",
)
)
enqueue(OC_SHARE_PROPFIND)
sync()

@ -9,8 +9,8 @@ import org.junit.Before
import org.junit.Rule
import org.junit.rules.Timeout
import org.tasks.R
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.dao.CaldavDao
import org.tasks.injection.InjectingTestCase
import org.tasks.preferences.Preferences
import org.tasks.security.KeyStoreEncryption

@ -1,19 +1,19 @@
package org.tasks.caldav
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.SERVER_OPEN_XCHANGE
import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavAccount.Companion.SERVER_OPEN_XCHANGE
import org.tasks.data.entity.CaldavAccount.Companion.SERVER_OWNCLOUD
import org.tasks.data.entity.CaldavAccount.Companion.SERVER_SABREDAV
import org.tasks.data.entity.CaldavAccount.Companion.SERVER_TASKS
import org.tasks.data.entity.CaldavAccount.Companion.SERVER_UNKNOWN
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.injection.ProductionModule
@UninstallModules(ProductionModule::class)
@ -79,13 +79,14 @@ class ServerDetectionTest : CaldavTest() {
vararg headers: Pair<String, String>,
accountType: Int = TYPE_CALDAV
) {
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = "username"
password = encryption.encrypt("password")
url = server.url("/remote.php/dav/calendars/user1/").toString()
id = caldavDao.insert(this)
this.accountType = accountType
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = "username",
password = encryption.encrypt("password"),
url = server.url("/remote.php/dav/calendars/user1/").toString(),
accountType = accountType,
).let {
it.copy(id = caldavDao.insert(it))
}
this.headers.putAll(headers)
enqueue(NO_CALENDARS)

@ -1,16 +1,16 @@
package org.tasks.caldav
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_WRITE
import org.tasks.data.PrincipalDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_READ_WRITE
import org.tasks.data.dao.PrincipalDao
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@ -22,12 +22,13 @@ class SharingMailboxDotOrgTest : CaldavTest() {
@Test
fun ownerAccess() = runBlocking {
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = "3"
password = encryption.encrypt("password")
url = server.url("/caldav/").toString()
id = caldavDao.insert(this)
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = "3",
password = encryption.encrypt("password"),
url = server.url("/caldav/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
}
val calendar = CaldavCalendar(
account = this@SharingMailboxDotOrgTest.account.uuid,
@ -45,12 +46,13 @@ class SharingMailboxDotOrgTest : CaldavTest() {
@Test
fun principalForSharee() = runBlocking {
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = "3"
password = encryption.encrypt("password")
url = server.url("/caldav/").toString()
id = caldavDao.insert(this)
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = "3",
password = encryption.encrypt("password"),
url = server.url("/caldav/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
}
val calendar = CaldavCalendar(
account = this@SharingMailboxDotOrgTest.account.uuid,

@ -1,16 +1,16 @@
package org.tasks.caldav
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Test
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_ONLY
import org.tasks.data.PrincipalDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_READ_ONLY
import org.tasks.data.dao.PrincipalDao
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@ -21,12 +21,13 @@ class SharingOwncloudTest : CaldavTest() {
@Inject lateinit var principalDao: PrincipalDao
private suspend fun setupAccount(user: String) {
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = user
password = encryption.encrypt("password")
url = server.url("/remote.php/dav/calendars/$user/").toString()
id = caldavDao.insert(this)
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = user,
password = encryption.encrypt("password"),
url = server.url("/remote.php/dav/calendars/$user/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
}
}

@ -1,18 +1,18 @@
package org.tasks.caldav
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_WRITE
import org.tasks.data.CaldavCalendar.Companion.INVITE_ACCEPTED
import org.tasks.data.PrincipalDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_READ_WRITE
import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_ACCEPTED
import org.tasks.data.dao.PrincipalDao
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@ -23,12 +23,13 @@ class SharingSabredavTest : CaldavTest() {
@Inject lateinit var principalDao: PrincipalDao
private suspend fun setupAccount(user: String) {
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = user
password = encryption.encrypt("password")
url = server.url("/calendars/$user/").toString()
id = caldavDao.insert(this)
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = user,
password = encryption.encrypt("password"),
url = server.url("/calendars/$user/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
}
}

@ -2,7 +2,6 @@ package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.andlib.utility.DateUtilities.now
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -10,11 +9,15 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.TaskDao
import org.tasks.data.entity.CaldavTask
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskContainerMaker
import org.tasks.makers.TaskContainerMaker.CREATED
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -101,7 +104,9 @@ class CaldavDaoShiftTests : InjectingTestCase() {
fun ignoreMovedTasksWhenShiftingDown() = runBlocking {
val created = DateTime(2020, 5, 17, 9, 53, 17)
addTask(with(CREATED, created))
caldavDao.update(caldavDao.getTask(tasks[0].id).apply { this?.deleted = now() }!!)
caldavDao.update(caldavDao.getTask(tasks[0].id).apply { this?.deleted =
currentTimeMillis()
}!!)
caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())
@ -112,7 +117,7 @@ class CaldavDaoShiftTests : InjectingTestCase() {
fun ignoreDeletedTasksWhenShiftingDown() = runBlocking {
val created = DateTime(2020, 5, 17, 9, 53, 17)
addTask(with(CREATED, created))
taskDao.update(taskDao.fetch(tasks[0].id).apply { this?.deletionDate = now() }!!)
taskDao.update(taskDao.fetch(tasks[0].id).apply { this?.deletionDate = currentTimeMillis() }!!)
caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())

@ -2,12 +2,18 @@ package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.helper.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.TagDao
import org.tasks.data.dao.TagDataDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavTask
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskMaker.CREATION_TIME
@ -85,8 +91,7 @@ class CaldavDaoTests : InjectingTestCase() {
@Test
fun noResultsForEmptyAccounts() = runBlocking {
val caldavAccount = CaldavAccount()
caldavAccount.uuid = UUIDHelper.newUUID()
val caldavAccount = CaldavAccount(uuid = UUIDHelper.newUUID())
caldavDao.insert(caldavAccount)
assertTrue(caldavDao.getCaldavFilters(caldavAccount.uuid!!).isEmpty())
}

@ -2,13 +2,18 @@ package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.helper.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.CaldavDao.Companion.LOCAL
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.CaldavDao.Companion.LOCAL
import org.tasks.data.dao.DeletionDao
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
@ -16,7 +21,7 @@ import org.tasks.makers.TaskMaker.CREATION_TIME
import org.tasks.makers.TaskMaker.DELETION_TIME
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -43,7 +48,7 @@ class DeletionDaoTests : InjectingTestCase() {
deletionDao.markDeleted(listOf(task.id))
task = taskDao.fetch(task.id)!!
assertTrue(task.modificationDate > task.creationDate)
assertTrue(task.modificationDate < DateTimeUtils.currentTimeMillis())
assertTrue(task.modificationDate < currentTimeMillis())
}
@Test
@ -53,7 +58,7 @@ class DeletionDaoTests : InjectingTestCase() {
deletionDao.markDeleted(listOf(task.id))
task = taskDao.fetch(task.id)!!
assertTrue(task.deletionDate > task.creationDate)
assertTrue(task.deletionDate < DateTimeUtils.currentTimeMillis())
assertTrue(task.deletionDate < currentTimeMillis())
}
@Test

@ -9,11 +9,14 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.dao.GoogleTaskListDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavTask
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavAccountMaker.ACCOUNT_TYPE
import org.tasks.makers.CaldavAccountMaker.newCaldavAccount
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
@ -35,7 +38,7 @@ class GoogleTaskDaoTests : InjectingTestCase() {
override fun setUp() {
super.setUp()
runBlocking {
caldavDao.insert(newCaldavAccount(with(ACCOUNT_TYPE, TYPE_GOOGLE_TASKS)))
caldavDao.insert(CaldavAccount(uuid = "account", accountType = TYPE_GOOGLE_TASKS))
caldavDao.insert(newCaldavCalendar())
}
}

@ -3,8 +3,11 @@ package org.tasks.data
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskListDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@ -17,10 +20,10 @@ class GoogleTaskListDaoTest : InjectingTestCase() {
@Test
fun noResultsForEmptyAccount() = runBlocking {
val account = CaldavAccount().apply {
uuid = "user@gmail.com"
username = "user@gmail.com"
}
val account = CaldavAccount(
uuid = "user@gmail.com",
username = "user@gmail.com",
)
caldavDao.insert(account)
assertTrue(googleTaskListDao.getGoogleTaskFilters(account.username!!).isEmpty())

@ -1,9 +1,8 @@
package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -11,7 +10,12 @@ import org.junit.Assert.*
import org.junit.Test
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.caldav.GeoUtils.toLikeString
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.LocationDao
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.Geofence
import org.tasks.data.entity.Place
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
@ -21,6 +25,7 @@ import org.tasks.makers.TaskMaker.DUE_TIME
import org.tasks.makers.TaskMaker.HIDE_TYPE
import org.tasks.makers.TaskMaker.ID
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -113,33 +118,33 @@ class LocationDaoTest : InjectingTestCase() {
@Test
fun ignoreArrivalForSnoozedTask() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
val task = taskDao.createNew(newTask())
alarmDao.insert(Alarm(task, newDateTime().plusMinutes(15).millis, TYPE_SNOOZE))
locationDao.insert(Geofence(task = task, place = place.uid, isArrival = true))
assertTrue(locationDao.getArrivalGeofences(place.uid!!, now()).isEmpty())
assertTrue(locationDao.getArrivalGeofences(place.uid!!, currentTimeMillis()).isEmpty())
}
}
@Test
fun ignoreDepartureForSnoozedTask() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
val task = taskDao.createNew(newTask())
alarmDao.insert(Alarm(task, newDateTime().plusMinutes(15).millis, TYPE_SNOOZE))
locationDao.insert(Geofence(task = task, place = place.uid, isDeparture = true))
assertTrue(locationDao.getDepartureGeofences(place.uid!!, now()).isEmpty())
assertTrue(locationDao.getDepartureGeofences(place.uid!!, currentTimeMillis()).isEmpty())
}
}
@Test
fun getArrivalWithElapsedSnooze() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
val task = taskDao.createNew(newTask())
@ -147,13 +152,15 @@ class LocationDaoTest : InjectingTestCase() {
val geofence = Geofence(task = task, place = place.uid, isArrival = true)
.let { it.copy(id = locationDao.insert(it)) }
assertEquals(listOf(geofence), locationDao.getArrivalGeofences(place.uid!!, now()))
assertEquals(listOf(geofence), locationDao.getArrivalGeofences(place.uid!!,
currentTimeMillis()
))
}
}
@Test
fun getDepartureWithElapsedSnooze() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
val task = taskDao.createNew(newTask())
@ -161,13 +168,15 @@ class LocationDaoTest : InjectingTestCase() {
val geofence = Geofence(task = task, place = place.uid, isDeparture = true)
.let { it.copy(id = locationDao.insert(it)) }
assertEquals(listOf(geofence), locationDao.getDepartureGeofences(place.uid!!, now()))
assertEquals(listOf(geofence), locationDao.getDepartureGeofences(place.uid!!,
currentTimeMillis()
))
}
}
@Test
fun ignoreArrivalForHiddenTask() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
taskDao.createNew(newTask(
@ -176,13 +185,13 @@ class LocationDaoTest : InjectingTestCase() {
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE_TIME)))
locationDao.insert(Geofence(task = 1, place = place.uid, isArrival = true))
assertTrue(locationDao.getArrivalGeofences(place.uid!!, now()).isEmpty())
assertTrue(locationDao.getArrivalGeofences(place.uid!!, currentTimeMillis()).isEmpty())
}
}
@Test
fun ignoreDepartureForHiddenTask() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
taskDao.createNew(newTask(
@ -191,13 +200,13 @@ class LocationDaoTest : InjectingTestCase() {
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE_TIME)))
locationDao.insert(Geofence(task = 1, place = place.uid, isDeparture = true))
assertTrue(locationDao.getDepartureGeofences(place.uid!!, now()).isEmpty())
assertTrue(locationDao.getDepartureGeofences(place.uid!!, currentTimeMillis()).isEmpty())
}
}
@Test
fun getArrivalWithElapsedHideUntil() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
taskDao.createNew(newTask(
@ -209,13 +218,15 @@ class LocationDaoTest : InjectingTestCase() {
it.copy(id = locationDao.insert(it))
}
assertEquals(listOf(geofence), locationDao.getArrivalGeofences(place.uid!!, now()))
assertEquals(listOf(geofence), locationDao.getArrivalGeofences(place.uid!!,
currentTimeMillis()
))
}
}
@Test
fun getDepartureWithElapsedHideUntil() = runBlocking {
freezeAt(now()).thawAfter {
freezeAt(currentTimeMillis()).thawAfter {
val place = Place()
locationDao.insert(place)
taskDao.createNew(newTask(
@ -225,7 +236,9 @@ class LocationDaoTest : InjectingTestCase() {
val geofence = Geofence(task = 1, place = place.uid, isDeparture = true)
.let { it.copy(id = locationDao.insert(it)) }
assertEquals(listOf(geofence), locationDao.getDepartureGeofences(place.uid!!, now()))
assertEquals(listOf(geofence), locationDao.getDepartureGeofences(place.uid!!,
currentTimeMillis()
))
}
}
}

@ -3,7 +3,6 @@ package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.helper.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -11,9 +10,11 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.tasks.R
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavAccountMaker.newCaldavAccount
import org.tasks.makers.CaldavCalendarMaker.UUID
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker.CALENDAR
@ -42,7 +43,7 @@ class ManualGoogleTaskQueryTest : InjectingTestCase() {
preferences.setBoolean(R.string.p_manual_sort, true)
val calendar = newCaldavCalendar(with(UUID, "1234"))
runBlocking {
caldavDao.insert(newCaldavAccount())
caldavDao.insert(CaldavAccount())
caldavDao.insert(calendar)
}
filter = GtasksFilter(calendar)

@ -8,6 +8,8 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.dao.TagDao
import org.tasks.data.dao.TagDataDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TagDataMaker.NAME

@ -6,8 +6,7 @@
package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
@ -15,10 +14,12 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.tasks.data.dao.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -44,23 +45,23 @@ class TaskDaoTests : InjectingTestCase() {
// create hidden task
task = Task()
task.title = "hidden"
task.hideUntil = DateUtilities.now() + 10000
task.hideUntil = currentTimeMillis() + 10000
taskDao.createNew(task)
// create task with deadlines
task = Task()
task.title = "deadlineInFuture"
task.dueDate = DateUtilities.now() + 10000
task.dueDate = currentTimeMillis() + 10000
taskDao.createNew(task)
task = Task()
task.title = "deadlineInPast"
task.dueDate = DateUtilities.now() - 10000
task.dueDate = currentTimeMillis() - 10000
taskDao.createNew(task)
// create completed task
task = Task()
task.title = "completed"
task.completionDate = DateUtilities.now() - 10000
task.completionDate = currentTimeMillis() - 10000
taskDao.createNew(task)
// check is active

@ -8,6 +8,11 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.TagDao
import org.tasks.data.dao.TagDataDao
import org.tasks.data.dao.UpgraderDao
import org.tasks.data.entity.CaldavTask
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TagDataMaker

@ -2,7 +2,7 @@ package org.tasks.gtasks
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

@ -2,7 +2,7 @@ package org.tasks.injection
import android.content.Context
import androidx.room.Room
import com.todoroo.astrid.dao.Database
import org.tasks.data.db.Database
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

@ -8,7 +8,7 @@ package org.tasks.jobs
import android.net.Uri
import androidx.test.InstrumentationRegistry
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking

@ -1,7 +1,7 @@
package org.tasks.opentasks
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking

@ -1,7 +1,7 @@
package org.tasks.opentasks
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -13,11 +13,11 @@ import org.tasks.caldav.iCalendar.Companion.collapsed
import org.tasks.caldav.iCalendar.Companion.order
import org.tasks.caldav.iCalendar.Companion.parent
import org.tasks.caldav.iCalendar.Companion.snooze
import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao
import org.tasks.data.TagDao
import org.tasks.data.TagDataDao
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.TagDao
import org.tasks.data.dao.TagDataDao
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker
import org.tasks.makers.CaldavTaskMaker.CALENDAR

@ -4,11 +4,13 @@ import com.natpryce.makeiteasy.MakeItEasy.with
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.TYPE_OPENTASKS
import org.tasks.data.CaldavCalendar
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_OPENTASKS
import org.tasks.data.entity.CaldavCalendar
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
@ -38,10 +40,12 @@ class OpenTasksSynchronizerTest : OpenTasksTest() {
@Test
fun deleteRemovedAccounts() = runBlocking {
caldavDao.insert(CaldavAccount().apply {
uuid = "bitfire.at.davdroid:test_account"
accountType = TYPE_OPENTASKS
})
caldavDao.insert(
CaldavAccount(
uuid = "bitfire.at.davdroid:test_account",
accountType = TYPE_OPENTASKS,
)
)
synchronizer.sync()

@ -3,8 +3,8 @@ package org.tasks.opentasks
import com.todoroo.astrid.dao.TaskDao
import org.junit.Before
import org.tasks.R
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao
import org.tasks.injection.InjectingTestCase
import org.tasks.preferences.Preferences
import javax.inject.Inject

@ -4,13 +4,13 @@ import android.content.ContentProviderResult
import android.content.Context
import at.bitfire.ical4android.BatchOperation
import at.bitfire.ical4android.Task
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import dagger.hilt.android.qualifiers.ApplicationContext
import org.dmfs.tasks.contract.TaskContract
import org.dmfs.tasks.contract.TaskContract.TaskListColumns.ACCESS_LEVEL_OWNER
import org.tasks.caldav.iCalendar
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao
import org.tasks.data.MyAndroidTask
import org.tasks.data.OpenTaskDao
import javax.inject.Inject
@ -20,11 +20,11 @@ class TestOpenTaskDao @Inject constructor(
private val caldavDao: CaldavDao
) : OpenTaskDao(context, caldavDao) {
suspend fun insertList(
name: String = DEFAULT_LIST,
type: String = DEFAULT_TYPE,
account: String = DEFAULT_ACCOUNT,
url: String = UUIDHelper.newUUID(),
accessLevel: Int = ACCESS_LEVEL_OWNER,
name: String = DEFAULT_LIST,
type: String = DEFAULT_TYPE,
account: String = DEFAULT_ACCOUNT,
url: String = UUIDHelper.newUUID(),
accessLevel: Int = ACCESS_LEVEL_OWNER,
): Pair<Long, CaldavCalendar> {
val uri = taskLists.buildUpon()
.appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true")

@ -3,9 +3,9 @@ package org.tasks.preferences
import android.annotation.SuppressLint
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.todoroo.astrid.data.Task.Companion.NOTIFY_AFTER_DEADLINE
import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_DEADLINE
import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_START
import org.tasks.data.entity.Task.Companion.NOTIFY_AFTER_DEADLINE
import org.tasks.data.entity.Task.Companion.NOTIFY_AT_DEADLINE
import org.tasks.data.entity.Task.Companion.NOTIFY_AT_START
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

@ -11,7 +11,8 @@ import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.time.DateTime
import java.text.ParseException
import java.util.*
import java.util.Locale
import java.util.TimeZone
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -84,7 +85,7 @@ class RepeatRuleToStringTest : InjectingTestCase() {
Freeze.freezeAt(DateTime(2021, 1, 4)) {
withTZ(BERLIN) {
assertEquals(
"Repeats daily until February 23",
"Repeats daily, ends on February 23",
toString("RRULE:FREQ=DAILY;UNTIL=20210223;INTERVAL=1")
)
}
@ -96,7 +97,7 @@ class RepeatRuleToStringTest : InjectingTestCase() {
Freeze.freezeAt(DateTime(2021, 1, 4)) {
withTZ(LONDON) {
assertEquals(
"Repeats daily until February 23",
"Repeats daily, ends on February 23",
toString("RRULE:FREQ=DAILY;UNTIL=20210223;INTERVAL=1")
)
}
@ -108,7 +109,7 @@ class RepeatRuleToStringTest : InjectingTestCase() {
Freeze.freezeAt(DateTime(2021, 1, 4)) {
withTZ(NEW_YORK) {
assertEquals(
"Repeats daily until February 23",
"Repeats daily, ends on February 23",
toString("RRULE:FREQ=DAILY;UNTIL=20210223;INTERVAL=1")
)
}

@ -3,9 +3,9 @@ package org.tasks.ui.editviewmodel
import androidx.lifecycle.SavedStateHandle
import com.todoroo.astrid.activity.TaskEditFragment
import com.todoroo.astrid.alarms.AlarmService
import com.todoroo.astrid.dao.Database
import org.tasks.data.db.Database
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.gcal.GCalHelper
import com.todoroo.astrid.service.TaskCompleter
import com.todoroo.astrid.service.TaskDeleter
@ -15,10 +15,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.runBlocking
import org.tasks.calendars.CalendarEventProvider
import org.tasks.data.AlarmDao
import org.tasks.data.LocationDao
import org.tasks.data.TagDataDao
import org.tasks.data.UserActivityDao
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.LocationDao
import org.tasks.data.dao.TagDataDao
import org.tasks.data.dao.UserActivityDao
import org.tasks.data.getLocation
import org.tasks.injection.InjectingTestCase
import org.tasks.location.GeofenceApi
import org.tasks.preferences.DefaultFilterProvider
@ -64,20 +65,20 @@ open class BaseTaskEditViewModelTest : InjectingTestCase() {
calendarEventProvider,
gCalHelper,
taskMover,
db.locationDao,
db.locationDao(),
geofenceApi,
db.tagDao,
db.tagDataDao,
db.tagDao(),
db.tagDataDao(),
preferences,
db.googleTaskDao,
db.caldavDao,
db.googleTaskDao(),
db.caldavDao(),
taskCompleter,
alarmService,
MutableSharedFlow(),
MutableSharedFlow(),
userActivityDao = userActivityDao,
taskAttachmentDao = db.taskAttachmentDao,
alarmDao = db.alarmDao,
taskAttachmentDao = db.taskAttachmentDao(),
alarmDao = db.alarmDao(),
)
}

@ -1,7 +1,7 @@
package org.tasks.ui.editviewmodel
import com.natpryce.makeiteasy.MakeItEasy
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import org.junit.Assert

@ -1,16 +1,19 @@
package org.tasks.ui.editviewmodel
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.whenOverdue
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.Alarm.Companion.whenOverdue
import org.tasks.data.createDueDate
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTimeUtils.currentTimeMillis
import org.tasks.time.DateTimeUtils2.currentTimeMillis
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
@ -22,7 +25,7 @@ class ReminderTests : BaseTaskEditViewModelTest() {
setup(task)
viewModel.setStartDate(
Task.createDueDate(
createDueDate(
Task.URGENCY_SPECIFIC_DAY_TIME,
currentTimeMillis()
)
@ -43,7 +46,7 @@ class ReminderTests : BaseTaskEditViewModelTest() {
setup(task)
viewModel.setDueDate(
Task.createDueDate(
createDueDate(
Task.URGENCY_SPECIFIC_DAY_TIME,
currentTimeMillis()
)
@ -64,7 +67,7 @@ class ReminderTests : BaseTaskEditViewModelTest() {
setup(task)
viewModel.setDueDate(
Task.createDueDate(
createDueDate(
Task.URGENCY_SPECIFIC_DAY_TIME,
currentTimeMillis()
)

@ -1,6 +1,6 @@
package org.tasks.ui.editviewmodel
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking

@ -0,0 +1,161 @@
package org.tasks.ui.editviewmodel
import com.todoroo.astrid.core.BuiltInFilterExposer
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory
import org.tasks.data.dao.DeletionDao
import org.tasks.data.dao.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.ui.TaskListViewModel
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class TaskListViewModelTest : InjectingTestCase() {
private lateinit var viewModel: TaskListViewModel
@Inject lateinit var preferences: Preferences
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var deletionDao: DeletionDao
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var inventory: Inventory
@Inject lateinit var firebase: Firebase
@Before
override fun setUp() {
super.setUp()
viewModel = TaskListViewModel(
context = context,
preferences = preferences,
taskDao = taskDao,
deletionDao = deletionDao,
taskDeleter = taskDeleter,
localBroadcastManager = localBroadcastManager,
inventory = inventory,
firebase = firebase,
)
viewModel.setFilter(BuiltInFilterExposer.getMyTasksFilter(context.resources))
}
@Test
fun clearCompletedTask() = runBlocking {
val task = taskDao.createNew(
Task(completionDate = currentTimeMillis())
)
clearCompleted()
assertTrue(taskDao.fetch(task)!!.isDeleted)
}
@Test
fun dontDeleteTaskWithRecurringParent() = runBlocking {
val parent = taskDao.createNew(
Task(
recurrence = "RRULE:FREQ=DAILY;INTERVAL=1"
)
)
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
clearCompleted()
assertFalse(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun dontDeleteTaskWithRecurringGrandparent() = runBlocking {
val grandparent = taskDao.createNew(
Task(recurrence = "RRULE:FREQ=DAILY;INTERVAL=1")
)
val parent = taskDao.createNew(
Task(parent = grandparent)
)
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
clearCompleted()
assertFalse(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearGrandchildWithNoRecurringAncestors() = runBlocking {
val grandparent = taskDao.createNew(Task())
val parent = taskDao.createNew(
Task(parent = grandparent)
)
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearGrandchildWithCompletedRecurringAncestor() = runBlocking {
val grandparent = taskDao.createNew(
Task(
recurrence = "RRULE:FREQ=DAILY;INTERVAL=1",
completionDate = currentTimeMillis(),
)
)
val parent = taskDao.createNew(
Task(parent = grandparent)
)
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearHiddenSubtask() = runBlocking {
preferences.showCompleted = false
val parent = taskDao.createNew(Task())
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
private suspend fun clearCompleted() = viewModel.markDeleted(viewModel.getTasksToClear())
}

@ -1,7 +1,7 @@
package org.tasks.ui.editviewmodel
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.Task.Priority.Companion.HIGH
import org.tasks.data.entity.Task.Priority.Companion.HIGH
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking

@ -7,7 +7,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.dao.CaldavDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.preferences.Preferences

@ -1,7 +1,7 @@
package org.tasks.caldav
import androidx.test.annotation.UiThreadTest
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -9,7 +9,7 @@ import org.junit.Assert.assertEquals
import org.junit.Test
import org.tasks.R
import org.tasks.billing.Inventory
import org.tasks.data.CaldavAccount
import org.tasks.data.entity.CaldavAccount
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@ -25,10 +25,8 @@ class CaldavSubscriptionTest : CaldavTest() {
inventory.clear()
inventory.add(emptyList())
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
id = caldavDao.insert(this)
}
account = CaldavAccount(uuid = UUIDHelper.newUUID())
.let { it.copy(id = caldavDao.insert(it)) }
synchronizer.sync(account)

@ -5,8 +5,8 @@ import com.facebook.flipper.plugins.network.NetworkReporter
import com.facebook.flipper.plugins.network.NetworkReporter.ResponseInfo
import com.google.api.client.http.*
import com.google.api.client.json.GenericJson
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.IOException
@ -18,12 +18,12 @@ internal class FlipperHttpInterceptor<T>(private val plugin: NetworkFlipperPlugi
private set
override fun intercept(request: HttpRequest) {
plugin.reportRequest(toRequestInfo(request, DateUtilities.now()))
plugin.reportRequest(toRequestInfo(request, currentTimeMillis()))
}
@Throws(IOException::class)
override fun interceptResponse(response: HttpResponse) {
plugin.reportResponse(toResponseInfo(response, DateUtilities.now()))
plugin.reportResponse(toResponseInfo(response, currentTimeMillis()))
}
@Throws(IOException::class)

@ -5,7 +5,6 @@ import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import at.bitfire.cert4android.CustomCertManager.Companion.resetCertificates
import com.todoroo.andlib.utility.DateUtilities.now
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.R
@ -14,6 +13,7 @@ import org.tasks.billing.Inventory
import org.tasks.extensions.Context.toast
import org.tasks.injection.InjectingPreferenceFragment
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.min
@ -62,7 +62,7 @@ class Debug : InjectingPreferenceFragment() {
findPreference(R.string.debug_clear_hints).setOnPreferenceClickListener {
preferences.installDate =
min(preferences.installDate, now() - TimeUnit.DAYS.toMillis(14))
min(preferences.installDate, currentTimeMillis() - TimeUnit.DAYS.toMillis(14))
preferences.lastSubscribeRequest = 0L
preferences.lastReviewRequest = 0L
preferences.shownBeastModeHint = false

@ -7,11 +7,11 @@ import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.todoroo.andlib.utility.DateUtilities.now
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.jobs.WorkManager
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -64,14 +64,14 @@ class Firebase @Inject constructor(
}
private val installCooldown: Boolean
get() = preferences.installDate + days("install_cooldown", 14L) > now()
get() = preferences.installDate + days("install_cooldown", 14L) > currentTimeMillis()
val reviewCooldown: Boolean
get() = installCooldown || preferences.lastReviewRequest + days("review_cooldown", 30L) > now()
get() = installCooldown || preferences.lastReviewRequest + days("review_cooldown", 30L) > currentTimeMillis()
val subscribeCooldown: Boolean
get() = installCooldown
|| preferences.lastSubscribeRequest + days("subscribe_cooldown", 30L) > now()
|| preferences.lastSubscribeRequest + days("subscribe_cooldown", 30L) > currentTimeMillis()
val moreOptionsBadge: Boolean
get() = remoteConfig?.getBoolean("more_options_badge") ?: false

@ -13,10 +13,11 @@ import com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.Purchase.PurchaseState
import com.android.billingclient.api.Purchase.PurchasesResult
import com.android.billingclient.api.PurchasesResult
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.SkuDetailsParams
import com.android.billingclient.api.consumePurchase
import com.android.billingclient.api.queryPurchasesAsync
import com.android.billingclient.api.querySkuDetails
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
@ -48,8 +49,8 @@ class BillingClientImpl(
override suspend fun queryPurchases(throwError: Boolean) = try {
executeServiceRequest {
withContext(Dispatchers.IO + NonCancellable) {
val subs = billingClient.queryPurchases(SkuType.SUBS)
val iaps = billingClient.queryPurchases(SkuType.INAPP)
val subs = billingClient.queryPurchasesAsync(SkuType.SUBS)
val iaps = billingClient.queryPurchasesAsync(SkuType.INAPP)
if (subs.success || iaps.success) {
withContext(Dispatchers.Main) {
inventory.clear()
@ -198,7 +199,7 @@ class BillingClientImpl(
const val STATE_PURCHASED = PurchaseState.PURCHASED
private val PurchasesResult.success: Boolean
get() = responseCode == BillingResponseCode.OK
get() = billingResult.responseCode == BillingResponseCode.OK
private val BillingResult.success: Boolean
get() = responseCode == BillingResponseCode.OK
@ -231,6 +232,6 @@ class BillingClientImpl(
get() = billingResult.responseCodeString
private val PurchasesResult.purchases: List<com.android.billingclient.api.Purchase>
get() = purchasesList ?: emptyList()
get() = purchasesList
}
}

@ -1,16 +1,23 @@
package org.tasks.billing
import com.android.billingclient.api.Purchase
import com.google.gson.GsonBuilder
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.tasks.billing.BillingClientImpl.Companion.STATE_PURCHASED
import java.util.regex.Pattern
class Purchase(private val purchase: Purchase) {
constructor(json: String?) : this(GsonBuilder().create().fromJson<Purchase>(json, Purchase::class.java))
constructor(json: String) : this(
Json.parseToJsonElement(json).jsonObject.let {
Purchase(it["zza"]!!.jsonPrimitive.content, it["zzb"]!!.jsonPrimitive.content)
}
)
fun toJson(): String {
return GsonBuilder().create().toJson(purchase)
return Json.encodeToString(mapOf("zza" to purchase.originalJson, "zzb" to purchase.signature))
}
override fun toString(): String {

@ -5,11 +5,11 @@ import android.content.Context
import android.content.Intent
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofencingEvent
import com.todoroo.andlib.utility.DateUtilities
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.Notifier
import org.tasks.data.LocationDao
import org.tasks.data.dao.LocationDao
import org.tasks.injection.InjectingJobIntentService
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import timber.log.Timber
import javax.inject.Inject
@ -45,9 +45,9 @@ class GoogleGeofenceTransitionIntentService : InjectingJobIntentService() {
return
}
val geofences = if (arrival) {
locationDao.getArrivalGeofences(place.uid!!, DateUtilities.now())
locationDao.getArrivalGeofences(place.uid!!, currentTimeMillis())
} else {
locationDao.getDepartureGeofences(place.uid!!, DateUtilities.now())
locationDao.getDepartureGeofences(place.uid!!, currentTimeMillis())
}
notifier.triggerNotifications(place.id, geofences, arrival)
} catch (e: Exception) {

@ -10,7 +10,7 @@ import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.*
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.data.Place
import org.tasks.data.entity.Place
import org.tasks.location.MapFragment.MapFragmentCallback
import javax.inject.Inject

@ -12,7 +12,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tasks.data.MergedGeofence
import org.tasks.data.Place
import org.tasks.data.entity.Place
import javax.inject.Inject
import kotlin.coroutines.suspendCoroutine

@ -1,17 +1,19 @@
package org.tasks.play
import android.app.Activity
import android.content.Context
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability.getInstance
import com.google.android.play.core.ktx.launchReview
import com.google.android.play.core.ktx.requestReview
import com.google.android.play.core.review.ReviewManagerFactory
import com.todoroo.andlib.utility.DateUtilities.now
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.analytics.Firebase
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import javax.inject.Inject
class PlayServices @Inject constructor(
@ -22,17 +24,17 @@ class PlayServices @Inject constructor(
fun isAvailable() =
getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS
suspend fun requestReview(activity: Activity) {
fun requestReview(activity: ComponentActivity) = activity.lifecycleScope.launch {
if (firebase.reviewCooldown) {
return
return@launch
}
try {
with(ReviewManagerFactory.create(context)) {
val request = requestReview()
launchReview(activity, request)
preferences.lastReviewRequest = now()
firebase.logEvent(R.string.event_request_review)
}
preferences.lastReviewRequest = currentTimeMillis()
firebase.logEvent(R.string.event_request_review)
} catch (e: Exception) {
firebase.reportException(e)
}

@ -310,12 +310,15 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/scrollable_widget_provider_info"/>
<meta-data
android:name="com.samsung.android.appwidget.provider"
android:resource="@xml/samsung_scrollable_flex_window_widget_meta_info"/>
</receiver>
<!-- ======================================================== Services = -->
<service
android:name=".widget.ScrollableWidgetUpdateService"
android:name=".widget.TasksWidgetAdapter"
android:permission="android.permission.BIND_REMOTEVIEWS"/>
<service
@ -436,12 +439,6 @@
android:name=".activities.PlaceSettingsActivity"
android:theme="@style/Tasks" />
<activity
android:name="com.todoroo.astrid.gcal.CalendarReminderActivity"
android:theme="@style/TasksDialog"/>
<receiver android:name="com.todoroo.astrid.gcal.CalendarAlarmReceiver"/>
<activity android:name=".activities.GoogleTaskListSettingsActivity"/>
<activity
@ -496,12 +493,6 @@
android:name=".scheduling.NotificationSchedulerIntentService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
<receiver android:name=".scheduling.CalendarNotificationIntentService$Broadcast"/>
<service
android:exported="false"
android:name=".scheduling.CalendarNotificationIntentService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
<receiver android:name=".notifications.NotificationClearedReceiver"/>
<service
@ -541,10 +532,6 @@
android:value="org.tasks.dashclock.DashClockSettings"/>
</service>
<service
android:exported="false"
android:name=".jobs.NotificationService"/>
<activity
android:exported="true"
android:name=".dashclock.DashClockSettings"/>
@ -610,6 +597,8 @@
</intent-filter>
</receiver>
<receiver android:name="org.tasks.jobs.NotificationReceiver" />
<activity
android:name=".auth.MicrosoftAuthenticationActivity"
android:theme="@style/TranslucentDialog"/>

@ -1,8 +0,0 @@
package com.todoroo.andlib.data
import com.todoroo.andlib.sql.Field
class Property internal constructor(val name: String?, expression: String) : Field(expression) {
constructor(table: Table, columnName: String) : this(columnName, "${table.name()}.$columnName")
}

@ -6,20 +6,17 @@
package com.todoroo.andlib.utility;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Looper;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import org.tasks.BuildConfig;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@ -84,12 +81,12 @@ public class AndroidUtilities {
result.append(SERIALIZATION_SEPARATOR);
}
public static Map<String, Object> mapFromSerializedString(String string) {
public static Map<String, Serializable> mapFromSerializedString(String string) {
if (string == null) {
return new HashMap<>();
}
Map<String, Object> result = new HashMap<>();
Map<String, Serializable> result = new HashMap<>();
fromSerialized(
string,
result,
@ -143,8 +140,12 @@ public class AndroidUtilities {
return !atLeastOreo();
}
public static boolean preS() {
return !atLeastS();
}
public static boolean preTiramisu() {
return VERSION.SDK_INT < VERSION_CODES.TIRAMISU;
return !atLeastTiramisu();
}
public static boolean preUpsideDownCake() {
@ -159,6 +160,10 @@ public class AndroidUtilities {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public static boolean atLeastOreoMR1() {
return Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1;
}
public static boolean atLeastP() {
return VERSION.SDK_INT >= Build.VERSION_CODES.P;
}
@ -195,38 +200,6 @@ public class AndroidUtilities {
return Thread.currentThread() == Looper.getMainLooper().getThread();
}
/** Capitalize the first character */
public static String capitalize(String string) {
return string.substring(0, 1).toUpperCase() + string.substring(1);
}
public static void hideKeyboard(Activity activity) {
try {
View currentFocus = activity.getCurrentFocus();
if (currentFocus != null) {
InputMethodManager inputMethodManager =
(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
currentFocus.clearFocus();
}
} catch (Exception e) {
Timber.e(e);
}
}
/**
* Dismiss the keyboard if it is displayed by any of the listed views
*
* @param views - a list of views that might potentially be displaying the keyboard
*/
public static void hideSoftInputForViews(Context context, View... views) {
InputMethodManager imm =
(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
for (View v : views) {
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
interface SerializedPut<T> {
void put(T object, String key, char type, String value) throws NumberFormatException;

@ -7,14 +7,13 @@
package com.todoroo.andlib.utility;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.content.Context;
import android.text.format.DateFormat;
import androidx.annotation.Nullable;
import com.todoroo.astrid.data.Task;
import org.tasks.data.entity.Task;
import org.tasks.BuildConfig;
import org.tasks.R;
@ -38,11 +37,6 @@ public class DateUtilities {
static Boolean is24HourOverride = null;
/** Returns unixtime for current time */
public static long now() {
return currentTimeMillis();
}
/* ======================================================================
* =========================================================== formatters
* ====================================================================== */

@ -5,68 +5,74 @@
*/
package com.todoroo.astrid.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.mandatorySystemGestures
import androidx.core.content.IntentCompat.getParcelableExtra
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.activity.TaskEditFragment.Companion.newTaskEditFragment
import com.todoroo.astrid.activity.TaskListFragment.TaskListFragmentCallbackHandler
import com.todoroo.astrid.adapter.SubheaderClickHandler
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.service.TaskCreator
import com.todoroo.astrid.timers.TimerControlSet.TimerControlSetCallback
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.activities.TagSettingsActivity
import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory
import org.tasks.data.AlarmDao
import org.tasks.data.LocationDao
import org.tasks.data.Place
import org.tasks.data.TagDataDao
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.drawer.TasksMenu
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.LocationDao
import org.tasks.data.entity.Place
import org.tasks.data.dao.TagDataDao
import org.tasks.data.entity.Task
import org.tasks.data.getLocation
import org.tasks.databinding.TaskListActivityBinding
import org.tasks.dialogs.SortSettingsActivity
import org.tasks.dialogs.NewFilterDialog
import org.tasks.dialogs.WhatsNewDialog
import org.tasks.extensions.Context.nightMode
import org.tasks.extensions.hideKeyboard
import org.tasks.filters.FilterProvider
import org.tasks.filters.PlaceFilter
import org.tasks.intents.TaskIntents.getTaskListIntent
import org.tasks.location.LocationPickerActivity
import org.tasks.play.PlayServices
import org.tasks.location.LocationPickerActivity.Companion.EXTRA_PLACE
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider
import org.tasks.themes.Theme
import org.tasks.themes.ThemeColor
import org.tasks.ui.EmptyTaskEditFragment.Companion.newEmptyTaskEditFragment
import org.tasks.ui.MainActivityEvent
import org.tasks.ui.MainActivityEventBus
import org.tasks.ui.NavigationDrawerFragment
import org.tasks.ui.NavigationDrawerFragment.Companion.newNavigationDrawer
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, TimerControlSetCallback {
class MainActivity : AppCompatActivity() {
@Inject lateinit var preferences: Preferences
@Inject lateinit var defaultFilterProvider: DefaultFilterProvider
@Inject lateinit var theme: Theme
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var taskCreator: TaskCreator
@Inject lateinit var inventory: Inventory
@Inject lateinit var colorProvider: ColorProvider
@ -74,15 +80,19 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, Timer
@Inject lateinit var tagDataDao: TagDataDao
@Inject lateinit var alarmDao: AlarmDao
@Inject lateinit var eventBus: MainActivityEventBus
@Inject lateinit var playServices: PlayServices
@Inject lateinit var firebase: Firebase
private val viewModel: MainActivityViewModel by viewModels()
private var currentNightMode = 0
private var currentPro = false
private var filter: Filter? = null
private var actionMode: ActionMode? = null
private lateinit var binding: TaskListActivityBinding
private val settingsRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
recreate()
}
/** @see android.app.Activity.onCreate
*/
override fun onCreate(savedInstanceState: Bundle?) {
@ -92,48 +102,135 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, Timer
currentPro = inventory.hasPro
binding = TaskListActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
if (savedInstanceState != null) {
filter = savedInstanceState.getParcelable(EXTRA_FILTER)
applyTheme()
}
logIntent("onCreate")
handleIntent()
binding.composeView.setContent {
val state = viewModel.state.collectAsStateLifecycleAware().value
if (state.drawerOpen) {
MdcTheme {
TasksMenu(
bottomPadding = WindowInsets.mandatorySystemGestures
.asPaddingValues()
.calculateBottomPadding(),
items = state.drawerItems,
begForMoney = state.begForMoney,
isTopAppBar = preferences.isTopAppBar,
setFilter = { viewModel.setFilter(it) },
toggleCollapsed = { viewModel.toggleCollapsed(it) },
addFilter = {
val rc = it.addIntentRc
if (rc == FilterProvider.REQUEST_NEW_FILTER) {
NewFilterDialog.newFilterDialog().show(
supportFragmentManager,
SubheaderClickHandler.FRAG_TAG_NEW_FILTER
)
} else {
val intent = it.addIntent ?: return@TasksMenu
startActivityForResult(intent, rc)
}
},
dismiss = { viewModel.setDrawerOpen(false) },
)
}
}
}
eventBus
.onEach(this::process)
.launchIn(lifecycleScope)
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
updateSystemBars(viewModel.state.value.filter)
}
}
viewModel
.state
.flowWithLifecycle(lifecycle)
.map { it.filter to it.task }
.distinctUntilChanged()
.onEach { (newFilter, task) ->
Timber.d("filter: $newFilter task: $task")
val existingTlf =
supportFragmentManager.findFragmentByTag(FRAG_TAG_TASK_LIST) as TaskListFragment?
val existingFilter = existingTlf?.getFilter()
val tlf = if (
existingFilter != null
&& existingFilter.areItemsTheSame(newFilter)
&& existingFilter == newFilter
// && check if manual sort changed
) {
existingTlf
} else {
clearUi()
TaskListFragment.newTaskListFragment(newFilter)
}
val existingTef =
supportFragmentManager.findFragmentByTag(FRAG_TAG_TASK_EDIT) as TaskEditFragment?
val transaction = supportFragmentManager.beginTransaction()
if (task == null) {
if (intent.finishAffinity) {
finishAffinity()
} else if (existingTef != null) {
if (intent.removeTask && intent.broughtToFront) {
moveTaskToBack(true)
}
hideKeyboard()
transaction
.replace(R.id.detail, newEmptyTaskEditFragment())
.runOnCommit {
if (isSinglePaneLayout) {
binding.master.visibility = View.VISIBLE
binding.detail.visibility = View.GONE
}
}
}
} else if (task != existingTef?.task) {
existingTef?.save(remove = false)
transaction
.replace(R.id.detail, newTaskEditFragment(task), FRAG_TAG_TASK_EDIT)
.runOnCommit {
if (isSinglePaneLayout) {
binding.detail.visibility = View.VISIBLE
binding.master.visibility = View.GONE
}
}
}
defaultFilterProvider.setLastViewedFilter(newFilter)
theme
.withThemeColor(getFilterColor(newFilter))
.applyToContext(this) // must happen before committing fragment
transaction
.replace(R.id.master, tlf, FRAG_TAG_TASK_LIST)
.runOnCommit { updateSystemBars(newFilter) }
.commit()
}
.launchIn(lifecycleScope)
}
private suspend fun process(event: MainActivityEvent) = when (event) {
is MainActivityEvent.OpenTask ->
onTaskListItemClicked(event.task)
is MainActivityEvent.RequestRating ->
playServices.requestReview(this)
private fun process(event: MainActivityEvent) = when (event) {
is MainActivityEvent.ClearTaskEditFragment ->
removeTaskEditFragment()
viewModel.setTask(null)
}
@Deprecated("Deprecated in Java")
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
NavigationDrawerFragment.REQUEST_SETTINGS -> recreate()
NavigationDrawerFragment.REQUEST_NEW_LIST ->
if (resultCode == RESULT_OK) {
data
?.getParcelableExtra<Filter>(OPEN_FILTER)
?.let { startActivity(getTaskListIntent(this, it)) }
}
NavigationDrawerFragment.REQUEST_NEW_PLACE ->
if (resultCode == RESULT_OK) {
data
?.getParcelableExtra<Place>(LocationPickerActivity.EXTRA_PLACE)
?.let { startActivity(getTaskListIntent(this, PlaceFilter(it))) }
REQUEST_NEW_LIST ->
if (resultCode == RESULT_OK && data != null) {
getParcelableExtra(data, OPEN_FILTER, Filter::class.java)?.let {
viewModel.setFilter(it)
}
}
TaskListFragment.REQUEST_SORT ->
if (resultCode == RESULT_OK) {
sortChanged(
reload = data?.getBooleanExtra(SortSettingsActivity.EXTRA_FORCE_RELOAD, false) ?: false,
groupChange = data?.getBooleanExtra(SortSettingsActivity.EXTRA_CHANGED_GROUP, false) ?: false,
)
REQUEST_NEW_PLACE ->
if (resultCode == RESULT_OK && data != null) {
getParcelableExtra(data, EXTRA_PLACE, Place::class.java)?.let {
viewModel.setFilter(PlaceFilter(it))
}
}
else ->
super.onActivityResult(requestCode, resultCode, data)
}
@ -142,201 +239,74 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, Timer
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
logIntent("onNewIntent")
handleIntent()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(EXTRA_FILTER, filter)
}
private fun clearUi() {
finishActionMode()
navigationDrawer?.dismiss()
actionMode?.finish()
actionMode = null
viewModel.setDrawerOpen(false)
}
private suspend fun getTaskToLoad(filter: Filter?): Task? {
val intent = intent
if (intent.isFromHistory) {
return null
}
if (intent.hasExtra(CREATE_TASK)) {
private suspend fun getTaskToLoad(filter: Filter?): Task? = when {
intent.isFromHistory -> null
intent.hasExtra(CREATE_TASK) -> {
val source = intent.getStringExtra(CREATE_SOURCE)
firebase.addTask(source ?: "unknown")
intent.removeExtra(CREATE_TASK)
intent.removeExtra(CREATE_SOURCE)
return taskCreator.createWithValues(filter, "")
taskCreator.createWithValues(filter, "")
}
if (intent.hasExtra(OPEN_TASK)) {
val task: Task? = intent.getParcelableExtra(OPEN_TASK)
intent.hasExtra(OPEN_TASK) -> {
val task = getParcelableExtra(intent, OPEN_TASK, Task::class.java)
intent.removeExtra(OPEN_TASK)
return task
task
}
return null
}
private fun openTask(filter: Filter?) = lifecycleScope.launch {
val task = getTaskToLoad(filter)
when {
task != null -> onTaskListItemClicked(task)
taskEditFragment == null -> hideDetailFragment()
else -> showDetailFragment()
}
else -> null
}
private fun handleIntent() {
val intent = intent
val openFilter = intent.getFilter
val loadFilter = intent.getFilterString
val openTask = !intent.isFromHistory
&& (intent.hasExtra(OPEN_TASK) || intent.hasExtra(CREATE_TASK))
val tef = taskEditFragment
private fun logIntent(caller: String) {
if (BuildConfig.DEBUG) {
Timber.d(
"""
**********
broughtToFront: ${intent.broughtToFront}
isFromHistory: ${intent.isFromHistory}
flags: ${intent.flagsToString}
OPEN_FILTER: ${openFilter?.let { "${it.listingTitle}: $it" }}
LOAD_FILTER: $loadFilter
OPEN_TASK: ${intent.getParcelableExtra<Task>(OPEN_TASK)}
CREATE_TASK: ${intent.hasExtra(CREATE_TASK)}
taskListFragment: ${taskListFragment?.getFilter()?.let { "${it.listingTitle}: $it" }}
taskEditFragment: ${taskEditFragment?.editViewModel?.task}
**********"""
Timber.d("""
$caller
**********
broughtToFront: ${intent.broughtToFront}
isFromHistory: ${intent.isFromHistory}
flags: ${intent.flagsToString}
OPEN_FILTER: ${getParcelableExtra(intent, OPEN_FILTER, Filter::class.java)?.let { "${it.title}: $it" }}
LOAD_FILTER: ${intent.getStringExtra(LOAD_FILTER)}
OPEN_TASK: ${getParcelableExtra(intent, OPEN_TASK, Task::class.java)}
CREATE_TASK: ${intent.hasExtra(CREATE_TASK)}
**********""".trimIndent()
)
}
if (!openTask && (openFilter != null || !loadFilter.isNullOrBlank())) {
tef?.let {
lifecycleScope.launch {
it.save()
}
}
}
if (!loadFilter.isNullOrBlank() || openFilter == null && filter == null) {
lifecycleScope.launch {
val filter = if (loadFilter.isNullOrBlank()) {
defaultFilterProvider.getStartupFilter()
} else {
defaultFilterProvider.getFilterFromPreference(loadFilter)
}
clearUi()
if (isSinglePaneLayout) {
if (openTask) {
setFilter(filter)
openTask(filter)
} else {
openTaskListFragment(filter, true)
}
} else {
openTaskListFragment(filter, true)
openTask(filter)
}
}
} else if (openFilter != null) {
clearUi()
if (isSinglePaneLayout) {
if (openTask) {
setFilter(openFilter)
openTask(openFilter)
} else {
openTaskListFragment(openFilter, true)
}
} else {
openTaskListFragment(openFilter, true)
openTask(openFilter)
}
} else {
val existing = taskListFragment
val target = if (existing == null || existing.getFilter() !== filter) {
TaskListFragment.newTaskListFragment(applicationContext, filter)
} else {
existing
}
if (isSinglePaneLayout) {
if (openTask || tef != null) {
openTask(filter)
} else {
openTaskListFragment(filter, false)
}
} else {
openTaskListFragment(target, false)
openTask(filter)
}
}
if (intent.hasExtra(TOKEN_CREATE_NEW_LIST_NAME)) {
val listName = intent.getStringExtra(TOKEN_CREATE_NEW_LIST_NAME)
intent.removeExtra(TOKEN_CREATE_NEW_LIST_NAME)
val activityIntent = Intent(this@MainActivity, TagSettingsActivity::class.java)
activityIntent.putExtra(TagSettingsActivity.TOKEN_AUTOPOPULATE_NAME, listName)
startActivityForResult(activityIntent, NavigationDrawerFragment.REQUEST_NEW_LIST)
}
}
private fun showDetailFragment() {
if (isSinglePaneLayout) {
binding.detail.visibility = View.VISIBLE
binding.master.visibility = View.GONE
private fun handleIntent() {
lifecycleScope.launch {
val filter = intent.getFilter
?: intent.getFilterString?.let { defaultFilterProvider.getFilterFromPreference(it) }
?: viewModel.state.value.filter
val task = getTaskToLoad(filter)
viewModel.setFilter(filter = filter, task = task)
}
}
private fun hideDetailFragment() {
supportFragmentManager
.beginTransaction()
.replace(R.id.detail, newEmptyTaskEditFragment())
.runOnCommit {
if (isSinglePaneLayout) {
binding.master.visibility = View.VISIBLE
binding.detail.visibility = View.GONE
}
}
.commit()
}
private fun setFilter(newFilter: Filter?) {
filter = newFilter
applyTheme()
}
private fun openTaskListFragment(filter: Filter?, force: Boolean = false) {
openTaskListFragment(TaskListFragment.newTaskListFragment(applicationContext, filter), force)
}
private fun openTaskListFragment(taskListFragment: TaskListFragment, force: Boolean) {
AndroidUtilities.assertMainThread()
if (supportFragmentManager.isDestroyed) {
return
}
val newFilter = taskListFragment.getFilter()
if (filter != null
&& !force
&& filter!!.areItemsTheSame(newFilter)
&& filter!!.areContentsTheSame(newFilter)) {
return
private fun updateSystemBars(filter: Filter) {
with (getFilterColor(filter)) {
applyToNavigationBar(this@MainActivity)
applyTaskDescription(this@MainActivity, filter.title ?: getString(R.string.app_name))
}
filter = newFilter
defaultFilterProvider.lastViewedFilter = newFilter
applyTheme()
supportFragmentManager
.beginTransaction()
.replace(R.id.master, taskListFragment, FRAG_TAG_TASK_LIST)
.commitNowAllowingStateLoss()
}
private fun applyTheme() {
val filterColor = filterColor
filterColor.applyToNavigationBar(this)
filterColor.applyTaskDescription(this, filter?.listingTitle ?: getString(R.string.app_name))
theme.withThemeColor(filterColor).applyToContext(this)
}
private val filterColor: ThemeColor
get() = if (filter != null && filter!!.tint != 0) colorProvider.getThemeColor(filter!!.tint, true) else theme.themeColor
private val navigationDrawer: NavigationDrawerFragment?
get() = supportFragmentManager.findFragmentByTag(FRAG_TAG_NAV_DRAWER) as? NavigationDrawerFragment
private fun getFilterColor(filter: Filter) =
if (filter.tint != 0)
colorProvider.getThemeColor(filter.tint, true)
else
theme.themeColor
override fun onResume() {
super.onResume()
@ -355,107 +325,37 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, Timer
}
}
override suspend fun onTaskListItemClicked(task: Task?) {
private suspend fun newTaskEditFragment(task: Task): TaskEditFragment {
AndroidUtilities.assertMainThread()
if (task == null) {
return
}
taskEditFragment?.save(remove = false)
clearUi()
coroutineScope {
val freshTask = async { if (task.isNew) task else taskDao.fetch(task.id) ?: task }
val list = async { defaultFilterProvider.getList(task) }
val location = async { locationDao.getLocation(task, preferences) }
val tags = async { tagDataDao.getTags(task) }
val alarms = async { alarmDao.getAlarms(task) }
val fragment = withContext(Dispatchers.Default) {
return coroutineScope {
withContext(Dispatchers.Default) {
val freshTask = async { if (task.isNew) task else taskDao.fetch(task.id) ?: task }
val list = async { defaultFilterProvider.getList(task) }
val location = async { locationDao.getLocation(task, preferences) }
val tags = async { tagDataDao.getTags(task) }
val alarms = async { alarmDao.getAlarms(task) }
newTaskEditFragment(
freshTask.await(),
list.await(),
location.await(),
tags.await(),
alarms.await(),
freshTask.await(),
list.await(),
location.await(),
tags.await(),
alarms.await(),
)
}
supportFragmentManager.beginTransaction()
.replace(R.id.detail, fragment, TaskEditFragment.TAG_TASKEDIT_FRAGMENT)
.runOnCommit { showDetailFragment() }
.commitNowAllowingStateLoss()
}
}
override fun onNavigationIconClicked() {
hideKeyboard()
newNavigationDrawer(filter).show(supportFragmentManager, FRAG_TAG_NAV_DRAWER)
}
private val taskListFragment: TaskListFragment?
get() = supportFragmentManager.findFragmentByTag(FRAG_TAG_TASK_LIST) as TaskListFragment?
private val taskEditFragment: TaskEditFragment?
get() = supportFragmentManager.findFragmentByTag(TaskEditFragment.TAG_TASKEDIT_FRAGMENT) as TaskEditFragment?
override suspend fun stopTimer(): Task {
return taskEditFragment!!.stopTimer()
}
override suspend fun startTimer(): Task {
return taskEditFragment!!.startTimer()
}
private val isSinglePaneLayout: Boolean
get() = !resources.getBoolean(R.bool.two_pane_layout)
private fun removeTaskEditFragment() {
val removeTask = intent.removeTask
val finishAffinity = intent.finishAffinity
if (finishAffinity || taskListFragment == null) {
finishAffinity()
} else {
if (removeTask && intent.broughtToFront) {
moveTaskToBack(true)
}
hideKeyboard()
hideDetailFragment()
taskListFragment?.let {
setFilter(it.getFilter())
it.loadTaskListContent()
}
}
}
private fun hideKeyboard() {
val view = currentFocus
if (view != null) {
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
}
}
private fun sortChanged(reload: Boolean, groupChange: Boolean) {
if (groupChange) {
taskListFragment?.clearCollapsed()
}
localBroadcastManager.broadcastRefresh()
if (reload) {
openTaskListFragment(filter, true)
}
}
override fun onSupportActionModeStarted(mode: ActionMode) {
super.onSupportActionModeStarted(mode)
actionMode = mode
}
private fun finishActionMode() {
actionMode?.finish()
actionMode = null
}
companion object {
/** For indicating the new list screen should be launched at fragment setup time */
const val TOKEN_CREATE_NEW_LIST_NAME = "newListName" // $NON-NLS-1$
const val OPEN_FILTER = "open_filter" // $NON-NLS-1$
const val LOAD_FILTER = "load_filter"
const val CREATE_TASK = "open_task" // $NON-NLS-1$
@ -465,16 +365,17 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler, Timer
const val FINISH_AFFINITY = "finish_affinity"
private const val FRAG_TAG_TASK_LIST = "frag_tag_task_list"
private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new"
private const val FRAG_TAG_NAV_DRAWER = "frag_tag_nav_drawer"
private const val EXTRA_FILTER = "extra_filter"
private const val FRAG_TAG_TASK_EDIT = "frag_tag_task_edit"
private const val FLAG_FROM_HISTORY
= Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
const val REQUEST_NEW_LIST = 10100
const val REQUEST_NEW_PLACE = 10104
val Intent.getFilter: Filter?
get() = if (isFromHistory) {
null
} else {
getParcelableExtra<Filter?>(OPEN_FILTER)?.let {
getParcelableExtra(this, OPEN_FILTER, Filter::class.java)?.let {
removeExtra(OPEN_FILTER)
it
}

@ -0,0 +1,205 @@
package com.todoroo.astrid.activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.todoroo.astrid.activity.MainActivity.Companion.LOAD_FILTER
import com.todoroo.astrid.activity.MainActivity.Companion.OPEN_FILTER
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.CustomFilter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.billing.Inventory
import org.tasks.compose.drawer.DrawerItem
import org.tasks.data.dao.CaldavDao
import org.tasks.data.NO_COUNT
import org.tasks.data.entity.Task
import org.tasks.data.dao.TaskDao
import org.tasks.data.count
import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.filters.PlaceFilter
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider
import org.tasks.themes.CustomIcons
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class MainActivityViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val defaultFilterProvider: DefaultFilterProvider,
private val filterProvider: FilterProvider,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val inventory: Inventory,
private val colorProvider: ColorProvider,
private val caldavDao: CaldavDao,
private val preferences: Preferences,
) : ViewModel() {
data class State(
val begForMoney: Boolean = false,
val filter: Filter,
val task: Task? = null,
val drawerOpen: Boolean = false,
val drawerItems: ImmutableList<DrawerItem> = persistentListOf(),
)
private val _state = MutableStateFlow(
State(
filter = savedStateHandle.get<Filter>(OPEN_FILTER)
?: savedStateHandle.get<String>(LOAD_FILTER)?.let {
runBlocking { defaultFilterProvider.getFilterFromPreference(it) }
}
?: runBlocking { defaultFilterProvider.getStartupFilter() },
begForMoney = if (IS_GENERIC) !inventory.hasTasksAccount else !inventory.hasPro,
)
)
val state = _state.asStateFlow()
private val refreshReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
LocalBroadcastManager.REFRESH,
LocalBroadcastManager.REFRESH_LIST -> updateFilters()
}
}
}
suspend fun resetFilter() {
setFilter(defaultFilterProvider.getDefaultOpenFilter())
}
fun setFilter(
filter: Filter,
task: Task? = null,
) {
if (filter == _state.value.filter && task == null) {
return
}
_state.update {
it.copy(
filter = filter,
task = task,
)
}
updateFilters()
defaultFilterProvider.setLastViewedFilter(filter)
}
fun setDrawerOpen(open: Boolean) {
_state.update { it.copy(drawerOpen = open) }
}
init {
localBroadcastManager.registerRefreshListReceiver(refreshReceiver)
updateFilters()
}
override fun onCleared() {
localBroadcastManager.unregisterReceiver(refreshReceiver)
}
fun updateFilters() = viewModelScope.launch(Dispatchers.Default) {
val selected = state.value.filter
filterProvider
.drawerItems()
.map { item ->
when (item) {
is Filter ->
DrawerItem.Filter(
title = item.title ?: "",
icon = getIcon(item),
color = getColor(item),
count = item.count.takeIf { it != NO_COUNT } ?: try {
taskDao.count(item)
} catch (e: Exception) {
Timber.e(e)
0
},
selected = item.areItemsTheSame(selected),
shareCount = if (item is CaldavFilter) item.principals else 0,
type = { item },
)
is NavigationDrawerSubheader ->
DrawerItem.Header(
title = item.title ?: "",
collapsed = item.isCollapsed,
hasError = item.error,
canAdd = item.addIntent != null,
type = { item },
)
else -> throw IllegalArgumentException()
}
}
.let { filters -> _state.update { it.copy(drawerItems = filters.toPersistentList()) } }
}
private fun getColor(filter: Filter): Int {
if (filter.tint != 0) {
val color = colorProvider.getThemeColor(filter.tint, true)
if (color.isFree || inventory.purchasedThemes()) {
return color.primaryColor
}
}
return 0
}
private fun getIcon(filter: Filter): Int {
if (filter.icon < 1000 || filter.icon == CustomIcons.PLACE || inventory.hasPro) {
val icon = CustomIcons.getIconResId(filter.icon)
if (icon != null) {
return icon
}
}
return when (filter) {
is TagFilter -> R.drawable.ic_outline_label_24px
is GtasksFilter,
is CaldavFilter -> R.drawable.ic_list_24px
is CustomFilter -> R.drawable.ic_outline_filter_list_24px
is PlaceFilter -> R.drawable.ic_outline_place_24px
else -> filter.icon
}
}
fun toggleCollapsed(subheader: NavigationDrawerSubheader) = viewModelScope.launch {
val collapsed = !subheader.isCollapsed
when (subheader.subheaderType) {
NavigationDrawerSubheader.SubheaderType.PREFERENCE -> {
preferences.setBoolean(subheader.id.toInt(), collapsed)
localBroadcastManager.broadcastRefreshList()
}
NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS,
NavigationDrawerSubheader.SubheaderType.CALDAV,
NavigationDrawerSubheader.SubheaderType.TASKS,
NavigationDrawerSubheader.SubheaderType.ETESYNC -> {
caldavDao.setCollapsed(subheader.id, collapsed)
localBroadcastManager.broadcastRefreshList()
}
}
}
fun setTask(task: Task?) {
_state.update { it.copy(task = task) }
}
}

@ -6,13 +6,13 @@ import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskCreator
import com.todoroo.astrid.utility.Constants
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.analytics.Firebase
import org.tasks.data.TaskAttachment
import org.tasks.data.entity.TaskAttachment
import org.tasks.files.FileHelper
import org.tasks.intents.TaskIntents
import org.tasks.preferences.Preferences

@ -10,7 +10,6 @@ import android.content.Context
import android.content.Intent
import android.graphics.Paint
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
@ -36,6 +35,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.os.BundleCompat
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@ -43,16 +43,15 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.AndroidUtilities.atLeastOreoMR1
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.files.FilesControlSet
import com.todoroo.astrid.repeats.RepeatControlSet
import com.todoroo.astrid.tags.TagsControlSet
import com.todoroo.astrid.timers.TimerControlSet
import com.todoroo.astrid.timers.TimerPlugin
import com.todoroo.astrid.ui.ReminderControlSet
import com.todoroo.astrid.ui.StartDateControlSet
import dagger.hilt.android.AndroidEntryPoint
@ -72,10 +71,10 @@ import org.tasks.compose.edit.DueDateRow
import org.tasks.compose.edit.InfoRow
import org.tasks.compose.edit.ListRow
import org.tasks.compose.edit.PriorityRow
import org.tasks.data.Alarm
import org.tasks.data.entity.Alarm
import org.tasks.data.Location
import org.tasks.data.TagData
import org.tasks.data.UserActivityDao
import org.tasks.data.entity.TagData
import org.tasks.data.dao.UserActivityDao
import org.tasks.databinding.FragmentTaskEditBinding
import org.tasks.databinding.TaskEditCalendarBinding
import org.tasks.databinding.TaskEditFilesBinding
@ -92,6 +91,7 @@ import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.FilterPicker.Companion.newFilterPicker
import org.tasks.dialogs.FilterPicker.Companion.setFilterPickerResultListener
import org.tasks.dialogs.Linkify
import org.tasks.extensions.hideKeyboard
import org.tasks.files.FileHelper
import org.tasks.fragments.TaskEditControlSetFragmentManager
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_CREATION
@ -101,6 +101,7 @@ import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_LIST
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_PRIORITY
import org.tasks.markdown.MarkdownProvider
import org.tasks.notifications.NotificationManager
import org.tasks.play.PlayServices
import org.tasks.preferences.Preferences
import org.tasks.ui.CalendarControlSet
import org.tasks.ui.ChipProvider
@ -125,12 +126,12 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@Inject lateinit var taskEditControlSetFragmentManager: TaskEditControlSetFragmentManager
@Inject lateinit var preferences: Preferences
@Inject lateinit var firebase: Firebase
@Inject lateinit var timerPlugin: TimerPlugin
@Inject lateinit var linkify: Linkify
@Inject lateinit var markdownProvider: MarkdownProvider
@Inject lateinit var taskEditEventBus: TaskEditEventBus
@Inject lateinit var locale: Locale
@Inject lateinit var chipProvider: ChipProvider
@Inject lateinit var playServices: PlayServices
val editViewModel: TaskEditViewModel by viewModels()
lateinit var binding: FragmentTaskEditBinding
@ -140,6 +141,9 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
activity?.recreate()
}
val task: Task?
get() = BundleCompat.getParcelable(requireArguments(), EXTRA_TASK, Task::class.java)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
requireActivity().onBackPressedDispatcher.addCallback(owner = viewLifecycleOwner) {
@ -151,6 +155,9 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
discardButtonClick()
}
}
if (atLeastOreoMR1()) {
activity?.setShowWhenLocked(preferences.showEditScreenWithoutUnlock)
}
binding = FragmentTaskEditBinding.inflate(inflater)
val view: View = binding.root
@ -306,6 +313,13 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
return view
}
override fun onDestroyView() {
super.onDestroyView()
if (atLeastOreoMR1()) {
activity?.setShowWhenLocked(false)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
taskEditEventBus
.onEach(this::process)
@ -358,7 +372,7 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
override fun onMenuItemClick(item: MenuItem): Boolean {
AndroidUtilities.hideKeyboard(activity)
activity?.hideKeyboard()
if (item.itemId == R.id.menu_delete) {
deleteButtonClick()
return true
@ -369,33 +383,11 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
return false
}
suspend fun stopTimer(): Task {
val model = editViewModel.task
timerPlugin.stopTimer(model)
val elapsedTime = DateUtils.formatElapsedTime(model.elapsedSeconds.toLong())
editViewModel.addComment(String.format(
"%s %s\n%s %s", // $NON-NLS-1$
getString(R.string.TEA_timer_comment_stopped),
DateUtilities.getTimeString(context, newDateTime()),
getString(R.string.TEA_timer_comment_spent),
elapsedTime),
null)
return model
suspend fun save(remove: Boolean = true) {
editViewModel.save(remove)
activity?.let { playServices.requestReview(it) }
}
suspend fun startTimer(): Task {
val model = editViewModel.task
timerPlugin.startTimer(model)
editViewModel.addComment(String.format(
"%s %s",
getString(R.string.TEA_timer_comment_started),
DateUtilities.getTimeString(context, newDateTime())),
null)
return model
}
suspend fun save(remove: Boolean = true) = editViewModel.save(remove)
private fun discardButtonClick() {
if (editViewModel.hasChanges()) {
dialogBuilder
@ -537,7 +529,6 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
companion object {
const val TAG_TASKEDIT_FRAGMENT = "taskedit_fragment"
const val EXTRA_TASK = "extra_task"
const val EXTRA_LIST = "extra_list"
const val EXTRA_LOCATION = "extra_location"

@ -6,6 +6,7 @@
package com.todoroo.astrid.activity
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -18,19 +19,23 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.ui.platform.LocalContext
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ShareCompat
import androidx.core.content.IntentCompat
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.core.view.setMargins
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
@ -38,58 +43,65 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.room.withTransaction
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.composethemeadapter.MdcTheme
import com.google.android.material.snackbar.Snackbar
import org.tasks.data.sql.Join
import org.tasks.data.sql.QueryTemplate
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.adapter.TaskAdapter
import com.todoroo.astrid.adapter.TaskAdapterProvider
import com.todoroo.astrid.api.AstridApiConstants.EXTRAS_OLD_DUE_DATE
import com.todoroo.astrid.api.AstridApiConstants.EXTRAS_TASK_ID
import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.CustomFilter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.FilterImpl
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.IdListFilter
import com.todoroo.astrid.api.SearchFilter
import com.todoroo.astrid.api.TagFilter
import com.todoroo.astrid.core.BuiltInFilterExposer
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.repeats.RepeatTaskHelper
import com.todoroo.astrid.service.TaskCompleter
import com.todoroo.astrid.service.TaskCreator
import com.todoroo.astrid.service.TaskDeleter
import com.todoroo.astrid.service.TaskDuplicator
import com.todoroo.astrid.service.TaskMover
import com.todoroo.astrid.timers.TimerPlugin
import com.todoroo.astrid.utility.Flags
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.ShortcutManager
import org.tasks.Tasks
import org.tasks.activities.FilterSettingsActivity
import org.tasks.activities.GoogleTaskListSettingsActivity
import org.tasks.activities.PlaceSettingsActivity
import org.tasks.activities.TagSettingsActivity
import org.tasks.analytics.Firebase
import org.tasks.billing.PurchaseActivity
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.compose.SubscriptionNagBanner
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.data.CaldavDao
import org.tasks.data.TagDataDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.db.Database
import org.tasks.data.entity.Tag
import org.tasks.data.dao.TagDataDao
import org.tasks.data.entity.Task
import org.tasks.data.TaskContainer
import org.tasks.data.listSettingsClass
import org.tasks.databinding.FragmentTaskListBinding
import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.data.db.SuspendDbUtils.chunkedMap
import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker
import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.FilterPicker.Companion.newFilterPicker
@ -99,24 +111,27 @@ import org.tasks.dialogs.SortSettingsActivity
import org.tasks.extensions.Context.openUri
import org.tasks.extensions.Context.toast
import org.tasks.extensions.Fragment.safeStartActivityForResult
import org.tasks.extensions.formatNumber
import org.tasks.extensions.hideKeyboard
import org.tasks.extensions.setOnQueryTextListener
import org.tasks.filters.PlaceFilter
import org.tasks.intents.TaskIntents
import org.tasks.markdown.MarkdownProvider
import org.tasks.preferences.Device
import org.tasks.preferences.Preferences
import org.tasks.sync.SyncAdapters
import org.tasks.tags.TagPickerActivity
import org.tasks.tasklist.DragAndDropRecyclerAdapter
import org.tasks.tasklist.SectionedDataSource
import org.tasks.tasklist.TaskViewHolder
import org.tasks.tasklist.ViewHolderFactory
import org.tasks.themes.ColorProvider
import org.tasks.themes.ThemeColor
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.ui.TaskEditEvent
import org.tasks.ui.TaskEditEventBus
import org.tasks.ui.TaskListEvent
import org.tasks.ui.TaskListEventBus
import org.tasks.ui.TaskListViewModel
import org.tasks.ui.TaskListViewModel.Companion.createSearchQuery
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
@ -129,7 +144,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
private val repeatConfirmationReceiver = RepeatConfirmationReceiver()
@Inject lateinit var syncAdapters: SyncAdapters
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var preferences: Preferences
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var taskCreator: TaskCreator
@ -152,23 +166,47 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
@Inject lateinit var repeatTaskHelper: RepeatTaskHelper
@Inject lateinit var taskListEventBus: TaskListEventBus
@Inject lateinit var taskEditEventBus: TaskEditEventBus
@Inject lateinit var database: Database
@Inject lateinit var markdown: MarkdownProvider
private val listViewModel: TaskListViewModel by viewModels()
private val mainViewModel: MainActivityViewModel by activityViewModels()
private lateinit var taskAdapter: TaskAdapter
private var recyclerAdapter: DragAndDropRecyclerAdapter? = null
private lateinit var filter: Filter
private var searchJob: Job? = null
private lateinit var search: MenuItem
private var searchQuery: String? = null
private var mode: ActionMode? = null
lateinit var themeColor: ThemeColor
private lateinit var callbacks: TaskListFragmentCallbackHandler
private lateinit var binding: FragmentTaskListBinding
private val onBackPressed = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
search.collapseActionView()
private val sortRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
result.data?.let { data ->
if (data.getBooleanExtra(SortSettingsActivity.EXTRA_FORCE_RELOAD, false)) {
activity?.recreate()
}
if (data.getBooleanExtra(SortSettingsActivity.EXTRA_CHANGED_GROUP, false)) {
listViewModel.clearCollapsed()
}
listViewModel.invalidate()
}
}
}
private val listSettingsRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != RESULT_OK) return@registerForActivityResult
val data = result.data ?: return@registerForActivityResult
when (data.action) {
ACTION_DELETED ->
mainViewModel.setFilter(BuiltInFilterExposer.getMyTasksFilter(resources))
ACTION_RELOAD ->
IntentCompat.getParcelableExtra(data, MainActivity.OPEN_FILTER, Filter::class.java)?.let {
mainViewModel.setFilter(it)
}
}
}
}
private fun process(event: TaskListEvent) = when (event) {
is TaskListEvent.TaskCreated ->
@ -194,17 +232,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
}
override fun onAttach(activity: Activity) {
super.onAttach(activity)
callbacks = activity as TaskListFragmentCallbackHandler
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val selectedTaskIds: List<Long> = taskAdapter.getSelected()
outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray())
outState.putString(EXTRA_SEARCH, searchQuery)
outState.putLongArray(EXTRA_COLLAPSED, taskAdapter.getCollapsed().toLongArray())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -215,15 +246,21 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
.launchIn(viewLifecycleOwner.lifecycleScope)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(onBackPressed)
}
@OptIn(ExperimentalAnimationApi::class)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
requireActivity().onBackPressedDispatcher.addCallback(owner = viewLifecycleOwner) {
if (search.isActionViewExpanded) {
search.collapseActionView()
} else {
requireActivity().finish()
if (!preferences.getBoolean(R.string.p_open_last_viewed_list, true)) {
runBlocking {
mainViewModel.resetFilter()
}
}
}
}
binding = FragmentTaskListBinding.inflate(inflater, container, false)
filter = getFilter()
val swipeRefreshLayout: SwipeRefreshLayout
@ -237,36 +274,34 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
fab.isVisible = filter.isWritable
}
themeColor = if (filter.tint != 0) colorProvider.getThemeColor(filter.tint, true) else defaultThemeColor
filter.setFilterQueryOverride(null)
(filter as? AstridOrderingFilter)?.filterOverride = null
// set up list adapters
taskAdapter = taskAdapterProvider.createTaskAdapter(filter)
taskAdapter.setCollapsed(savedInstanceState?.getLongArray(EXTRA_COLLAPSED))
if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(EXTRA_SEARCH)
}
listViewModel.setFilter((if (searchQuery == null) filter else createSearchFilter(searchQuery!!)))
listViewModel.setFilter(filter)
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context)
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
listViewModel.state.collect {
submitList(it.tasks)
if (it.tasks.isEmpty()) {
swipeRefreshLayout.visibility = View.GONE
emptyRefreshLayout.visibility = View.VISIBLE
} else {
swipeRefreshLayout.visibility = View.VISIBLE
emptyRefreshLayout.visibility = View.GONE
if (it.tasks is TaskListViewModel.TasksResults.Results) {
submitList(it.tasks.tasks)
if (it.tasks.tasks.isEmpty()) {
swipeRefreshLayout.visibility = View.GONE
emptyRefreshLayout.visibility = View.VISIBLE
} else {
swipeRefreshLayout.visibility = View.VISIBLE
emptyRefreshLayout.visibility = View.GONE
}
swipeRefreshLayout.isRefreshing = it.syncOngoing
emptyRefreshLayout.isRefreshing = it.syncOngoing
}
swipeRefreshLayout.isRefreshing = it.syncOngoing
emptyRefreshLayout.isRefreshing = it.syncOngoing
}
}
}
setupRefresh(swipeRefreshLayout)
setupRefresh(emptyRefreshLayout)
binding.toolbar.title = filter.listingTitle
binding.toolbar.title = filter.title
binding.appbarlayout.addOnOffsetChangedListener { _, verticalOffset ->
if (verticalOffset == 0 && binding.bottomAppBar.isScrolledDown) {
binding.bottomAppBar.performShow()
@ -294,7 +329,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
(binding.toolbar.layoutParams as AppBarLayout.LayoutParams).scrollFlags = 0
}
toolbar.setOnMenuItemClickListener(this)
toolbar.setNavigationOnClickListener { callbacks.onNavigationIconClicked() }
toolbar.setNavigationOnClickListener {
activity?.hideKeyboard()
mainViewModel.setDrawerOpen(true)
}
setupMenu(toolbar)
childFragmentManager.setFilterPickerResultListener(this) {
val selected = taskAdapter.getSelected()
@ -304,23 +342,42 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
finishActionMode()
}
binding.banner.setContent {
val context = LocalContext.current
val showBanner = listViewModel.state.collectAsStateLifecycleAware().value.begForSubscription
MdcTheme {
SubscriptionNagBanner(
visible = showBanner,
subscribe = { listViewModel.dismissBanner(clickedPurchase = true) },
dismiss = { listViewModel.dismissBanner(clickedPurchase = false) },
subscribe = {
listViewModel.dismissBanner(clickedPurchase = true)
if (Tasks.IS_GOOGLE_PLAY) {
context.startActivity(Intent(context, PurchaseActivity::class.java))
} else {
preferences.lastSubscribeRequest = currentTimeMillis()
context.openUri(R.string.url_donate)
}
},
dismiss = {
listViewModel.dismissBanner(clickedPurchase = false)
},
)
}
}
return binding.root
}
private fun submitList(tasks: List<TaskContainer>) {
private fun submitList(tasks: SectionedDataSource) {
if (recyclerAdapter !is DragAndDropRecyclerAdapter) {
setAdapter(
DragAndDropRecyclerAdapter(
taskAdapter, binding.bodyStandard.recyclerView, viewHolderFactory, this, tasks, preferences))
adapter = taskAdapter,
recyclerView = binding.bodyStandard.recyclerView,
viewHolderFactory = viewHolderFactory,
taskList = this,
tasks = tasks,
preferences = preferences,
toggleCollapsed = { listViewModel.toggleCollapsed(it) },
)
)
} else {
recyclerAdapter?.submitList(tasks)
}
@ -335,12 +392,19 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
private fun setupMenu(appBar: Toolbar) {
val menu = appBar.menu
menu.clear()
if (filter.hasBeginningMenu()) {
appBar.inflateMenu(filter.beginningMenu)
if (filter is PlaceFilter) {
appBar.inflateMenu(R.menu.menu_location_actions)
}
appBar.inflateMenu(R.menu.menu_task_list_fragment_bottom)
if (filter.hasMenu()) {
appBar.inflateMenu(filter.menu)
when (filter) {
is CaldavFilter -> R.menu.menu_caldav_list_fragment
is CustomFilter -> R.menu.menu_custom_filter
is GtasksFilter -> R.menu.menu_gtasks_list_fragment
is TagFilter -> R.menu.menu_tag_view_fragment
is PlaceFilter -> R.menu.menu_location_list_fragment
else -> null
}?.let {
appBar.inflateMenu(it)
}
if (appBar is BottomAppBar) {
menu.removeItem(R.id.menu_search)
@ -370,33 +434,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
menu.findItem(R.id.menu_clear_completed).isVisible = filter.isWritable
}
private fun openFilter(filter: Filter?) {
if (filter == null) {
startActivity(TaskIntents.getTaskListByIdIntent(context, null))
} else {
startActivity(TaskIntents.getTaskListIntent(context, filter))
}
}
private fun searchByQuery(query: String?) {
searchJob?.cancel()
searchJob = lifecycleScope.launch {
delay(SEARCH_DEBOUNCE_TIMEOUT)
searchQuery = query?.trim { it <= ' ' } ?: ""
if (searchQuery?.isEmpty() == true) {
listViewModel.setFilter(
BuiltInFilterExposer.getMyTasksFilter(requireContext().resources))
} else {
val savedFilter = createSearchFilter(searchQuery!!)
listViewModel.setFilter(savedFilter)
}
}
}
private fun createSearchFilter(query: String): Filter {
return SearchFilter(getString(R.string.FLA_search_filter, query), query)
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_voice_add -> {
@ -417,13 +454,12 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
true
}
R.id.menu_sort -> {
requireActivity().startActivityForResult(
sortRequest.launch(
SortSettingsActivity.getIntent(
requireActivity(),
filter.supportsManualSort(),
filter.supportsAstridSorting() && preferences.isAstridSortEnabled,
),
REQUEST_SORT
filter is AstridOrderingFilter && preferences.isAstridSortEnabled,
)
)
true
}
@ -440,48 +476,69 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
true
}
R.id.menu_clear_completed -> {
dialogBuilder
.newDialog(R.string.clear_completed_tasks_confirmation)
.setPositiveButton(R.string.ok) { _, _ -> clearCompleted() }
.setNegativeButton(R.string.cancel, null)
.show()
lifecycleScope.launch {
val tasks = listViewModel.getTasksToClear()
val countString = requireContext().resources.getQuantityString(R.plurals.Ntasks, tasks.size, tasks.size)
if (tasks.isEmpty()) {
context?.toast(R.string.delete_multiple_tasks_confirmation, countString)
} else {
dialogBuilder
.newDialog(R.string.clear_completed_tasks_confirmation)
.setMessage(R.string.clear_completed_tasks_count, countString)
.setPositiveButton(R.string.ok) { _, _ ->
lifecycleScope.launch {
listViewModel.markDeleted(tasks)
context?.toast(
R.string.delete_multiple_tasks_confirmation,
countString
)
}
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
true
}
R.id.menu_filter_settings -> {
val filterSettings = Intent(activity, FilterSettingsActivity::class.java)
filterSettings.putExtra(FilterSettingsActivity.TOKEN_FILTER, filter)
startActivityForResult(filterSettings, REQUEST_LIST_SETTINGS)
listSettingsRequest.launch(
Intent(activity, FilterSettingsActivity::class.java)
.putExtra(FilterSettingsActivity.TOKEN_FILTER, filter)
)
true
}
R.id.menu_caldav_list_fragment -> {
val calendar = (filter as CaldavFilter).calendar
lifecycleScope.launch {
val account = caldavDao.getAccountByUuid(calendar.account!!)
val caldavSettings = Intent(activity, account!!.listSettingsClass())
.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT, account)
.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_CALENDAR, calendar)
startActivityForResult(caldavSettings, REQUEST_LIST_SETTINGS)
listSettingsRequest.launch(
Intent(activity, account!!.listSettingsClass())
.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT, account)
.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_CALENDAR, calendar)
)
}
true
}
R.id.menu_location_settings -> {
val place = (filter as PlaceFilter).place
val intent = Intent(activity, PlaceSettingsActivity::class.java)
intent.putExtra(PlaceSettingsActivity.EXTRA_PLACE, place as Parcelable)
startActivityForResult(intent, REQUEST_LIST_SETTINGS)
listSettingsRequest.launch(
Intent(activity, PlaceSettingsActivity::class.java)
.putExtra(PlaceSettingsActivity.EXTRA_PLACE, place as Parcelable)
)
true
}
R.id.menu_gtasks_list_settings -> {
val gtasksSettings = Intent(activity, GoogleTaskListSettingsActivity::class.java)
gtasksSettings.putExtra(
GoogleTaskListSettingsActivity.EXTRA_STORE_DATA, (filter as GtasksFilter).list)
startActivityForResult(gtasksSettings, REQUEST_LIST_SETTINGS)
listSettingsRequest.launch(
Intent(activity, GoogleTaskListSettingsActivity::class.java)
.putExtra(GoogleTaskListSettingsActivity.EXTRA_STORE_DATA, (filter as GtasksFilter).list)
)
true
}
R.id.menu_tag_settings -> {
val tagSettings = Intent(activity, TagSettingsActivity::class.java)
tagSettings.putExtra(TagSettingsActivity.EXTRA_TAG_DATA, (filter as TagFilter).tagData)
startActivityForResult(tagSettings, REQUEST_LIST_SETTINGS)
listSettingsRequest.launch(
Intent(activity, TagSettingsActivity::class.java)
.putExtra(TagSettingsActivity.EXTRA_TAG_DATA, (filter as TagFilter).tagData)
)
true
}
R.id.menu_expand_subtasks -> {
@ -512,11 +569,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
}
private fun clearCompleted() = lifecycleScope.launch {
val count = taskDeleter.clearCompleted(filter)
context?.toast(R.string.delete_multiple_tasks_confirmation, locale.formatNumber(count))
}
private fun createNewTask() {
lifecycleScope.launch {
shortcutManager.reportShortcutUsed(ShortcutManager.SHORTCUT_NEW_TASK)
@ -606,15 +658,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
}
}
REQUEST_LIST_SETTINGS -> if (resultCode == Activity.RESULT_OK) {
val action = data!!.action
if (ACTION_DELETED == action) {
openFilter(BuiltInFilterExposer.getMyTasksFilter(resources))
} else if (ACTION_RELOAD == action) {
openFilter(data.getParcelableExtra(MainActivity.OPEN_FILTER))
}
}
REQUEST_TAG_TASKS -> if (resultCode == Activity.RESULT_OK) {
REQUEST_TAG_TASKS -> if (resultCode == RESULT_OK) {
lifecycleScope.launch {
val modified = tagDataDao.applyTags(
taskDao
@ -631,20 +675,13 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
}
override fun onContextItemSelected(item: MenuItem): Boolean {
return onOptionsItemSelected(item)
}
private fun onTaskListItemClicked(task: Task?) = lifecycleScope.launch {
callbacks.onTaskListItemClicked(task)
mainViewModel.setTask(task)
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
onBackPressed.isEnabled = true
search.setOnQueryTextListener(this)
if (searchQuery == null) {
searchByQuery("")
}
listViewModel.setSearchQuery("")
if (preferences.isTopAppBar) {
binding.toolbar.menu.forEach { it.isVisible = false }
}
@ -652,11 +689,9 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
onBackPressed.isEnabled = false
search.setOnQueryTextListener(null)
listViewModel.setFilter(filter)
searchJob?.cancel()
searchQuery = null
listViewModel.setSearchQuery(null)
if (preferences.isTopAppBar) {
setupMenu(binding.toolbar)
}
@ -664,13 +699,13 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
override fun onQueryTextSubmit(query: String): Boolean {
openFilter(createSearchFilter(query.trim { it <= ' ' }))
mainViewModel.setFilter(requireContext().createSearchQuery(query.trim()))
search.collapseActionView()
return true
}
override fun onQueryTextChange(query: String): Boolean {
searchByQuery(query)
listViewModel.setSearchQuery(query)
return true
}
@ -751,8 +786,19 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
R.id.menu_share -> {
lifecycleScope.launch {
selected.chunkedMap { taskDao.fetchTasks(preferences, IdListFilter(it)) }
.apply { send(this) }
selected
.chunkedMap {
taskDao.fetchTasks(
preferences,
FilterImpl(
sql = QueryTemplate()
.join(Join.left(Tag.TABLE, Tag.TASK.eq(Task.ID)))
.where(Task.ID.`in`(it))
.toString()
)
)
}
.let { send(it) }
}
true
}
@ -785,7 +831,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
val intent = ShareCompat
.IntentBuilder(requireContext())
.setType("text/plain")
.setSubject(filter.listingTitle)
.setSubject(filter.title)
.setText(output)
.createChooserIntent()
startActivity(intent)
@ -810,11 +856,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
}
interface TaskListFragmentCallbackHandler {
suspend fun onTaskListItemClicked(task: Task?)
fun onNavigationIconClicked()
}
val isActionModeActive: Boolean
get() = mode != null
@ -841,7 +882,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
finishActionMode()
val result = withContext(NonCancellable) {
taskDeleter.markDeleted(tasks)
listViewModel.markDeleted(tasks)
}
result.forEach { onTaskDelete(it) }
makeSnackbar(R.string.delete_multiple_tasks_confirmation, result.size.toString())?.show()
@ -862,15 +903,13 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString())?.show()
}
fun clearCollapsed() = taskAdapter.clearCollapsed()
override fun onCompletedTask(task: TaskContainer, newState: Boolean) {
if (task.isReadOnly) {
return
}
lifecycleScope.launch {
taskCompleter.setComplete(task.task, newState)
taskAdapter.onCompletedTask(task, newState)
taskAdapter.onCompletedTask(task.uuid, newState)
loadTaskListContent()
}
}
@ -893,8 +932,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
override fun onClick(filter: Filter) {
if (!isActionModeActive) {
val context = activity
context?.startActivity(TaskIntents.getTaskListIntent(context, filter))
mainViewModel.setFilter(filter)
}
}
@ -926,7 +964,12 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
lifecycleScope.launch {
val tasks =
(intent.getSerializableExtra(EXTRAS_TASK_ID) as? ArrayList<Long>)
?.let { taskDao.fetch(it) }
?.let {
// hack to wait for task save transaction to complete
database.withTransaction {
taskDao.fetch(it)
}
}
?.filterNot { it.readOnly }
?.takeIf { it.isNotEmpty() }
?: return@launch
@ -949,9 +992,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
if (isRecurringCompletion) {
val task = tasks.first()
val title = markdown.markdown(force = true).toMarkdown(task.title)
val text = getString(
R.string.repeat_snackbar,
task.title,
title,
DateUtilities.getRelativeDateTime(
context, task.dueDate, locale, FormatStyle.LONG, true
)
@ -970,28 +1014,20 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
companion object {
const val TAGS_METADATA_JOIN = "for_tags" // $NON-NLS-1$
const val CALDAV_METADATA_JOIN = "for_caldav" // $NON-NLS-1$
const val ACTION_RELOAD = "action_reload"
const val ACTION_DELETED = "action_deleted"
private const val EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids"
private const val EXTRA_SEARCH = "extra_search"
private const val EXTRA_COLLAPSED = "extra_collapsed"
private const val VOICE_RECOGNITION_REQUEST_CODE = 1234
private const val EXTRA_FILTER = "extra_filter"
private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker"
private const val FRAG_TAG_DATE_TIME_PICKER = "frag_tag_date_time_picker"
private const val FRAG_TAG_PRIORITY_PICKER = "frag_tag_priority_picker"
private const val REQUEST_LIST_SETTINGS = 10101
private const val REQUEST_TAG_TASKS = 10106
const val REQUEST_SORT = 10107
private const val SEARCH_DEBOUNCE_TIMEOUT = 300L
fun newTaskListFragment(context: Context, filter: Filter?): TaskListFragment {
fun newTaskListFragment(filter: Filter): TaskListFragment {
val fragment = TaskListFragment()
val bundle = Bundle()
bundle.putParcelable(
EXTRA_FILTER,
filter ?: BuiltInFilterExposer.getMyTasksFilter(context.resources))
bundle.putParcelable(EXTRA_FILTER, filter)
fragment.arguments = bundle
return fragment
}

@ -1,38 +0,0 @@
package com.todoroo.astrid.adapter
import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.todoroo.astrid.api.FilterListItem
import org.tasks.databinding.FilterAdapterActionBinding
import org.tasks.themes.DrawableUtil
class ActionViewHolder internal constructor(
private val context: Context,
itemView: View,
private val onClick: ((FilterListItem?) -> Unit)?) : RecyclerView.ViewHolder(itemView) {
private val row: View
private val text: TextView
private val icon: ImageView
init {
FilterAdapterActionBinding.bind(itemView).let {
row = it.row
text = it.text
icon = it.icon
}
}
fun bind(filter: FilterListItem) {
text.text = filter.listingTitle
icon.setImageDrawable(DrawableUtil.getWrapped(context, filter.icon))
if (onClick != null) {
row.setOnClickListener {
onClick.invoke(filter)
}
}
}
}

@ -1,31 +1,31 @@
package com.todoroo.astrid.adapter
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskMover
import com.todoroo.astrid.subtasks.SubtasksFilterUpdater
import org.tasks.LocalBroadcastManager
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.TaskContainer
import org.tasks.data.TaskListMetadata
import org.tasks.data.entity.TaskListMetadata
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import timber.log.Timber
import java.util.*
import java.util.Collections
import kotlin.math.abs
@Deprecated("legacy astrid manual sorting")
class AstridTaskAdapter internal constructor(
private val list: TaskListMetadata,
private val filter: Filter,
private val updater: SubtasksFilterUpdater,
googleTaskDao: GoogleTaskDao,
caldavDao: CaldavDao,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
taskMover: TaskMover,
private val list: TaskListMetadata,
private val filter: AstridOrderingFilter,
private val updater: SubtasksFilterUpdater,
googleTaskDao: GoogleTaskDao,
caldavDao: CaldavDao,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
taskMover: TaskMover,
) : TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover) {
private val chainedCompletions = Collections.synchronizedMap(HashMap<String, ArrayList<String>>())
@ -66,11 +66,10 @@ class AstridTaskAdapter internal constructor(
override suspend fun onTaskDeleted(task: Task) = updater.onDeleteTask(list, filter, task.uuid)
override suspend fun onCompletedTask(task: TaskContainer, newState: Boolean) {
val itemId = task.uuid
val completionDate = if (newState) DateUtilities.now() else 0
override suspend fun onCompletedTask(uuid: String, newState: Boolean) {
val completionDate = if (newState) currentTimeMillis() else 0
if (!newState) {
val chained = chainedCompletions[itemId]
val chained = chainedCompletions[uuid]
if (chained != null) {
for (taskId in chained) {
taskDao.setCompletionDate(taskId, completionDate)
@ -79,7 +78,7 @@ class AstridTaskAdapter internal constructor(
return
}
val chained = ArrayList<String>()
updater.applyToDescendants(itemId) { node: SubtasksFilterUpdater.Node ->
updater.applyToDescendants(uuid) { node: SubtasksFilterUpdater.Node ->
val uuid = node.uuid
taskDao.setCompletionDate(uuid, completionDate)
chained.add(node.uuid)
@ -90,14 +89,14 @@ class AstridTaskAdapter internal constructor(
var madeChanges = false
for (t in tasks) {
if (!isNullOrEmpty(t.recurrence)) {
updater.moveToParentOf(t.uuid, itemId)
updater.moveToParentOf(t.uuid, uuid)
madeChanges = true
}
}
if (madeChanges) {
updater.writeSerialization(list, updater.serializeTree())
}
chainedCompletions[itemId] = chained
chainedCompletions[uuid] = chained
}
}

@ -3,15 +3,15 @@ package com.todoroo.astrid.adapter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.service.TaskMover
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
class CaldavManualSortTaskAdapter internal constructor(
googleTaskDao: GoogleTaskDao,
caldavDao: CaldavDao,
taskDao: TaskDao,
localBroadcastManager: LocalBroadcastManager,
taskMover: TaskMover,
googleTaskDao: GoogleTaskDao,
caldavDao: CaldavDao,
taskDao: TaskDao,
localBroadcastManager: LocalBroadcastManager,
taskMover: TaskMover,
) : TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover) {
override suspend fun moved(from: Int, to: Int, indent: Int) {

@ -7,7 +7,11 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.todoroo.astrid.api.*
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.CustomFilter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter
import org.tasks.R
import org.tasks.billing.Inventory
import org.tasks.databinding.FilterAdapterRowBinding
@ -16,7 +20,7 @@ import org.tasks.filters.PlaceFilter
import org.tasks.themes.ColorProvider
import org.tasks.themes.CustomIcons.getIconResId
import org.tasks.themes.DrawableUtil
import java.util.*
import java.util.Locale
class FilterViewHolder internal constructor(
itemView: View,
@ -25,7 +29,7 @@ class FilterViewHolder internal constructor(
private val context: Context,
private val inventory: Inventory,
private val colorProvider: ColorProvider,
private val onClick: ((FilterListItem?) -> Unit)?
private val onClick: (Filter) -> Unit,
) : RecyclerView.ViewHolder(itemView) {
private val row: View
@ -34,7 +38,7 @@ class FilterViewHolder internal constructor(
private val size: TextView
private val shareIndicator: ImageView
lateinit var filter: FilterListItem
lateinit var filter: Filter
init {
FilterAdapterRowBinding.bind(itemView).let {
@ -53,7 +57,7 @@ class FilterViewHolder internal constructor(
itemView.isSelected = moving
}
fun bind(filter: FilterListItem, selected: Boolean, count: Int?) {
fun bind(filter: Filter, selected: Boolean, count: Int?) {
this.filter = filter
if (navigationDrawer) {
itemView.isSelected = selected
@ -63,7 +67,7 @@ class FilterViewHolder internal constructor(
val icon = getIcon(filter)
this.icon.setImageDrawable(DrawableUtil.getWrapped(context, icon))
this.icon.drawable.setTint(getColor(filter))
text.text = filter.listingTitle
text.text = filter.title
if (count == null || count == 0) {
size.visibility = View.INVISIBLE
} else {
@ -71,21 +75,20 @@ class FilterViewHolder internal constructor(
size.visibility = View.VISIBLE
}
shareIndicator.apply {
isVisible = filter.principals > 0
isVisible = filter is CaldavFilter && filter.principals > 0
setImageResource(when {
filter !is CaldavFilter -> 0
filter.principals <= 0 -> 0
filter.principals == 1 -> R.drawable.ic_outline_perm_identity_24px
else -> R.drawable.ic_outline_people_outline_24
})
}
if (onClick != null) {
row.setOnClickListener {
onClick.invoke(filter)
}
row.setOnClickListener {
onClick.invoke(filter)
}
}
private fun getColor(filter: FilterListItem): Int {
private fun getColor(filter: Filter): Int {
if (filter.tint != 0) {
val color = colorProvider.getThemeColor(filter.tint, true)
if (color.isFree || inventory.purchasedThemes()) {
@ -95,7 +98,7 @@ class FilterViewHolder internal constructor(
return context.getColor(R.color.text_primary)
}
private fun getIcon(filter: FilterListItem): Int {
private fun getIcon(filter: Filter): Int {
if (filter.icon < 1000 || inventory.hasPro) {
val icon = getIconResId(filter.icon)
if (icon != null) {

@ -3,15 +3,15 @@ package com.todoroo.astrid.adapter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.service.TaskMover
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
class GoogleTaskManualSortAdapter internal constructor(
googleTaskDao: GoogleTaskDao,
caldavDao: CaldavDao,
taskDao: TaskDao,
localBroadcastManager: LocalBroadcastManager,
taskMover: TaskMover,
googleTaskDao: GoogleTaskDao,
caldavDao: CaldavDao,
taskDao: TaskDao,
localBroadcastManager: LocalBroadcastManager,
taskMover: TaskMover,
) : TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover) {
override suspend fun moved(from: Int, to: Int, indent: Int) {

@ -20,7 +20,9 @@ import org.tasks.activities.DragAndDropDiffer
import org.tasks.billing.Inventory
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.themes.ColorProvider
import java.util.*
import java.util.LinkedList
import java.util.Locale
import java.util.Queue
import java.util.concurrent.Executors
import javax.inject.Inject
import kotlin.math.max
@ -32,12 +34,11 @@ class NavigationDrawerAdapter @Inject constructor(
private val colorProvider: ColorProvider,
private val subheaderClickHandler: SubheaderClickHandler,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),
DragAndDropDiffer<FilterListItem, MutableList<FilterListItem>> {
DragAndDropDiffer<FilterListItem, ArrayList<FilterListItem>> {
private lateinit var onClick: (FilterListItem?) -> Unit
private var selected: Filter? = null
override val channel = Channel<List<FilterListItem>>(Channel.UNLIMITED)
override val updates: Queue<Pair<MutableList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList()
override val channel = Channel<ArrayList<FilterListItem>>(Channel.UNLIMITED)
override val updates: Queue<Pair<ArrayList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList()
override val scope: CoroutineScope =
CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher() + Job())
override var items = initializeDiffer(ArrayList())
@ -55,11 +56,6 @@ class NavigationDrawerAdapter @Inject constructor(
override fun getItemCount() = items.size
fun setSelected(selected: Filter?) {
this.selected = selected
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val type = FilterListItem.Type.values()[viewType]
val view = LayoutInflater.from(parent.context).inflate(type.layout, parent, false)
@ -67,20 +63,17 @@ class NavigationDrawerAdapter @Inject constructor(
FilterListItem.Type.ITEM -> FilterViewHolder(
view, true, locale, activity, inventory, colorProvider) { onClickFilter(it) }
FilterListItem.Type.SUBHEADER -> SubheaderViewHolder(view, subheaderClickHandler)
FilterListItem.Type.ACTION -> ActionViewHolder(activity, view) { onClickFilter(it) }
else -> SeparatorViewHolder(view)
}
}
private fun onClickFilter(filter: FilterListItem?) =
onClick(if (filter == selected) null else filter)
onClick(filter)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (item.itemType) {
FilterListItem.Type.ITEM ->
(holder as FilterViewHolder).bind(item, item == selected, max(item.count, 0))
FilterListItem.Type.ACTION -> (holder as ActionViewHolder).bind(item)
(holder as FilterViewHolder).bind(item as Filter, false, max(item.count, 0))
FilterListItem.Type.SUBHEADER ->
(holder as SubheaderViewHolder).bind((item as NavigationDrawerSubheader))
else -> {}
@ -91,9 +84,7 @@ class NavigationDrawerAdapter @Inject constructor(
private fun getItem(position: Int) = items[position]
override fun transform(list: List<FilterListItem>) = list.toMutableList()
override fun diff(last: MutableList<FilterListItem>, next: MutableList<FilterListItem>) =
override fun diff(last: ArrayList<FilterListItem>, next: ArrayList<FilterListItem>) =
DiffUtil.calculateDiff(DiffCallback(last, next))
private class DiffCallback(val old: List<FilterListItem>, val new: List<FilterListItem>) : DiffUtil.Callback() {
@ -105,7 +96,7 @@ class NavigationDrawerAdapter @Inject constructor(
old[oldPosition].areItemsTheSame(new[newPosition])
override fun areContentsTheSame(oldPosition: Int, newPosition: Int) =
old[oldPosition].areContentsTheSame(new[newPosition])
old[oldPosition] == new[newPosition]
}
override fun onChanged(position: Int, count: Int, payload: Any?) =

@ -1,6 +0,0 @@
package com.todoroo.astrid.adapter
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class SeparatorViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView)

@ -6,13 +6,17 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.dao.CaldavDao
import org.tasks.dialogs.NewFilterDialog
import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.*
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.CALDAV
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.ETESYNC
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.PREFERENCE
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.TASKS
import org.tasks.preferences.MainPreferences
import org.tasks.preferences.Preferences
import org.tasks.ui.NavigationDrawerFragment
import javax.inject.Inject
class SubheaderClickHandler @Inject constructor(
@ -30,6 +34,7 @@ class SubheaderClickHandler @Inject constructor(
CALDAV,
TASKS,
ETESYNC -> caldavDao.setCollapsed(subheader.id, collapsed)
else -> throw IllegalArgumentException()
}
localBroadcastManager.broadcastRefreshList()
}
@ -37,7 +42,7 @@ class SubheaderClickHandler @Inject constructor(
override fun onAdd(subheader: NavigationDrawerSubheader) {
when (subheader.addIntentRc) {
NavigationDrawerFragment.REQUEST_NEW_FILTER ->
FilterProvider.REQUEST_NEW_FILTER ->
NewFilterDialog.newFilterDialog().show(
(activity as AppCompatActivity).supportFragmentManager,
FRAG_TAG_NEW_FILTER
@ -50,6 +55,6 @@ class SubheaderClickHandler @Inject constructor(
activity.startActivity(Intent(activity, MainPreferences::class.java))
companion object {
private const val FRAG_TAG_NEW_FILTER = "frag_tag_new_filter"
const val FRAG_TAG_NEW_FILTER = "frag_tag_new_filter"
}
}

@ -33,7 +33,7 @@ internal class SubheaderViewHolder(
fun bind(subheader: NavigationDrawerSubheader) {
add.isVisible = subheader.addIntent != null
this.subheader = subheader
text.text = subheader.listingTitle
text.text = subheader.title
when {
subheader.error || subheader.subheaderType == ETESYNC ->
with(errorIcon) {

@ -11,30 +11,30 @@ import com.todoroo.astrid.core.SortHelper.SORT_LIST
import com.todoroo.astrid.core.SortHelper.SORT_MANUAL
import com.todoroo.astrid.core.SortHelper.SORT_START
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY
import com.todoroo.astrid.service.TaskMover
import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.GoogleTaskDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.CaldavDao.Companion.toAppleEpoch
import org.tasks.data.entity.CaldavTask
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.entity.Task
import org.tasks.data.entity.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY
import org.tasks.data.TaskContainer
import org.tasks.date.DateTimeUtils.toAppleEpoch
import org.tasks.data.createDueDate
import org.tasks.data.createHideUntil
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.tasklist.SectionedDataSource.Companion.HEADER_COMPLETED
import org.tasks.time.DateTimeUtils.millisOfDay
open class TaskAdapter(
private val newTasksOnTop: Boolean,
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val taskMover: TaskMover,
private val newTasksOnTop: Boolean,
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val taskMover: TaskMover,
) {
private val selected = HashSet<Long>()
private val collapsed = mutableSetOf(HEADER_COMPLETED)
private lateinit var dataSource: TaskAdapterDataSource
val count: Int
@ -56,15 +56,6 @@ open class TaskAdapter(
fun clearSelections() = selected.clear()
fun getCollapsed() = HashSet(collapsed)
fun setCollapsed(groups: LongArray?) {
clearCollapsed()
groups?.toList()?.let(collapsed::addAll)
}
fun clearCollapsed() = collapsed.retainAll(listOf(HEADER_COMPLETED))
open fun getIndent(task: TaskContainer): Int = task.indent
open fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int): Boolean {
@ -125,20 +116,12 @@ open class TaskAdapter(
}
}
fun toggleCollapsed(group: Long) {
if (collapsed.contains(group)) {
collapsed.remove(group)
} else {
collapsed.add(group)
}
}
open fun supportsAstridSorting(): Boolean = false
open suspend fun moved(from: Int, to: Int, indent: Int) {
val task = getTask(from)
val newParent = findParent(indent, to)
if ((newParent?.id ?: 0) == task.parent) {
if ((newParent?.id ?: 0) == task.parent || (indent > 0 && dataSource.subtaskSortMode == SORT_MANUAL)) {
if (indent == 0) {
changeSortGroup(task, if (from < to) to - 1 else to)
} else if (dataSource.subtaskSortMode == SORT_MANUAL) {
@ -170,7 +153,7 @@ open class TaskAdapter(
fun getItemUuid(position: Int): String = getTask(position).uuid
open suspend fun onCompletedTask(task: TaskContainer, newState: Boolean) {}
open suspend fun onCompletedTask(uuid: String, newState: Boolean) {}
open suspend fun onTaskCreated(uuid: String) {}
@ -210,9 +193,7 @@ open class TaskAdapter(
SORT_IMPORTANCE -> {
val newPriority = dataSource.nearestHeader(if (pos == 0) 1 else pos).toInt()
if (newPriority != task.priority) {
val t = task.task
t.priority = newPriority
taskDao.save(t)
taskDao.save(task.task.copy(priority = newPriority))
}
}
SORT_LIST -> taskMover.move(task.id, dataSource.nearestHeader(if (pos == 0) 1 else pos))
@ -226,7 +207,7 @@ open class TaskAdapter(
task.setDueDateAdjustingHideUntil(when {
date == 0L -> 0L
task.hasDueTime() -> date.toDateTime().withMillisOfDay(original.millisOfDay()).millis
else -> Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, date)
else -> createDueDate(Task.URGENCY_SPECIFIC_DAY, date)
})
if (original != task.dueDate) {
taskDao.save(task)
@ -281,7 +262,7 @@ open class TaskAdapter(
private suspend fun changeCaldavParent(task: TaskContainer, newParent: TaskContainer?) {
val list = newParent?.caldav ?: task.caldav!!
val caldavTask = task.caldavTask ?: CaldavTask(
val caldavTask = task.caldavTask.takeIf { list == task.caldav } ?: CaldavTask(
task = task.id,
calendar = list,
)
@ -289,9 +270,8 @@ open class TaskAdapter(
if (newParentId == 0L) {
caldavTask.remoteParent = ""
} else {
val parentTask = caldavDao.getTask(newParentId) ?: return
caldavTask.calendar = list
caldavTask.remoteParent = parentTask.remoteId
caldavTask.remoteParent = newParent?.caldavTask?.remoteId ?: return
}
task.task.order = if (newTasksOnTop) {
caldavDao.findFirstTask(list, newParentId)
@ -303,12 +283,13 @@ open class TaskAdapter(
?.plus(1)
}
if (caldavTask.id == 0L) {
val newTask = CaldavTask(
task = task.id,
calendar = list,
caldavDao.insert(
CaldavTask(
task = task.id,
calendar = list,
remoteParent = caldavTask.remoteParent,
)
)
newTask.remoteParent = caldavTask.remoteParent
caldavDao.insert(newTask)
} else {
caldavDao.update(caldavTask)
}
@ -419,7 +400,12 @@ open class TaskAdapter(
indent == previous.indent -> previous.caldavSortOrder + 1
else -> getTask((to - 1 downTo 0).find { getTask(it).indent == indent }!!).caldavSortOrder + 1
}
caldavDao.move(task, oldParent, newParent, newPosition)
caldavDao.move(
task = task,
previousParent = oldParent,
newParent = newParent,
newPosition = newPosition,
)
taskDao.touch(task.id)
localBroadcastManager.broadcastRefresh()
}
@ -436,13 +422,24 @@ open class TaskAdapter(
val caldavTask = task.caldavTask ?: return
if (newParent == 0L) {
caldavTask.remoteParent = ""
task.parent = 0
caldavDao.update(caldavTask.id, caldavTask.remoteParent)
} else {
val parentTask = caldavDao.getTask(newParent) ?: return
caldavTask.remoteParent = parentTask.remoteId
task.parent = newParent
if (parentTask.calendar == caldavTask.calendar) {
caldavTask.remoteParent = parentTask.remoteId
caldavDao.update(caldavTask.id, caldavTask.remoteParent)
} else {
caldavDao.markDeleted(listOf(task.id))
caldavDao.insert(
CaldavTask(
task = task.id,
calendar = parentTask.calendar,
remoteParent = parentTask.remoteId,
)
)
}
}
caldavDao.update(caldavTask.id, caldavTask.remoteParent)
task.parent = newParent
taskDao.save(task.task, null)
}
}

@ -1,13 +1,14 @@
package com.todoroo.astrid.adapter
import android.content.Context
import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter
import com.todoroo.astrid.core.BuiltInFilterExposer
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task.Companion.isUuidEmpty
import org.tasks.data.entity.Task.Companion.isUuidEmpty
import com.todoroo.astrid.service.TaskMover
import com.todoroo.astrid.subtasks.SubtasksFilterUpdater
import com.todoroo.astrid.subtasks.SubtasksHelper
@ -15,25 +16,25 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.runBlocking
import org.tasks.LocalBroadcastManager
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskListMetadata
import org.tasks.data.TaskListMetadataDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.entity.TaskListMetadata
import org.tasks.data.dao.TaskListMetadataDao
import org.tasks.preferences.Preferences
import javax.inject.Inject
class TaskAdapterProvider @Inject constructor(
@param:ApplicationContext private val context: Context,
private val preferences: Preferences,
private val taskListMetadataDao: TaskListMetadataDao,
private val taskDao: TaskDao,
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao,
private val localBroadcastManager: LocalBroadcastManager,
private val taskMover: TaskMover,
@param:ApplicationContext private val context: Context,
private val preferences: Preferences,
private val taskListMetadataDao: TaskListMetadataDao,
private val taskDao: TaskDao,
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao,
private val localBroadcastManager: LocalBroadcastManager,
private val taskMover: TaskMover,
) {
fun createTaskAdapter(filter: Filter): TaskAdapter {
if (filter.supportsAstridSorting() && preferences.isAstridSort) {
if (filter is AstridOrderingFilter && preferences.isAstridSort) {
when (filter) {
is TagFilter -> return createManualTagTaskAdapter(filter)
else -> {
@ -67,7 +68,7 @@ class TaskAdapterProvider @Inject constructor(
AstridTaskAdapter(list!!, filter, updater, googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
}
private fun createManualFilterTaskAdapter(filter: Filter): TaskAdapter? = runBlocking {
private fun createManualFilterTaskAdapter(filter: AstridOrderingFilter): TaskAdapter? = runBlocking {
var filterId: String? = null
var prefId: String? = null
if (BuiltInFilterExposer.isInbox(context, filter)) {

@ -1,8 +1,7 @@
package com.todoroo.astrid.alarms
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task
import org.tasks.data.Alarm
import org.tasks.data.entity.Task
import org.tasks.data.entity.Alarm
import org.tasks.jobs.AlarmEntry
import org.tasks.preferences.Preferences
import org.tasks.reminders.Random
@ -81,11 +80,7 @@ class AlarmCalculator(
`when` = task.creationDate
}
`when` += (reminderPeriod * (0.85f + 0.3f * random.nextFloat())).toLong()
if (`when` < DateUtilities.now()) {
`when` =
DateUtilities.now() + ((0.5f + 6 * random.nextFloat()) * DateUtilities.ONE_HOUR).toLong()
}
return `when`
return Math.max(`when`, task.hideUntil)
}
return AlarmService.NO_ALARM
}

@ -5,14 +5,17 @@
*/
package com.todoroo.astrid.alarms
import com.todoroo.astrid.data.Task
import org.tasks.LocalBroadcastManager
import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao
import org.tasks.data.TaskDao
import org.tasks.jobs.NotificationQueue
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.TaskDao
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE
import org.tasks.jobs.AlarmEntry
import org.tasks.jobs.WorkManager
import org.tasks.notifications.NotificationManager
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import timber.log.Timber
import javax.inject.Inject
/**
@ -22,10 +25,10 @@ import javax.inject.Inject
*/
class AlarmService @Inject constructor(
private val alarmDao: AlarmDao,
private val jobs: NotificationQueue,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val notificationManager: NotificationManager,
private val workManager: WorkManager,
private val alarmCalculator: AlarmCalculator,
) {
suspend fun getAlarms(taskId: Long): List<Alarm> = alarmDao.getAlarms(taskId)
@ -36,7 +39,6 @@ class AlarmService @Inject constructor(
* @return true if data was changed
*/
suspend fun synchronizeAlarms(taskId: Long, alarms: MutableSet<Alarm>): Boolean {
val task = taskDao.fetch(taskId) ?: return false
var changed = false
for (existing in alarmDao.getAlarms(taskId)) {
if (!alarms.removeIf {
@ -55,51 +57,41 @@ class AlarmService @Inject constructor(
changed = true
}
if (changed) {
scheduleAlarms(task)
localBroadcastManager.broadcastRefreshList()
}
return changed
}
suspend fun scheduleAllAlarms() {
alarmDao
.getActiveAlarms()
.groupBy { it.task }
.forEach { (taskId, alarms) ->
val task = taskDao.fetch(taskId) ?: return@forEach
scheduleAlarms(task, alarms)
}
}
fun cancelAlarms(taskId: Long) {
jobs.cancelForTask(taskId)
}
suspend fun snooze(time: Long, taskIds: List<Long>) {
notificationManager.cancel(taskIds)
alarmDao.getSnoozed(taskIds).let { alarmDao.delete(it) }
taskIds.map { Alarm(it, time, TYPE_SNOOZE) }.let { alarmDao.insert(it) }
taskDao.touch(taskIds)
scheduleAlarms(taskIds)
workManager.triggerNotifications()
}
suspend fun scheduleAlarms(taskIds: List<Long>) {
taskDao.fetch(taskIds).forEach { scheduleAlarms(it) }
}
/** Schedules alarms for a single task */
suspend fun scheduleAlarms(task: Task) {
scheduleAlarms(task, alarmDao.getActiveAlarms(task.id))
}
private fun scheduleAlarms(task: Task, alarms: List<Alarm>) {
jobs.cancelForTask(task.id)
val alarmEntries = alarms.mapNotNull {
alarmCalculator.toAlarmEntry(task, it)
}
val next =
alarmEntries.find { it.type == TYPE_SNOOZE } ?: alarmEntries.minByOrNull { it.time }
next?.let { jobs.add(it) }
suspend fun getAlarms(): Pair<List<AlarmEntry>, List<AlarmEntry>> {
val start = currentTimeMillis()
val overdue = ArrayList<AlarmEntry>()
val future = ArrayList<AlarmEntry>()
alarmDao.getActiveAlarms()
.groupBy { it.task }
.forEach { (taskId, alarms) ->
val task = taskDao.fetch(taskId) ?: return@forEach
val alarmEntries = alarms.mapNotNull {
alarmCalculator.toAlarmEntry(task, it)
}
val (now, later) = alarmEntries.partition { it.time <= DateTime().startOfMinute().plusMinutes(1).millis }
later
.find { it.type == TYPE_SNOOZE }
?.let { future.add(it) }
?: run {
now.firstOrNull()?.let { overdue.add(it) }
later.minByOrNull { it.time }?.let { future.add(it) }
}
}
Timber.d("took ${currentTimeMillis() - start}ms overdue=${overdue.size} future=${future.size}")
return overdue to future
}
companion object {

@ -1,122 +0,0 @@
package com.todoroo.astrid.api;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.astrid.data.Task;
import org.tasks.R;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavTask;
import org.tasks.data.TaskDao;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class CaldavFilter extends Filter {
/** Parcelable Creator Object */
public static final Parcelable.Creator<CaldavFilter> CREATOR =
new Parcelable.Creator<>() {
/** {@inheritDoc} */
@Override
public CaldavFilter createFromParcel(Parcel source) {
CaldavFilter item = new CaldavFilter();
item.readFromParcel(source);
return item;
}
/** {@inheritDoc} */
@Override
public CaldavFilter[] newArray(int size) {
return new CaldavFilter[size];
}
};
private CaldavCalendar calendar;
private CaldavFilter() {
super();
}
public CaldavFilter(CaldavCalendar calendar) {
super(calendar.getName(), queryTemplate(calendar), getValuesForNewTask(calendar));
this.calendar = calendar;
id = calendar.getId();
tint = calendar.getColor();
icon = calendar.getIcon();
order = calendar.getOrder();
}
private static QueryTemplate queryTemplate(CaldavCalendar caldavCalendar) {
return new QueryTemplate()
.join(Join.left(CaldavTask.TABLE, Task.ID.eq(CaldavTask.TASK)))
.where(getCriterion(caldavCalendar));
}
private static Criterion getCriterion(CaldavCalendar caldavCalendar) {
return Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
CaldavTask.DELETED.eq(0),
CaldavTask.CALENDAR.eq(caldavCalendar.getUuid()));
}
private static Map<String, Object> getValuesForNewTask(CaldavCalendar caldavCalendar) {
Map<String, Object> result = new HashMap<>();
result.put(CaldavTask.KEY, caldavCalendar.getUuid());
return result;
}
public String getUuid() {
return calendar.getUuid();
}
public String getAccount() {
return calendar.getAccount();
}
public CaldavCalendar getCalendar() {
return calendar;
}
@Override
public boolean isReadOnly() {
return calendar.getAccess() == CaldavCalendar.ACCESS_READ_ONLY;
}
/** {@inheritDoc} */
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(calendar, 0);
}
@Override
protected void readFromParcel(Parcel source) {
super.readFromParcel(source);
calendar = source.readParcelable(getClass().getClassLoader());
}
@Override
public boolean supportsManualSort() {
return true;
}
@Override
public int getMenu() {
return R.menu.menu_caldav_list_fragment;
}
@Override
public boolean areContentsTheSame(@NonNull FilterListItem other) {
return super.areContentsTheSame(other)
&& Objects.equals(calendar, ((CaldavFilter) other).calendar);
}
}

@ -0,0 +1,57 @@
package com.todoroo.astrid.api
import org.tasks.data.sql.Criterion.Companion.and
import org.tasks.data.sql.Join.Companion.left
import org.tasks.data.sql.QueryTemplate
import com.todoroo.andlib.utility.AndroidUtilities
import kotlinx.parcelize.Parcelize
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask
import org.tasks.data.NO_COUNT
import org.tasks.data.entity.Task
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
@Parcelize
data class CaldavFilter(
val calendar: CaldavCalendar,
val principals: Int = 0,
override val count: Int = NO_COUNT,
) : Filter {
override val title: String?
get() = calendar.name
override val sql: String
get() = QueryTemplate()
.join(left(CaldavTask.TABLE, Task.ID.eq(CaldavTask.TASK)))
.where(
and(
activeAndVisible(),
CaldavTask.DELETED.eq(0),
CaldavTask.CALENDAR.eq(calendar.uuid)
)
)
.toString()
override val valuesForNewTasks: String
get() = AndroidUtilities.mapToSerializedString(mapOf(CaldavTask.KEY to calendar.uuid!!))
override val order: Int
get() = calendar.order
override val tint: Int
get() = calendar.color
override val icon: Int
get() = calendar.getIcon()!!
val uuid: String
get() = calendar.uuid!!
val account: String
get() = calendar.account!!
override val isReadOnly: Boolean
get() = calendar.access == CaldavCalendar.ACCESS_READ_ONLY
override fun supportsManualSort() = true
override fun areItemsTheSame(other: FilterListItem): Boolean {
return other is CaldavFilter && calendar.id == other.calendar.id
}
}

@ -1,73 +0,0 @@
package com.todoroo.astrid.api;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import org.tasks.R;
import java.util.Objects;
public class CustomFilter extends Filter {
/** Parcelable Creator Object */
public static final Parcelable.Creator<CustomFilter> CREATOR =
new Parcelable.Creator<>() {
/** {@inheritDoc} */
@Override
public CustomFilter createFromParcel(Parcel source) {
return new CustomFilter(source);
}
/** {@inheritDoc} */
@Override
public CustomFilter[] newArray(int size) {
return new CustomFilter[size];
}
};
private String criterion;
public CustomFilter(@NonNull org.tasks.data.Filter filter) {
super(filter.getTitle(), filter.getSql(), filter.getValuesAsMap());
id = filter.getId();
criterion = filter.getCriterion();
tint = filter.getColor();
icon = filter.getIcon();
order = filter.getOrder();
}
private CustomFilter(Parcel parcel) {
readFromParcel(parcel);
}
public String getCriterion() {
return criterion;
}
/** {@inheritDoc} */
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(criterion);
}
@Override
protected void readFromParcel(Parcel source) {
super.readFromParcel(source);
criterion = source.readString();
}
@Override
public int getMenu() {
return getId() > 0 ? R.menu.menu_custom_filter : 0;
}
@Override
public boolean areContentsTheSame(@NonNull FilterListItem other) {
return super.areContentsTheSame(other)
&& Objects.equals(criterion, ((CustomFilter) other).criterion);
}
}

@ -0,0 +1,34 @@
package com.todoroo.astrid.api
import kotlinx.parcelize.Parcelize
import org.tasks.themes.CustomIcons
@Parcelize
data class CustomFilter(
val filter: org.tasks.data.entity.Filter,
) : Filter {
override val title: String?
get() = filter.title
override val sql: String
get() = filter.sql!!
override val valuesForNewTasks: String?
get() = filter.values
val criterion: String?
get() = filter.criterion
override val order: Int
get() = filter.order
val id: Long
get() = filter.id
override val icon: Int
get() = filter.icon ?: CustomIcons.FILTER
override val tint: Int
get() = filter.color ?: 0
override fun areItemsTheSame(other: FilterListItem): Boolean {
return other is CustomFilter && id == other.id
}
}

@ -0,0 +1,8 @@
package com.todoroo.astrid.api
import kotlinx.parcelize.Parcelize
@Parcelize
class EmptyFilter(override val sql: String? = "WHERE 0", override val title: String? = null) : Filter {
override fun areItemsTheSame(other: FilterListItem): Boolean = false
}

@ -1,247 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.api;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.MenuRes;
import androidx.annotation.NonNull;
import com.todoroo.andlib.sql.QueryTemplate;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* A <code>FilterListFilter</code> allows users to display tasks that have something in common.
*
* <p>A plug-in can expose new <code>FilterListFilter</code>s to the system by responding to the
* <code>com.todoroo.astrid.GET_FILTERS</code> broadcast intent.
*
* @author Tim Su <tim@todoroo.com>
*/
public class Filter extends FilterListItem {
/** Parcelable Creator Object */
public static final Parcelable.Creator<Filter> CREATOR =
new Parcelable.Creator<>() {
/** {@inheritDoc} */
@Override
public Filter createFromParcel(Parcel source) {
Filter item = new Filter();
item.readFromParcel(source);
return item;
}
/** {@inheritDoc} */
@Override
public Filter[] newArray(int size) {
return new Filter[size];
}
};
/**
* Values to apply to a task when quick-adding a task from this filter. For example, when a user
* views tasks tagged 'ABC', the tasks they create should also be tagged 'ABC'. If set to null, no
* additional values will be stored for a task. Can use {@link PermaSql}
*/
public final Map<String, Object> valuesForNewTasks = new HashMap<>();
/**
* {@link PermaSql} query for this filter. The query will be appended to the select statement
* after "<code>SELECT fields FROM table %s</code>". It is recommended that you use a {@link
* QueryTemplate} to construct your query.
*
* <p>Examples:
*
* <ul>
* <li><code>"WHERE completionDate = 0"</code>
* <li><code>"INNER JOIN " +
* Constants.TABLE_METADATA + " ON metadata.task = tasks.id WHERE
* metadata.namespace = " + NAMESPACE + " AND metadata.key = 'a' AND
* metadata.value = 'b' GROUP BY tasks.id ORDER BY tasks.title"</code>
* </ul>
*/
public String sqlQuery;
/**
* Field for holding a modified sqlQuery based on sqlQuery. Useful for adjusting query for
* sort/subtasks without breaking the equality checking based on sqlQuery.
*/
private String filterOverride;
public Filter(String listingTitle, QueryTemplate sqlQuery) {
this(listingTitle, sqlQuery, Collections.emptyMap());
}
/**
* Utility constructor for creating a Filter object
*
* @param listingTitle Title of this item as displayed on the lists page, e.g. Inbox
* @param sqlQuery SQL query for this list (see {@link #sqlQuery} for examples).
*/
protected Filter(
String listingTitle, QueryTemplate sqlQuery, Map<String, Object> valuesForNewTasks) {
this(listingTitle, sqlQuery == null ? null : sqlQuery.toString(), valuesForNewTasks);
}
/**
* Utility constructor for creating a Filter object
*
* @param listingTitle Title of this item as displayed on the lists page, e.g. Inbox
* @param sqlQuery SQL query for this list (see {@link #sqlQuery} for examples).
*/
Filter(String listingTitle, String sqlQuery, Map<String, Object> valuesForNewTasks) {
this.listingTitle = listingTitle;
this.sqlQuery = sqlQuery;
this.filterOverride = null;
if (valuesForNewTasks != null) {
this.valuesForNewTasks.putAll(valuesForNewTasks);
}
}
protected Filter() {}
public String getOriginalSqlQuery() {
return sqlQuery;
}
public String getSqlQuery() {
if (filterOverride != null) {
return filterOverride;
}
return sqlQuery;
}
// --- parcelable
public void setFilterQueryOverride(String filterOverride) {
this.filterOverride = filterOverride;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((sqlQuery == null) ? 0 : sqlQuery.hashCode());
result = prime * result + ((listingTitle == null) ? 0 : listingTitle.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Filter other = (Filter) obj;
if (sqlQuery == null) {
if (other.sqlQuery != null) {
return false;
}
} else if (!sqlQuery.equals(other.sqlQuery)) {
return false;
}
if (listingTitle == null) {
return other.listingTitle == null;
} else return listingTitle.equals(other.listingTitle);
}
@Override
public Type getItemType() {
return Type.ITEM;
}
/** {@inheritDoc} */
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(""); // old title
dest.writeString(sqlQuery);
dest.writeMap(valuesForNewTasks);
}
@Override
protected void readFromParcel(Parcel source) {
super.readFromParcel(source);
source.readString(); // old title
sqlQuery = source.readString();
source.readMap(valuesForNewTasks, getClass().getClassLoader());
}
public boolean supportsAstridSorting() {
return false;
}
public boolean supportsManualSort() {
return false;
}
public boolean supportsHiddenTasks() {
return true;
}
public boolean supportsSubtasks() {
return true;
}
public boolean supportsSorting() {
return true;
}
public boolean isWritable() {
return !isReadOnly();
}
public boolean isReadOnly() {
return false;
}
public boolean hasBeginningMenu() {
return getBeginningMenu() != 0;
}
public @MenuRes int getBeginningMenu() {
return 0;
}
public boolean hasMenu() {
return getMenu() != 0;
}
public @MenuRes int getMenu() {
return 0;
}
@Override
public String toString() {
return "Filter{"
+ "sqlQuery='"
+ sqlQuery
+ '\''
+ ", filterOverride='"
+ filterOverride
+ '\''
+ ", valuesForNewTasks="
+ valuesForNewTasks
+ '}';
}
@Override
public boolean areItemsTheSame(@NonNull FilterListItem other) {
return other instanceof Filter && Objects.equals(sqlQuery, ((Filter) other).sqlQuery);
}
@Override
public boolean areContentsTheSame(@NonNull FilterListItem other) {
return super.areContentsTheSame(other)
&& Objects.equals(sqlQuery, ((Filter) other).sqlQuery);
}
}

@ -0,0 +1,53 @@
package com.todoroo.astrid.api
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.tasks.data.NO_COUNT
import org.tasks.data.NO_ORDER
interface Filter : FilterListItem, Parcelable {
val valuesForNewTasks: String?
get() = null
val sql: String?
val icon: Int
get() = -1
val title: String?
val tint: Int
get() = 0
@Deprecated("Remove this")
val count: Int
get() = NO_COUNT
val order: Int
get() = NO_ORDER
override val itemType: FilterListItem.Type
get() = FilterListItem.Type.ITEM
val isReadOnly: Boolean
get() = false
val isWritable: Boolean
get() = !isReadOnly
fun supportsManualSort(): Boolean = false
fun supportsHiddenTasks(): Boolean = true
fun supportsSubtasks(): Boolean = true
fun supportsSorting(): Boolean = true
}
@Deprecated("Use manual ordering")
interface AstridOrderingFilter : Filter {
var filterOverride: String?
fun getSqlQuery(): String = filterOverride ?: sql!!
}
@Parcelize
data class FilterImpl(
override val title: String? = null,
override val sql: String? = null,
override val valuesForNewTasks: String? = null,
override val icon: Int = -1,
override val tint: Int = 0,
) : Filter {
override fun areItemsTheSame(other: FilterListItem): Boolean {
return other is Filter && sql == other.sql
}
}

@ -1,110 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.api;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import org.tasks.R;
import java.util.Objects;
/**
* Represents an item displayed by Astrid's FilterListActivity
*
* @author Tim Su <tim@todoroo.com>
*/
public abstract class FilterListItem implements Parcelable {
public static final int NO_ORDER = -1;
/** Title of this item displayed on the Filters page */
public String listingTitle = null;
public long id = 0;
public int icon = -1;
public int tint = 0;
public int count = -1;
public int principals = 0;
public int order = NO_ORDER;
public abstract Type getItemType();
@Override
public int describeContents() {
return 0;
}
/** {@inheritDoc} */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(listingTitle);
dest.writeInt(icon);
dest.writeInt(tint);
dest.writeInt(count);
dest.writeInt(order);
dest.writeLong(id);
}
// --- parcelable helpers
/** Utility method to read FilterListItem properties from a parcel. */
protected void readFromParcel(Parcel source) {
listingTitle = source.readString();
icon = source.readInt();
tint = source.readInt();
count = source.readInt();
order = source.readInt();
id = source.readLong();
}
public long getId() {
return id;
}
public boolean areItemsTheSame(@NonNull FilterListItem other) {
return getClass().equals(other.getClass()) && id == other.id;
}
public boolean areContentsTheSame(@NonNull FilterListItem other) {
return Objects.equals(listingTitle, other.listingTitle)
&& icon == other.icon
&& tint == other.tint
&& count == other.count
&& order == other.order
&& principals == other.principals;
}
@Override
public String toString() {
return "FilterListItem{" +
"listingTitle='" + listingTitle + '\'' +
", id=" + id +
", icon=" + icon +
", tint=" + tint +
", count=" + count +
", principals=" + principals +
", order=" + order +
'}';
}
public enum Type {
ITEM(R.layout.filter_adapter_row),
ACTION(R.layout.filter_adapter_action),
SUBHEADER(R.layout.filter_adapter_subheader),
SEPARATOR(R.layout.filter_adapter_separator);
public final int layout;
Type(@LayoutRes int layout) {
this.layout = layout;
}
}
}

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

Loading…
Cancel
Save