Compare commits

...

285 Commits
13.6.2 ... 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/
3 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/
3 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/
3 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 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

@ -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,8 +1,112 @@
### 13.6.2 (2023-11-30)
### 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-11-27)
### 13.6.1 (2023-10-27)
* Push pending changes when app is backgrounded
* Don't require internet connection for DAVx5/EteSync/DecSync sync

@ -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 = 130604
versionName = "13.6.2"
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)

@ -4,10 +4,10 @@ import androidx.test.InstrumentationRegistry
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

@ -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() }!!, null)
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

@ -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

@ -8,12 +8,12 @@ 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(
@ -33,7 +33,7 @@ class PlayServices @Inject constructor(
val request = requestReview()
launchReview(activity, request)
}
preferences.lastReviewRequest = now()
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
@ -529,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"/>
@ -598,6 +597,8 @@
</intent-filter>
</receiver>
<receiver android:name="org.tasks.jobs.NotificationReceiver" />
<activity
android:name=".auth.MicrosoftAuthenticationActivity"
android:theme="@style/TranslucentDialog"/>

@ -140,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() {
@ -156,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;
}

@ -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
* ====================================================================== */

@ -8,60 +8,71 @@ package com.todoroo.astrid.activity
import android.content.Intent
import android.os.Bundle
import android.view.View
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 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.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.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.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 {
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
@ -71,12 +82,17 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
@Inject lateinit var eventBus: MainActivityEventBus
@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?) {
@ -86,39 +102,135 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
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)
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)) }
REQUEST_NEW_LIST ->
if (resultCode == RESULT_OK && data != null) {
getParcelableExtra(data, OPEN_FILTER, Filter::class.java)?.let {
viewModel.setFilter(it)
}
}
NavigationDrawerFragment.REQUEST_NEW_PLACE ->
if (resultCode == RESULT_OK) {
data
?.getParcelableExtra<Place>(LocationPickerActivity.EXTRA_PLACE)
?.let { startActivity(getTaskListIntent(this, PlaceFilter(it))) }
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)
}
@ -127,191 +239,74 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
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.title}: $it" }}
LOAD_FILTER: $loadFilter
OPEN_TASK: ${intent.getParcelableExtra<Task>(OPEN_TASK)}
CREATE_TASK: ${intent.hasExtra(CREATE_TASK)}
taskListFragment: ${taskListFragment?.getFilter()?.let { "${it.title}: $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)
}
}
}
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
private fun updateSystemBars(filter: Filter) {
with (getFilterColor(filter)) {
applyToNavigationBar(this@MainActivity)
applyTaskDescription(this@MainActivity, filter.title ?: getString(R.string.app_name))
}
val newFilter = taskListFragment.getFilter()
if (!force && filter == newFilter) {
return
}
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?.title ?: 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()
@ -330,78 +325,35 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
}
}
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?
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()
}
}
}
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 OPEN_FILTER = "open_filter" // $NON-NLS-1$
@ -413,16 +365,17 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
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

@ -35,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
@ -42,10 +43,11 @@ 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.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
@ -69,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
@ -139,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) {
@ -150,6 +155,9 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
discardButtonClick()
}
}
if (atLeastOreoMR1()) {
activity?.setShowWhenLocked(preferences.showEditScreenWithoutUnlock)
}
binding = FragmentTaskEditBinding.inflate(inflater)
val view: View = binding.root
@ -305,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)
@ -514,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"

@ -19,7 +19,7 @@ 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
@ -27,6 +27,7 @@ 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
@ -34,6 +35,7 @@ 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
@ -41,14 +43,15 @@ 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 com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.QueryTemplate
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
@ -63,11 +66,9 @@ 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
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
@ -77,24 +78,30 @@ import kotlinx.coroutines.NonCancellable
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.Tag
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
@ -104,19 +111,21 @@ 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
@ -135,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
@ -158,21 +166,18 @@ 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 lateinit var search: MenuItem
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 ->
@ -182,7 +187,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
activity?.recreate()
}
if (data.getBooleanExtra(SortSettingsActivity.EXTRA_CHANGED_GROUP, false)) {
taskAdapter.clearCollapsed()
listViewModel.clearCollapsed()
}
listViewModel.invalidate()
}
@ -195,10 +200,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
val data = result.data ?: return@registerForActivityResult
when (data.action) {
ACTION_DELETED ->
openFilter(BuiltInFilterExposer.getMyTasksFilter(resources))
mainViewModel.setFilter(BuiltInFilterExposer.getMyTasksFilter(resources))
ACTION_RELOAD ->
IntentCompat.getParcelableExtra(data, MainActivity.OPEN_FILTER, Filter::class.java)?.let {
openFilter(it)
mainViewModel.setFilter(it)
}
}
}
@ -227,16 +232,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
}
override fun onAttach(context: Context) {
super.onAttach(requireContext())
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.putLongArray(EXTRA_COLLAPSED, taskAdapter.getCollapsed().toLongArray())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -247,15 +246,21 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
.launchIn(viewLifecycleOwner.lifecycleScope)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(requireActivity(), 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
@ -273,23 +278,24 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
// set up list adapters
taskAdapter = taskAdapterProvider.createTaskAdapter(filter)
taskAdapter.setCollapsed(savedInstanceState?.getLongArray(EXTRA_COLLAPSED))
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
}
}
}
@ -323,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()
@ -333,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)
}
@ -402,21 +430,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
menu.findItem(R.id.menu_expand_subtasks).isVisible = false
}
menu.findItem(R.id.menu_voice_add).isVisible = device.voiceInputAvailable() && filter.isWritable
search = binding.toolbar.menu.findItem(R.id.menu_search).also {
it.setOnActionExpandListener(this)
it.setOnQueryTextListener(this)
}
search = binding.toolbar.menu.findItem(R.id.menu_search).setOnActionExpandListener(this)
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))
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_voice_add -> {
@ -459,11 +476,28 @@ 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 -> {
@ -535,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)
@ -647,11 +676,11 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
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)
listViewModel.setSearchQuery("")
if (preferences.isTopAppBar) {
binding.toolbar.menu.forEach { it.isVisible = false }
@ -660,7 +689,8 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
onBackPressed.isEnabled = false
search.setOnQueryTextListener(null)
listViewModel.setFilter(filter)
listViewModel.setSearchQuery(null)
if (preferences.isTopAppBar) {
setupMenu(binding.toolbar)
@ -669,7 +699,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
override fun onQueryTextSubmit(query: String): Boolean {
openFilter(requireContext().createSearchQuery(query.trim()))
mainViewModel.setFilter(requireContext().createSearchQuery(query.trim()))
search.collapseActionView()
return true
}
@ -826,11 +856,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
}
}
interface TaskListFragmentCallbackHandler {
suspend fun onTaskListItemClicked(task: Task?)
fun onNavigationIconClicked()
}
val isActionModeActive: Boolean
get() = mode != null
@ -857,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()
@ -878,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()
}
}
@ -909,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)
}
}
@ -942,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
@ -965,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
)
@ -989,19 +1017,17 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
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_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_TAG_TASKS = 10106
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,37 +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 org.tasks.databinding.FilterAdapterActionBinding
import org.tasks.filters.NavigationDrawerAction
import org.tasks.themes.DrawableUtil
class ActionViewHolder internal constructor(
private val context: Context,
itemView: View,
private val onClick: (NavigationDrawerAction) -> 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: NavigationDrawerAction) {
text.text = filter.title
icon.setImageDrawable(DrawableUtil.getWrapped(context, filter.icon))
row.setOnClickListener {
onClick.invoke(filter)
}
}
}

@ -1,17 +1,17 @@
package com.todoroo.astrid.adapter
import com.todoroo.andlib.utility.DateUtilities
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.Collections
import kotlin.math.abs
@ -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) {

@ -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) {

@ -18,7 +18,6 @@ import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.channels.Channel
import org.tasks.activities.DragAndDropDiffer
import org.tasks.billing.Inventory
import org.tasks.filters.NavigationDrawerAction
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.themes.ColorProvider
import java.util.LinkedList
@ -35,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())
@ -58,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)
@ -70,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 as Filter, item == selected, max(item.count, 0))
FilterListItem.Type.ACTION -> (holder as ActionViewHolder).bind(item as NavigationDrawerAction)
(holder as FilterViewHolder).bind(item as Filter, false, max(item.count, 0))
FilterListItem.Type.SUBHEADER ->
(holder as SubheaderViewHolder).bind((item as NavigationDrawerSubheader))
else -> {}
@ -94,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() {

@ -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,8 +6,9 @@ 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.CALDAV
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.ETESYNC
@ -16,7 +17,6 @@ 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(
@ -42,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
@ -55,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"
}
}

@ -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)
taskDao.save(task.task)
task.parent = newParent
taskDao.save(task.task, null)
}
}

@ -8,7 +8,7 @@ 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
@ -16,22 +16,22 @@ 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 is AstridOrderingFilter && preferences.isAstridSort) {

@ -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,21 +1,21 @@
package com.todoroo.astrid.api
import com.todoroo.andlib.sql.Criterion.Companion.and
import com.todoroo.andlib.sql.Join.Companion.left
import com.todoroo.andlib.sql.QueryTemplate
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 com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.data.Task
import kotlinx.parcelize.Parcelize
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavTask
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
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 var count: Int = NO_COUNT,
override val count: Int = NO_COUNT,
) : Filter {
override val title: String?
get() = calendar.name

@ -1,13 +1,11 @@
package com.todoroo.astrid.api
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import kotlinx.parcelize.Parcelize
import org.tasks.themes.CustomIcons
@Parcelize
data class CustomFilter(
val filter: org.tasks.data.Filter,
override var count: Int = NO_COUNT,
val filter: org.tasks.data.entity.Filter,
) : Filter {
override val title: String?
get() = filter.title

@ -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,8 +1,9 @@
package com.todoroo.astrid.api
import android.os.Parcelable
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import kotlinx.parcelize.Parcelize
import org.tasks.data.NO_COUNT
import org.tasks.data.NO_ORDER
interface Filter : FilterListItem, Parcelable {
val valuesForNewTasks: String?
@ -14,7 +15,8 @@ interface Filter : FilterListItem, Parcelable {
val tint: Int
get() = 0
@Deprecated("Remove this")
var count: Int
val count: Int
get() = NO_COUNT
val order: Int
get() = NO_ORDER
override val itemType: FilterListItem.Type
@ -28,11 +30,6 @@ interface Filter : FilterListItem, Parcelable {
fun supportsHiddenTasks(): Boolean = true
fun supportsSubtasks(): Boolean = true
fun supportsSorting(): Boolean = true
companion object {
const val NO_ORDER = -1
const val NO_COUNT = -1
}
}
@Deprecated("Use manual ordering")
@ -49,7 +46,6 @@ data class FilterImpl(
override val valuesForNewTasks: String? = null,
override val icon: Int = -1,
override val tint: Int = 0,
override var count: Int = NO_COUNT,
) : Filter {
override fun areItemsTheSame(other: FilterListItem): Boolean {
return other is Filter && sql == other.sql

@ -10,8 +10,6 @@ interface FilterListItem {
enum class Type(@param:LayoutRes val layout: Int) {
ITEM(R.layout.filter_adapter_row),
ACTION(R.layout.filter_adapter_action),
SUBHEADER(R.layout.filter_adapter_subheader),
SEPARATOR(R.layout.filter_adapter_separator)
}
}

@ -1,21 +1,21 @@
package com.todoroo.astrid.api
import com.todoroo.andlib.sql.Criterion.Companion.and
import com.todoroo.andlib.sql.Join.Companion.left
import com.todoroo.andlib.sql.QueryTemplate
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 com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import kotlinx.parcelize.Parcelize
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavTask
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask
import org.tasks.data.GoogleTask
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.data.NO_COUNT
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
@Parcelize
data class GtasksFilter(
val list: CaldavCalendar,
override var count: Int = NO_COUNT,
override val count: Int = NO_COUNT,
) : Filter {
override val title: String?
get() = list.name

@ -7,6 +7,7 @@
package com.todoroo.astrid.api;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils2.currentTimeMillis;
import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.time.DateTime;
@ -51,7 +52,7 @@ public final class PermaSql {
/** Replace placeholder strings with actual */
public static String replacePlaceholdersForQuery(String value) {
if (value.contains(VALUE_NOW)) {
value = value.replace(VALUE_NOW, Long.toString(DateUtilities.now()));
value = value.replace(VALUE_NOW, Long.toString(currentTimeMillis()));
}
if (value.contains(VALUE_EOD)
|| value.contains(VALUE_EOD_DAY_AFTER)
@ -74,7 +75,7 @@ public final class PermaSql {
public static String replacePlaceholdersForNewTask(String value) {
if (value.contains(VALUE_NOW)) {
value = value.replace(VALUE_NOW, Long.toString(DateUtilities.now()));
value = value.replace(VALUE_NOW, Long.toString(currentTimeMillis()));
}
if (value.contains(VALUE_EOD)
|| value.contains(VALUE_EOD_DAY_AFTER)

@ -1,24 +1,22 @@
package com.todoroo.astrid.api
import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.Query
import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.data.Task
import org.tasks.data.sql.Criterion
import org.tasks.data.sql.Join
import org.tasks.data.sql.Query
import org.tasks.data.sql.QueryTemplate
import org.tasks.data.entity.Task
import kotlinx.parcelize.Parcelize
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavTask
import org.tasks.data.Geofence
import org.tasks.data.Place
import org.tasks.data.Tag
import org.tasks.data.UserActivity
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask
import org.tasks.data.entity.Geofence
import org.tasks.data.entity.Place
import org.tasks.data.entity.Tag
import org.tasks.data.entity.UserActivity
@Parcelize
data class SearchFilter(
override val title: String,
val query: String,
override var count: Int = NO_COUNT,
) : Filter {
override val sql: String
get() {

@ -1,20 +1,20 @@
package com.todoroo.astrid.api
import com.todoroo.andlib.sql.Criterion.Companion.and
import com.todoroo.andlib.sql.Join.Companion.inner
import com.todoroo.andlib.sql.QueryTemplate
import org.tasks.data.sql.Criterion.Companion.and
import org.tasks.data.sql.Join.Companion.inner
import org.tasks.data.sql.QueryTemplate
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.data.Task
import org.tasks.data.entity.Task
import kotlinx.parcelize.Parcelize
import org.tasks.data.Tag
import org.tasks.data.TagData
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.data.NO_COUNT
import org.tasks.data.entity.Tag
import org.tasks.data.entity.TagData
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
@Parcelize
data class TagFilter(
val tagData: TagData,
override var count: Int = NO_COUNT,
override val count: Int = NO_COUNT,
override var filterOverride: String? = null,
) : AstridOrderingFilter {
override val title: String?

@ -7,43 +7,36 @@ package com.todoroo.astrid.core
import android.content.Context
import android.content.res.Resources
import com.todoroo.andlib.sql.Criterion.Companion.and
import com.todoroo.andlib.sql.Criterion.Companion.or
import com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.astrid.api.AstridOrderingFilter
import org.tasks.data.sql.Criterion.Companion.and
import org.tasks.data.sql.Criterion.Companion.or
import org.tasks.data.sql.Join
import org.tasks.data.sql.QueryTemplate
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.FilterImpl
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.timers.TimerPlugin
import org.tasks.data.entity.Task
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavTask
import org.tasks.data.TaskDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask
import org.tasks.data.dao.TaskDao
import org.tasks.filters.MyTasksFilter
import org.tasks.filters.NotificationsFilter
import org.tasks.filters.RecentlyModifiedFilter
import org.tasks.filters.SnoozedFilter
import org.tasks.filters.TimerFilter
import org.tasks.filters.TodayFilter
import org.tasks.preferences.Preferences
import javax.inject.Inject
/**
* Exposes Astrid's built in filters to the NavigationDrawerFragment
*
* @author Tim Su <tim></tim>@todoroo.com>
*/
class BuiltInFilterExposer @Inject constructor(
@param:ApplicationContext private val context: Context,
private val preferences: Preferences,
private val taskDao: TaskDao) {
private val taskDao: TaskDao
) {
val myTasksFilter: Filter
get() {
return getMyTasksFilter(context.resources)
}
get() = getMyTasksFilter(context.resources)
suspend fun filters(): List<Filter> {
val r = context.resources
@ -58,7 +51,7 @@ class BuiltInFilterExposer @Inject constructor(
filters.add(getSnoozedFilter(r))
}
if (taskDao.activeTimers() > 0) {
filters.add(TimerPlugin.createFilter(context))
filters.add(getTimerFilter(r))
}
if (taskDao.hasNotifications() > 0) {
filters.add(getNotificationsFilter(context))
@ -67,9 +60,7 @@ class BuiltInFilterExposer @Inject constructor(
}
companion object {
fun getMyTasksFilter(r: Resources): AstridOrderingFilter {
return MyTasksFilter(r.getString(R.string.BFE_Active))
}
fun getMyTasksFilter(r: Resources) = MyTasksFilter(r.getString(R.string.BFE_Active))
fun getTodayFilter(r: Resources): Filter {
return TodayFilter(r.getString(R.string.today))
@ -148,10 +139,12 @@ class BuiltInFilterExposer @Inject constructor(
)
fun getRecentlyModifiedFilter(r: Resources) =
RecentlyModifiedFilter(r.getString(R.string.BFE_Recent))
RecentlyModifiedFilter(r.getString(R.string.BFE_Recent))
fun getSnoozedFilter(r: Resources) = SnoozedFilter(r.getString(R.string.filter_snoozed))
fun getTimerFilter(r: Resources) = TimerFilter(r.getString(R.string.TFE_workingOn))
fun getNotificationsFilter(context: Context) = NotificationsFilter(context.getString(R.string.notifications))
@JvmStatic

@ -5,7 +5,7 @@ import com.todoroo.astrid.api.BooleanCriterion
import com.todoroo.astrid.api.CustomFilterCriterion
import com.todoroo.astrid.api.MultipleSelectCriterion
import com.todoroo.astrid.api.TextInputCriterion
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.UUIDHelper
class CriterionInstance {
lateinit var criterion: CustomFilterCriterion

@ -6,6 +6,7 @@
package com.todoroo.astrid.core;
import static org.tasks.data.dao.CaldavDaoKt.APPLE_EPOCH;
import static org.tasks.db.QueryUtils.showCompleted;
import static org.tasks.db.QueryUtils.showHidden;
@ -13,12 +14,12 @@ import android.annotation.SuppressLint;
import androidx.annotation.Nullable;
import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.OrderType;
import com.todoroo.astrid.data.Task;
import org.tasks.data.sql.Functions;
import org.tasks.data.sql.Order;
import org.tasks.data.sql.OrderType;
import org.tasks.data.entity.Task;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.entity.CaldavCalendar;
import org.tasks.preferences.QueryPreferences;
import java.util.Locale;
@ -44,7 +45,6 @@ public class SortHelper {
public static final int SORT_COMPLETED = 10;
public static final int SORT_MANUAL = 11;
public static final long APPLE_EPOCH = 978307200000L; // 1/1/2001 GMT
@SuppressLint("DefaultLocale")
public static final String CALDAV_ORDER_COLUMN =
String.format(Locale.US, "IFNULL(tasks.`order`, (tasks.created - %d) / 1000)", APPLE_EPOCH);

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

Loading…
Cancel
Save