Compare commits

..

No commits in common. 'main' and '11.9.2' have entirely different histories.
main ... 11.9.2

@ -1,4 +1,5 @@
github: abaker
liberapay: tasks
open_collective: tasks
patreon: tasks
custom: tasks.org/donate

@ -1,49 +0,0 @@
name: Assemble bundle
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
jobs:
check:
uses: ./.github/workflows/check.yml
bundle:
runs-on: ubuntu-latest
needs: [ check ]
steps:
- name: Decode Keystore
run: |
echo ${{ secrets.KEY_STORE }} | base64 -di > "${RUNNER_TEMP}"/keystore.jks
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Bundle
env:
KEY_PATH: ${{ runner.temp }}/keystore.jks
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
MAPBOX_KEY: ${{ secrets.MAPBOX_KEY }}
GOOGLE_KEY: ${{ secrets.GOOGLE_KEY }}
run: bundle exec fastlane bundle
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release
path: |
app/build/outputs/**
wear/build/outputs/**

@ -1,91 +0,0 @@
name: Run automated checks
on:
pull_request:
workflow_call:
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Lint checks
run: bundle exec fastlane lint
- name: Archive lint reports
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: lint-reports
path: app/build/reports/*.html
test:
runs-on: ubuntu-latest
strategy:
matrix:
flavor: [Googleplay, Generic]
api-level: [29]
steps:
- name: checkout
uses: actions/checkout@v4
- name: Set up JDK 17
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: AVD cache
# uses: actions/cache@v4
# id: avd-cache
# with:
# path: |
# ~/.android/avd/*
# ~/.android/adb*
# key: avd-${{ matrix.api-level }}
#
# - name: create AVD and generate snapshot for caching
# if: steps.avd-cache.outputs.cache-hit != 'true'
# uses: reactivecircus/android-emulator-runner@v2
# with:
# api-level: ${{ matrix.api-level }}
# force-avd-creation: false
# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
# disable-animations: false
# script: echo "Generated AVD snapshot for caching."
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: ./gradlew -Pcoverage app:test${{ matrix.flavor }}DebugUnitTest app:connected${{ matrix.flavor }}DebugAndroidTest
- name: Upload test reports
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: test-reports-${{ matrix.flavor }}
path: app/build/reports/**

@ -0,0 +1,22 @@
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Check licenses
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: '11.0.8'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew checkLicense

@ -0,0 +1,19 @@
name: Lint
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: '11.0.8'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew :app:lintGenericRelease :app:lintGoogleplayRelease

@ -0,0 +1,23 @@
name: Run tests
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: '11.0.8'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew -Pcoverage :app:jacocoTestReportGoogleplayDebug :app:createGoogleplayDebugAndroidTestCoverageReport
- name: upload coverage
uses: codecov/codecov-action@v1
with:
directory: ./app/build

2
.gitignore vendored

@ -1,4 +1,3 @@
.kotlin
.idea
*.iml
.gradle
@ -10,4 +9,3 @@ local.properties
Thumbs.db
/captures/
/fastlane/report.xml
/compose-metrics/

@ -1 +0,0 @@
3.3.5

@ -1,70 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="wear" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false" singleton="true">
<module name="tasks.Tasks.wear.main" />
<option name="DEPLOY" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" />
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
<option name="CLEAR_APP_STORAGE" value="false" />
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" />
<option name="RESTORE_ENABLED" value="false" />
<option name="RESTORE_FILE" value="" />
<option name="CLEAR_LOGCAT" value="true" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="" />
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

@ -1,474 +1,372 @@
### 14.0.1 (2024-11-09)
### 11.9.2 (2021-03-29)
* Fix widget crash
* Fix date translation issue - Thanks @mhmdanas!
* Fix misc translation strings - Thanks J. Lavoie!
* Update translations
* Hungarian - Kaci
* Italian - @ppasserini
* Kannada - @historicattle
* Marathi - @historicattle
* Spanish - gallegonovato
* Swedish - @bittin
### 14.0 (2024-11-05)
* Wear OS support (Google Play only)
* Move drawer items to top unless searching
* Fix drawer item layout issues
* Update translations
* Brazilian Portuguese - Nicolas Suzuki, pogoyar888
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - 大王叫我来巡山
* Chinese (Traditional) - hugoalh
* Dutch - Luna, @fvbommel
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - @p-rogalski, @franconian
* Hungarian - Kaci
* Italian - @ppasserini
* Spanish - gallegonovato
* Swedish - @bittin
* Turkish - @oersen
* German - @franconian, Achim Schumacher, J. Lavoie
* Hungarian - kaciokos
* Indonesian - when we were sober
* Italian - @Fs00
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - @emintufan
* Ukrainian - @IhorHordiichuk
### 13.11.2 (2024-09-29)
### 11.9.1 (2021-03-25)
* Target Android 14
* Fix crash in location picker [#2990](https://github.com/tasks/tasks/issues/2990)
* Fix SQLite crash [#3045](https://github.com/tasks/tasks/issues/3045)
* Update translations
* Arabic - @sanabel-al-firdaws
* Belarusian - @katalim
* Brazilian Portuguese - Jose Delvani
* Catalan - raulmagdalena, @truita
* Chinese (Traditional) - @abc0922001
* Croatian - @milotype
* Czech - atmosphericignition
* Danish - Tntdruid, Luna
* Dutch - @VIMVa
* Esperanto - Don Zouras
* Estonian - @dermezl
* German - @Atalanttore, @tct123
* Italian - @ppasserini
* Norwegian Bokmål - @RonnyAL
* Swedish - @JonatanWick, @bittin
### 13.11.1 (2024-07-15)
* Fix crash when collapsing list picker sections
* Fix crash in database migration
* Enabled Managed DAVx5
* Open documentation links in custom tabs
* Fix crash in Mapbox reverse geocoder
* Increase 'Add subtask' touch target
* Update translations
* Bulgarian - @StoyanDimitrov
### 13.11 (2024-07-14)
* New icon picker with over 2,100 icons! (pro feature)
* Fix Todo Agenda Widget integration [todoagenda/#145](https://github.com/andstatus/todoagenda/issues/145)
* Fix menu search bar on Android 10 and below [#2966](https://github.com/tasks/tasks/issues/2966)
* Arabic - @mhmdanas
* German - Achim Schumacher
* Hungarian - kaciokos
* Italian - @Fs00
* Turkish - @emintufan
### 11.9 (2021-03-20)
* New calendar and clock pickers
* New preference to default to text input for date and time
* Fix issue causing Tasks to use wrong search provider
* Fix crash when Nextcloud/ownCloud don't send list owner
* Update translations
* Brazilian Portuguese - Jose Delvani
* Bulgarian - @StoyanDimitrov
* Catalan - @Seveorr, @jtorrensamer
* Chinese (Simplified) - 大王叫我来巡山
* Chinese (Traditional) - hugoalh
* Basque - Sergio Varela
* Croatian - @milotype
* Dutch - @fvbommel
* French - @FlorianLeChat
* Spanish - gallegonovato
* Turkish - @oersen
* German - Achim Schumacher
* Hungarian - kaciokos
* Indonesian - when we were sober
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Ukrainian - @IhorHordiichuk
### 13.10 (2024-07-05)
### 11.8 (2021-03-15)
* Add search bar to drawer
* Add search bar to list picker
* Move 'Manage drawer' to ⚙️ > Navigation drawer
* Android 13+ users must grant additional reminder permissions
* Fix completing task multiple times from notification
* Fix deleting new subtasks from edit screen
* ~~Enable Managed DAVx5~~
* CalDAV: Send shared list invites
* Compatible with Tasks.org, Nextcloud, ownCloud, and sabre/dav
* Show shared list invite status in list settings
* Fix drawer count when list is shared with 2+ users
* Removed legacy EteSync v1 list management features
* Dropped support for Android 6.0
* Update translations
* Arabic - @islam2hamy
* Brazilian Portuguese - Jose Delvani
* Chinese (Simplified) - 大王叫我来巡山
* Chinese (Traditional) - hugoalh
* Croatian - @milotype
* Finnish - Rami Lehtinen, @CSharpest
* German - min7-i
* Spanish - gallegonovato
* Turkish - @oersen
### 13.9.9 (2024-05-30)
* Fix import backup crashes
* Fix showing completed subtasks in edit screen
### 13.9.7 (2024-05-23)
* Add default reminders when adding start/due dates to existing tasks [#1846](https://github.com/tasks/tasks/issues/1846)
* Fix import backup crash
### 13.9.6 (2024-05-18)
* Arabic - @mhmdanas
* Dutch - @fvbommel
* Esperanto - @jakubfabijan
* French - @FlorianLeChat
* German - @Jerome2103
* Hungarian - kaciokos
* Indonesian - when we were sober, @andhikapangestu29
* Norwegian Bokmål - @comradekingu
* Polish - @doegedomita
* Portuguese - @Jerome2103
* Spanish - @FlorianLeChat
* Turkish - Oğuz Ersen
* Ukrainian - @IhorHordiichuk
* 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
### 11.7 (2021-03-08)
### 13.9 (2024-05-01)
* @elmuffo: Add swipe-to-snooze [#2839](https://github.com/tasks/tasks/pull/2839)
* @IlyaBizyaev: Add option to use quick tile without unlocking device [#2847](https://github.com/tasks/tasks/pull/2847)
* @liz-desartiges: Add support for Z Flip 5 cover screen [#2843](https://github.com/tasks/tasks/pull/2843)
* @purushyb: Fix drawer not updating after editing items [#2855](https://github.com/tasks/tasks/pull/2855)
* @hady-exc: Migrate tag picker screen to Compose [#2849](https://github.com/tasks/tasks/pull/2849)
* @yurtpage: Add Russian app store description [#2848](https://github.com/tasks/tasks/pull/2848)
* Fix duplicate notifications [#2835](https://github.com/tasks/tasks/issues/2835)
* Fix adding '(Completed)' to calendar entries [#2832](https://github.com/tasks/tasks/issues/2832)
* Fix hiding empty items from drawer [#2831](https://github.com/tasks/tasks/issues/2831)
* Exclude old snoozed tasks from snoozed task filter
* Update translations
* Brazilian Portuguese - @mayhmemo, @gorgonun
* Chinese (Simplified) - 大王叫我来巡山
* Croatian - @milotype
* Esperanto - Don Zouras
* French - Lionel HANNEQUIN
* German - sorifukobexomajepasiricupuva33, min7-i
* Portuguese - @fparri, @laralem
* Spanish - gallegonovato
* Swedish - @JonatanWick
* Turkish - @emintufan, @oersen
### 13.8.1 (2024-03-24)
* Fix copy causing duplicate Google Tasks
* Fix navigation drawer crash
* Fix backup import dropping tasks
### 13.8 (2024-03-22)
* Dynamic widget theme (name-your-price subscription required)
* Replace 'until' with 'ends on' for repeating tasks [#2797](https://github.com/tasks/tasks/pull/2797) - @akwala
* Fix loading selected list on startup [#2777](https://github.com/tasks/tasks/issues/2777)
* Fix repeating tasks ending one day early
* Fix repeating task crash
* Fix backup import crash
* Fix Astrid manual ordering crash in widget
* Update translations
* Brazilian Portuguese - @mayhmemo
* Bulgarian - @StoyanDimitrov
* Catalan - @ferranpujolcamins
* Chinese (Simplified) - 大王叫我来巡山
* Croatian - @milotype
* Czech - Odweta
* German - @macpac59
* Italian - @ppasserini
* Spanish - gallegonovato
* Swedish - @bittin
* Ukrainian - @IhorHordiichuk
* Vietnamese - @ngocanhtve
### 13.7 (2024-02-07)
* Fix returning to previous filter after search [#2700](https://github.com/tasks/tasks/pull/2700)
* Fix wearable notifications on Android 14+
* Fix issue causing repeating tasks to not repeat
* Fix dragging a task into a subtask in another list
* Rewrote navigation drawer in Jetpack Compose
* Internal changes to navigation
* Enable multi-select when adding attachments
* Show count of tasks to be deleted when clearing completed
* Include hidden subtasks when clearing completed [#2724](https://github.com/tasks/tasks/issues/2724)
* Don't show hidden or completed tasks in snoozed filter
* Remove markdown from repeating task snackbar
* Update translations
* Azerbaijani - Shaban Mamedov
* Bulgarian - @StoyanDimitrov
* Catalan - raulmagdalena
* Chinese (Simplified) - 大王叫我来巡山
* Chinese (Traditional) - @abc0922001
* Croatian - @milotype
* Dutch - @mm4c
* Esperanto - Don Zouras
* Finnish - @millerii
* French - J. Lavoie
* German - @CennoxX
* Hebrew - @elig0n
* Interlingua - @softinterlingua
* Odia - @SubhamJena
* Persian - @Monirzadeh
* Spanish - gallegonovato
* Swedish - @bittin
* Turkish - @oersen
* Ukrainian - Сергій
* Vietnamese - @ngocanhtve
### 13.6.3 (2023-11-25)
* Revert "Preserve modification times on initial sync" [#2460](https://github.com/tasks/tasks/issues/2640)
* Fix unnecessary DecSync work
### 13.6.2 (2023-10-30)
* Fix updating modification timestamp on edits
### 13.6.1 (2023-10-27)
* Push pending changes when app is backgrounded
* Don't require internet connection for DAVx5/EteSync/DecSync sync
* Don't perform background sync for DAVx5/EteSync/DecSync
* Background sync is performed by the sync app
* Preserve modification times on initial sync [#2496](https://github.com/tasks/tasks/issues/2496)
* Replace deprecated method call [#2547](https://github.com/tasks/tasks/pull/2547) - @kmj-99
* Improve task list scrolling performance
* Fix hourly recurrence bug
* CalDAV: Display shared list members in list settings
* Compatible with Tasks.org, Nextcloud, ownCloud, OpenXchange, and sabre/dav
* CalDAV: List owners can remove shared list members from list
* Compatible with Tasks.org, Nextcloud, ownCloud, and sabre/dav
* Fix time zone issue in recurrence picker
* Update translations
* Chinese (Simplified) - Eric
* Croatian - @milotype
* Czech - @ceskyDJ
* Finnish - @millerii
* French - Lionel HANNEQUIN, Bruno Duyé
* Japanese - Kazushi Hayama
* Portuguese - @loucurapt
* Romanian - @ygorigor
* Swedish - @bittin
### 13.6 (2023-10-07)
* Change priority with multi-select [#2257](https://github.com/tasks/tasks/pull/2452) - @vulewuxe86
* Automatically select newly copied tasks [#2246](https://github.com/tasks/tasks/pull/2446) - @vulewuxe86
* Reduce minimum size for widgets [#2436](https://github.com/tasks/tasks/pull/2436) - @histefanhere
* Replace deprecated method call [#2526](https://github.com/tasks/tasks/pull/2526) - @kmj-99
* Improve handling text shared to Tasks [#2485](https://github.com/tasks/tasks/issues/2485)
* Use notification audio stream for completion sound
* Notification preference 'More settings' opens channel settings directly
* Respect 'New tasks on top' preference when creating subtasks
* Automatically add due dates for recurring tasks
* Fix crash on startup
* Update translations
* Brazilian Portuguese - @gorgonun
* Bulgarian - @StoyanDimitrov, @salif
* Catalan - Joan Montané
* Chinese (Simplified) - Poesty Li
* Chinese (Traditional) - @abc0922001
* Arabic - @mhmdanas
* Basque - Sergio Varela
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - @qwerty287, deep map, @franconian
* Hungarian - Kaci
* Italian - @ppasserini
* Japanese - Kazushi Hayama, Naga
* Hungarian - kaciokos
* Indonesian - @putulopi
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Swedish - @Anaemix, @bittin
* Turkish - @emintufan, @oersen
* Turkish - @emintufan, Oğuz Ersen
* Ukrainian - @IhorHordiichuk
### 13.5.1 (2023-08-02)
### 11.6.1 (2021-03-11)
* Fix crash when importing Google Tasks from a backup file
* Added Burmese translations - @htetoh
* Update translations
* Chinese (Simplified) - Poesty Li
* Croatian - @milotype
* Japanese - Kazushi Hayama
* Polish - @alex-ter
* Russian - @alex-ter
* Ukrainian - @IhorHordiichuk
* Vietnamese - @unbiaseduser
* F-Droid: Fix OpenStreetMap crash
### 13.5 (2023-07-28)
### 11.6 (2021-03-04)
* New custom recurrence picker
* CalDAV: Display indicator in drawer when a list is shared with other users
* Compatible with Tasks.org, Nextcloud, ownCloud, OpenXchange, and sabre/dav
* CalDAV: Don't upload changes to read-only lists
([#931](https://github.com/tasks/tasks/issues/931))
* Remove unnecessary icon-mirroring for RTL users
([#1385](https://github.com/tasks/tasks/issues/1385) and
[#1391](https://github.com/tasks/tasks/pull/1391)) - Thanks to @mhmdanas
* Update translations
* Arabic - @mhmdanas
* Basque - Sergio Varela
* Bulgarian - @StoyanDimitrov
* Czech - @ceskyDJ
* Czech - @vitSkalicky
* Dutch - @fvbommel
* French - @FlorianLeChat
* Italian - @ppasserini
* Hungarian - kaciokos
* Indonesian - @putulopi
* Russian - Nikita Epifanov
* Simplified Chinese - @sr093906
* Sinhala - HelaBasa
* Spanish - @FlorianLeChat
* Ukrainian - @IhorHordiichuk
### 13.4 - (2023-07-16)
### 11.5.2 (2021-02-25)
* Sorting improvements
* Add subtask sort configuration
* Update sort menu button design
* Don't show subtasks of hidden tasks in 'My Tasks'
* Fix Google Tasks sync issue
* Update translations
* Bulgarian - @StoyanDimitrov
* Catalan - @and4po, Eudald Puy Polls
* Croatian - @milotype
* Dutch - @fvbommel
* German - @schneidr
* Hungarian - Kaci
* Japanese - Naga
* Korean - Sunjae Choi
* Portuguese - @laralem
* Swedish - @bittin
### 13.3.2 - (2023-06-02)
* Sorting improvements
* Configure sort grouping
* Configure sorting within sort group
* Configure completed task sorting
* Fix Google Task list chips showing on widget
* Fix CalDAV sync error
* Report errors when generating recurrence dates
### 11.5.1 (2021-02-24)
* Fix 'repeat until' date
* Fix repeat dates for UTC+13
([#1374](https://github.com/tasks/tasks/issues/1374))
* F-Droid: Handle null name in Nominatim reverse geocoder
([#1380](https://github.com/tasks/tasks/issues/1380))
* Update translations
* Bulgarian - @StoyanDimitrov
* Catalan - @and4po
* Chinese (Simplified) - Poesty Li
* Croatian - @milotype
* Basque - Sergio Varela
* Croatian - @ggdorman
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - @qwerty287, @franconian
* Hungarian - Kaci
* Italian - @ppasserini
* Hungarian - kaciokos
* Norwegian Bokmål - @comradekingu
* Polish - @alex-ter
* Russian - Nikita Epifanov
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - Oğuz Ersen
* Ukrainian - @IhorHordiichuk
### 13.2.4 - (2023-05-24)
* Add 'By list' sort mode [#1265](https://github.com/tasks/tasks/issues/1265)
* Save task when pressing done [#2125](https://github.com/tasks/tasks/pull/2125)
* Use ISO 8601 date formatting for backup filenames [#1550](https://github.com/tasks/tasks/pull/1550)
* Fix filter sorting bug [#1561](https://github.com/tasks/tasks/issues/1561)
* Fix manual sorting crash [#2141](https://github.com/tasks/tasks/issues/2141)
* Fix manual sorting bug [#2101](https://github.com/tasks/tasks/issues/2101)
* Fix multiple accounts on same server [#2301](https://github.com/tasks/tasks/issues/2301)
* Don't set `COUNT=0` on recurrence rules [#2158](https://github.com/tasks/tasks/issues/2158)
* Improve task list performance [#2062](https://github.com/tasks/tasks/issues/2062)
* Attempt to hide inactive widgets in settings [#2145](https://github.com/tasks/tasks/issues/2145)
* Disable persistent reminders on Android 14+
* Android 14+ no longer supports persistent reminders 😢
* Fix notifications on Android 14
* Fix crash when missing exact alarm permissions
* Update logic for adding default reminders during sync
* Don't add reminders on initial sync
* Don't add reminders if other client supports reminder sync
* Internal database changes
* You will need to reconfigure any widgets that were set to display a Google
Task list or filter. Sorry for the interruption!
* Add Odia translations - @SubhamJena
* Urdu - Maaz
### 11.5 (2021-02-17)
* Sync snooze time with Tasks.org, DAVx⁵, CalDAV, EteSync, and DecSync
* Compatible with Thunderbird
* New map theme preference
* 10 new icons
* F-Droid: Use Nominatim for reverse geocoding
* Google Play: Use OpenStreetMap tiles when Play Services not available
* Google Play: Use Android location services when Play Services not available
* Tasks.org accounts: Use Google Places for map search
* Update translations
* Brazilian Portuguese - @lnux-usr
* Bulgarian - @StoyanDimitrov
* Catalan - @and4po
* Chinese (Simplified) - Poesty Li
* Chinese (Traditional) - Chih-Hsuan Yen
* Croatian - @milotype
* Dutch - @fvbommel
* Esperanto - Don Zouras
* Finnish - @millerii
* French - @FlorianLeChat
* Italian - @ppasserini
* Japanese - @kisaragi-hiu, Naga
* Korean - Sunjae Choi, @o20n3
* Romanian - @simonaiacob
* Russian - @AHOHNMYC
* Hungarian - kaciokos
* Indonesian - when we were sober
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - @ersen0
* Ukrainian - @IhorHordiichuk
### 13.1.2 (2023-02-02)
* Add default reminders to incoming iCalendar tasks [#1984](https://github.com/tasks/tasks/issues/1984)
* Sync when brought to the foreground [#2096](https://github.com/tasks/tasks/issues/2096)
* Update translations
* Arabic - haidarah esmander
* Czech - @SlavekB
* Danish - Tntdruid
* Esperanto - Don Zouras, @J053Fabi0
* Finnish - @millerii
* German - @franconian
* Italian - @ppasserini
* Japanese - Kazushi Hayama
* Korean - @o20n3
* Polish - @gnu-ewm
* Vietnamese - @unbiaseduser
### 13.1.1 (2022-12-06)
* Fix crash when opening notification settings
* Fix IAP errors in some locales
* Update translations
* Italian - @ppasserini
* Japanese - Kazushi Hayama
### 13.1.0 (2022-11-30)
### 11.4 (2021-02-09)
* Support for DAVx5 and CalDAV read-only lists [#931](https://github.com/tasks/tasks/issues/931)
* Use default Android network security configuration
* Update translations
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - Eric
* Croatian - @milotype
* Sync collapsed subtask state with Tasks.org, DAVx⁵, CalDAV, EteSync, and
DecSync ([#1339](https://github.com/tasks/tasks/issues/1339))
* Compatible with Nextcloud and ownCloud
* F-Droid: Add location based reminders ([#770](https://github.com/tasks/tasks/issues/770))
* F-Droid: Replace Mapbox tiles with OpenStreetMap tiles ([#922](https://github.com/tasks/tasks/issues/922))
* Fix default start date ([#1350](https://github.com/tasks/tasks/issues/1350))
### 11.3.4 (2021-02-03)
* Adjust start times by one second during sync
([#1326](https://github.com/tasks/tasks/issues/1326))
* Can now sync start time = due time with DAVx⁵, EteSync app, and DecSync CC
* All day start date must come before all day due date with DAVx⁵, EteSync
app, and DecSync CC
* 'Show unstarted' toggled on by default
### 11.3.3 (2021-01-30)
* Fix all-day due date synchronization
([#1325](https://github.com/tasks/tasks/issues/1325))
### 11.3.2 (2021-01-28)
* Fix recurrence sync issue
([#1323](https://github.com/tasks/tasks/issues/1323))
### 11.3.1 (2021-01-27)
* Improve support for recurring tasks with subtasks
* Subtasks will be unchecked after completing a recurring task
* Clear completed will not delete subtasks of recurring tasks
* Improve widget sort header when space is limited
* Add option to hide widget title
* Fix timezone conversions during synchronization
* Add Esperanto translations - @jakubfabijan
### 11.3 (2021-01-20)
* 'Hide until' is now 'Start date'
* Synchronize start dates with Tasks.org, DAVx⁵, CalDAV, EteSync, and DecSync
* New start date picker
* New start date custom filter criteria
* Add sort 'By start date'
* Display start dates as chips
* Don't perform background sync when data saver enabled
* Preference changes
* Add app and widget preferences to disable start date chips
* Synchronization accounts displayed on main preference screen
* Removed background sync and metered connection options (now respecting data
saver mode)
* Removed Google Tasks 'Custom order synchronization fix' (automatically
performing full sync if 'My order' enabled)
* Bug fixes
### 11.2.2 (2021-01-07)
* Rename 'Lists' to 'Local lists' to clarify that they are not synchronized
* Tasks.org sign in improvements
* Miscellaneous improvements - Thanks @mhmdanas!
### 11.2.1 (2021-01-05)
* Fix Portuguese translation issue
* Report OpenTask sync errors
* Report Tasks.org sign in errors
* Don't crash on widget configuration error
* Purchase dialog changes
### 11.2 (2020-12-30)
* [Synchronize your Tasks.org account with third-party task and calendar apps, like Outlook,
Thunderbird, or Apple Reminders](https://tasks.org/passwords)
* Miscellaneous improvements - Thanks @mhmdanas!
### 11.1.1 (2020-12-24)
* Fix compatibility issues with third-party clients
* Completed tasks without completion dates
([222a34f](https://github.com/tasks/tasks/commit/222a34fc263816bb23f633bc9c79de78aeb3968d))
* Tasks with start date but no due date
([7a1d566](https://github.com/tasks/tasks/commit/7a1d566bfb613b95d3fe1df46d8fa67200c91021))
* Miscellaneous improvements - Thanks @mhmdanas!
### 11.1 (2020-12-21)
* Add [DecSync CC synchronization](https://tasks.org/decsync)
* Fix rescheduling remotely completed recurring task
([5eb9370](https://github.com/tasks/tasks/commit/5eb9370294ef707b3e667c4a42851030419920d8))
* Miscellaneous code improvements - Thanks @mhmdanas!
### 11.0.1 (2020-12-17)
* Fix EteSync client issue with v2 accounts
([b761309](https://github.com/tasks/tasks/commit/b76130902ae0be6e1d580d588798a9ed0d7ff385))
* Fix multi-select 'Pick time' crash
* Fix default hide until due time
([#842](https://github.com/tasks/tasks/issues/842#issuecomment-746358382))
* Add Croatian translations - Garden Hose
* Add Urdu translations - Maaz
### 11.0 (2020-12-10)
* New Tasks.org synchronization service
* Multi-select rescheduling
* New task default settings
* Default tags
* Default recurrence
* Default location
* Hide until due time
* New custom filter criteria
* Hidden tasks
* Completed tasks
* Subtasks
* Parent tasks
* Recurring tasks
* Added EteSync v2 support
* Deprecated EteSync v1 support
* v1 accounts cannot be added to Tasks.org
* v1 accounts can be added to the EteSync Android client
* Add ability to delete comments (Thanks to @romedius!)
* Add option to always display date (Thanks to @T0M0F!)
* Copy subtasks when copying tasks (Thanks to @supermzn!)
* Fix ring five times cutoff (Thanks to @przemhb!)
* Bug fixes
* Translation updates
* Arabic - @mhmdanas
* Basque - @osoitz, @ppasserini
* Dutch - @fvbommel
* Finnish - @millerii
* French - @FlorianLeChat
* German - @helloworldtest123
* Hungarian - Kaci
* Italian - @ppasserini
* Lithuanian - @70h
* German - @franconian, J. Lavoie, @myabc
* Hebrew - @yarons
* Hungarian - kaciokos
* Indonesian - @andikatuluspangestu
* Italian - @ppasserini, @Fs00, @pjammo
* Korean - Sunjae Choi, @Hwaro-K
* Norwegian Bokmål - @comradekingu
* Polish - @alex-ter
* Russian - Nikita Epifanov
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - @ersen0
* Ukrainian - @IhorHordiichuk
* Traditional Chinese - @realpineapplemilk
* Turkish - @emintufan, Oğuz Ersen
### 13.0.2 (2022-11-22)
* Fix persistent notifications on Android 13
* Fix Samsung crash on too many reminders (DAVx5, EteSync, DecSync CC)
* Fix crash on too many tasks for Astrid Manual Sorting
* Fix RTL text in task edit customization screen
* Fix priority button order
### 13.0.1 (2022-10-20)
* 🚨 Major internal changes to task edit screen. Please report any bugs! 🚨
* Show thumbnails for attachments
* Tap on existing alarms to replace them
* Add task info row to edit screen [#1839](https://github.com/tasks/tasks/pull/1839)
* Add option to disable reminders for all-day tasks [#2003](https://github.com/tasks/tasks/pull/2003)
* Updated chip style
* Show geofence circle in place settings
* Fix removing preferences [#1981](https://github.com/tasks/tasks/pull/1981)
* Set user-agent on HTTP requests [#1978](https://github.com/tasks/tasks/issues/1978)
* Preserve HTTP session cookies [#1978](https://github.com/tasks/tasks/issues/1978)
* Sort selected tags at top of tag picker
* Android 13 support
* Runtime notification permissions
* Language preference
* Improvements to copying tasks
* Don't forget parent when copying tasks [#1964](https://github.com/tasks/tasks/pull/1964)
* Copy attachments when duplicating tasks [#812](https://github.com/tasks/tasks/issues/812)
* Fix duplicating subtasks
* Fix some missing reminders
* Incoming Google Tasks
* Tasker tasks [#1937](https://github.com/tasks/tasks/issues/1937)
* New subtasks [#1914](https://github.com/tasks/tasks/issues/1914)
* Fix Google Task creation time
* Fix EteSync stops synchronizing [#1893](https://github.com/tasks/tasks/issues/1893)
* Don't overwrite coordinates when synchronizing locations [#1667](https://github.com/tasks/tasks/issues/1667)
* Update translations
* Asturian - @enolp
* Basque - Sergio Varela
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - Eric
* Croatian - @milotype
* Czech - Shimon
* Dutch - @fvbommel
* French - @FlorianLeChat, J. Lavoie
* German - @qwerty287
* Italian - @ppasserini
* Norwegian Bokmål - @comradekingu
* Persian - @latelateprogrammer
* Polish - @ebogucka
* Portuguese - @laralem
* Romanian - @simonaiacob
* Russian - @Allineer, Nikita Epifanov
* Sinhala - @Dilshan-H
* Spanish - @FlorianLeChat
* Turkish - @ersen0
* Ukrainian - @IhorHordiichuk, @artemmolotov
* Vietnamese - @unbiaseduser
### 10.4.1 (2020-11-09)
* Fix Mapbox Maps crash on Android 11 (F-Droid only)
### 10.4 (2020-10-09)
* New widget configuration options
* Sort
* Show hidden
* Show completed
* Header spacing
* Bug fixes
### 10.3 (2020-10-02)
* Collapsible sort groups in widget
* Add 'System default' widget theme
* Bug fixes
### 10.2 (2020-09-25)
* Display list, tag, and place chips on widgets
* Add option to disable list, tag, and place chips on widgets
### 10.1 (2020-09-23)
* Android 11 support
* Backup improvements
* Swipe-to-refresh initiates DAVx5/EteSync sync
* Show indicator when DAVx5/EteSync are synchronizing
* Bug fixes
### 10.0.3 (2020-09-16)
* Fix crash from calendar event snackbar
* Fix crash when setting Google Maps markers
* Fix invalid calendar entry creation
### 10.0.2 (2020-09-14)
* Fix crash from corrupted custom filter
* Fix crash in 'Astrid manual sorting' mode
* Fix missing 'Calendar event created' snackbar
### 10.0.1 (2020-09-05)
* Bug fixes
* Translation updates
* Czech - @vitSkalicky
* Danish - @ChMunk
### 10.0 (2020-08-31)
* PRO: DAVx⁵ support (requires [DAVx⁵ beta](https://tasks.org/davx5))
* PRO: EteSync client support
* [ToDo Agenda](https://play.google.com/store/apps/details?id=org.andstatus.todoagenda) integration
* Changed backstack behavior to follow Android conventions
* Major internal changes! Please report any bugs!
* Remove Mapbox tiles (Google Play only)
* Added 'Astrid manual sort' information to backup file
* Bug fixes
* Performance improvements
* Security improvements
[Older releases](https://github.com/tasks/tasks/blob/main/V10_12_CHANGELOG.md)
[Older releases](https://github.com/tasks/tasks/blob/main/V06_09_CHANGELOG.md)

@ -14,7 +14,7 @@ Before opening an issue, please make sure that your issue:
#### To get started with development:
1. [Fork](https://help.github.com/articles/fork-a-repo/) and [clone](https://help.github.com/articles/cloning-a-repository/) the repository
2. Install and launch [Android Studio's canary build](https://developer.android.com/studio/preview) (Tasks depends on some bleeding-edge features of the canary build, but in the future when those features are stabilized, you will be able to use the stable release of Android Studio)
2. Install and launch [Android Studio](https://developer.android.com/studio/index.html)
3. Select `File > Open`, select the Tasks directory, and accept prompts to install missing SDK components
#### Set up Mapbox

@ -1,209 +1,154 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
CFPropertyList (3.0.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.958.0)
aws-sdk-core (3.201.3)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.88.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.156.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.9.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
claide (1.1.0)
babosa (1.0.3)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.111.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
excon (0.72.0)
faraday (0.17.3)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.3.1)
fastlane (2.222.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.141.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)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
faraday_middleware (~> 0.13.1)
fastimage (>= 2.1.0, < 3.0.0)
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)
google-api-client (>= 0.29.2, < 0.37.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
public_suffix (~> 2.0.0)
rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
google-api-client (0.36.4)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
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.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.7.0)
google-cloud-env (>= 1.0, < 3.a)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.4.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
google-cloud-env (1.3.0)
faraday (~> 0.11)
google-cloud-errors (1.0.0)
google-cloud-storage (1.25.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.31.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
googleauth (0.10.0)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.6)
signet (~> 0.12)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.7.2)
jwt (2.8.2)
base64
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
nanaimo (0.3.0)
naturally (2.2.1)
nkf (0.2.0)
optparse (0.5.0)
os (1.1.4)
plist (3.7.1)
public_suffix (5.1.1)
rake (13.2.1)
representable (3.2.0)
json (2.3.0)
jwt (2.1.0)
memoist (0.16.2)
mini_magick (4.10.1)
mini_mime (1.0.2)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.3.9)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
rubyzip (1.3.0)
security (0.1.3)
signet (0.12.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-screen (0.7.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.5.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.1)
word_wrap (1.0.0)
xcodeproj (1.19.0)
xcodeproj (1.15.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
@ -213,4 +158,4 @@ DEPENDENCIES
fastlane
BUNDLED WITH
2.2.32
2.1.2

@ -11,11 +11,11 @@ Please visit [tasks.org](https://tasks.org) for end user documentation and suppo
---
[![Donate with Bitcoin](https://img.shields.io/badge/bitcoin-donate-yellow.svg?logo=bitcoin)](https://tasks.org/docs/donate)
[![Donate with Bitcoin](https://img.shields.io/badge/bitcoin-donate-yellow.svg?logo=bitcoin)](https://en.cryptobadges.io/donate/136mW34jW3cmZKhxuTDn3tHXMRwbbaRU8s)
[![PayPal donate button](https://img.shields.io/badge/paypal-donate-yellow.svg?logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=alex@tasks.org)
[![Liberapay donate button](https://img.shields.io/liberapay/receives/tasks.svg?logo=liberapay)](https://liberapay.com/tasks/donate)
[![build](https://github.com/tasks/tasks/actions/workflows/bundle.yml/badge.svg)](https://github.com/tasks/tasks/actions/workflows/bundle.yml) [![weblate](https://hosted.weblate.org/widgets/tasks/-/android/svg-badge.svg)](https://hosted.weblate.org/engage/tasks/?utm_source=widget) [![codebeat badge](https://codebeat.co/badges/07924fca-2f18-4eff-99a3-120ec5ac2d5f)](https://codebeat.co/projects/github-com-tasks-tasks-main)
![tests](https://github.com/tasks/tasks/workflows/Run%20tests/badge.svg) [![weblate](https://hosted.weblate.org/widgets/tasks/-/android/svg-badge.svg)](https://hosted.weblate.org/engage/tasks/?utm_source=widget) [![codecov](https://codecov.io/gh/tasks/tasks/branch/main/graph/badge.svg)](https://codecov.io/gh/tasks/tasks) [![codebeat badge](https://codebeat.co/badges/07924fca-2f18-4eff-99a3-120ec5ac2d5f)](https://codebeat.co/projects/github-com-tasks-tasks-main)
### Contributing
@ -23,7 +23,7 @@ Contributions are always welcome! Whether translations, code changes, bug report
### Communication
Join the #tasks channel on Libera Chat to chat with the Tasks team and other people. [Link to webchat](https://web.libera.chat/#tasks)
Join the #tasks channel on Freenode to chat with the Tasks team and other people. [Link to webchat](https://webchat.freenode.net/?channels=tasks)
You can also use [GitHub Discussions](https://github.com/tasks/tasks/discussions).

@ -1,767 +0,0 @@
### 12.7 (2022-06-18)
* Android 13 themed icon - Thanks @hanthor!
* Fix self-signed SSL certificates on Android 12+
* Don't hide empty tags and places in pickers
* Update translations
* Basque - @Txopi, Sergio Varela, @osoitz
* Belarusian - @Prominence, Андрей
* Bulgarian - @StoyanDimitrov
* Czech - Shimon
* Danish - Tntdruid
* Dutch - @mm4c
* German - @3ole
* Hungarian - kaciokos
* Indonesian - Cyua Pyua
* Italian - @ppasserini
* Polish - @wiktor-k
* Portuguese (Brazilian) - @LevyMarCiS, @sunflowerskater
* Portuguese - @laralem, @alvar0liveira
* Swedish - @reportxx
* Turkish - @emintufan
* Vietnamese - @unbiaseduser
### 12.6.1 (2022-03-27)
* Move task list and edit screen options to top level settings
* Prompt users to customize edit screen
* Fix cancel button for recurring reminder dialog
* Update translations
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - Eric, @Geeyun-JY3
* Croatian - @milotype
* Dutch - @mm4c, @fvbommel
* Finnish - J. Lavoie
* French - @FlorianLeChat
* Galician - @mglbranco, J. Lavoie
* German - @qwerty287
* Hungarian - kaciokos
* Italian - @Fs00
* Norwegian Bokmål - @comradekingu
* Polish - @wiktor-k
* Portuguese (Brazilian) - @tsunamistonefly
* Romanian - @simonaiacob
* Russian - Nikita Epifanov
* Spanish - @FlorianLeChat
* Swedish - @reportxx
* Turkish - @ersen0, @emintufan
* Ukrainian - @IhorHordiichuk
* Vietnamese - @unbiaseduser, J. Lavoie
### 12.6 (2022-03-12)
* Configure notifications to repeat at custom intervals
([#3](https://github.com/tasks/tasks/issues/3))
* Notifications can repeat by minute, hour, day, or weekly intervals
* Add 'Snoozed' filter ([#1633](https://github.com/tasks/tasks/issues/1633))
* Add 'Notifications' filter
* CalDAV/DAVx5 server selection setting
* This replaces 'Let server schedule recurring tasks'
* Synology Calendar users must set this to fix sync
([#1802](https://github.com/tasks/tasks/issues/1802))
* Mailbox.org and Open-Xchange users must set this to prevent duplicate
repeating tasks
* Set geofence radius in place settings
* Remove DAVx5/EteSync app accounts when native CalDAV/EteSync enabled
* Clear reminders when they are dismissed in Thunderbird
* Fix reminder synchronization
* Fix crash in task edit screen
* Fix prompt to discard changes
* Fix crash during 12.4 upgrade
* Update translations
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - @Crystal-RainSlide, @Geeyun-JY3, Eric
* Croatian - @milotype
* Dutch - @mm4c, @fvbommel
* French - J. Lavoie, @FlorianLeChat
* German - @eldiep, J. Lavoie, @qwerty287
* Hungarian - kaciokos
* Italian - @ppasserini, J. Lavoie
* Portuguese (Brazilian) - @hugomg
* Romanian - @simonaiacob
* Russian - @Allineer
* Spanish - @toni-em, @FlorianLeChat, @Romerolweb
* Swedish - @reportxx
* Turkish - @ersen0
* Ukrainian - @IhorHordiichuk
* Urdue - @Crystal-RainSlide
* Vietnamese - @unbaseduser
### 12.5 (2022-02-27)
* Choose custom random reminder period
* Add multiple random reminders
* Fix sync crash for Tasks.org, CalDAV, and native EteSync
* Add Kurdish (Central) translations - @roj1512
* Update translations
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - Eric
* Croatian - @milotype
* Dutch - @mm4c
* French - @FlorianLeChat
* Portuguese - @laralem
* Spanish - @Romerolweb, Jeffree Romero
* Turkish - @ersen0
* Ukrainian - @IhorHordiichuk
### 12.4 (2022-02-19)
* Relative reminder support
* Quickly add reminders minutes, hours, days, or weeks before due
* Sync reminders with Tasks.org, DAVx5, CalDAV, EteSync, and DecSync CC
* Synchronize relative and absolute reminders
* Tasks.org, CalDAV, and native EteSync sync improvements
* Merge remote changes before pushing local changes
* Not applicable to DAVx5, EteSync app, or DecSync CC
* View and cancel snoozed reminders in task edit screen
* Add 'Has reminder' custom filter criteria
* Fix updating calendar entries after editing task
* Fix search when using top app bar
* Fix task deletion when adding from two devices simultaneously
* Update translations
* Arabic - @mhmdanas
* Basque - Sergio Varela
* Brazilian Portuguese - @Luiz-bro
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - Eric
* Croatian - @milotype
* Dutch - @mm4c
* French - @FlorianLeChat, J. Lavoie
* German - J. Lavoie, @qwerty287
* Hungarian - kaciokos
* Italian - @ppasserini, J. Lavoie, @andrearosso
* Portuguese - @laralem
* Romanian - @simonaiacob
* Russian - @NikGreens
* Spanish - @FlorianLeChat, Sergio Varela
* Turkish - @ersen0, @emintufan
* Ukrainian - @IhorHordiichuk
* Vietnamese - bruh, @unbaseduser
### 12.3 (2022-02-04)
* Add option to disable moving completed tasks to bottom
* Add option to disable sorting completed by completion date
* Add undo snackbar for task completion
* Fix crash when location lookup fails
* Fix voice reminders on Android 12
* Fix widget due dates in overdue sort group
* Add Karelian translations - Olexii Ondrei
* Update translations
* Basque - Sergio Varela
* Catalan - @ivangjxyz
* Chinese (Simplified) - Eric
* Croatian - @milotype
* Dutch - @mm4c
* French - @FlorianLeChat
* German - @qwerty287
* Hungarian - kaciokos
* Romanian - @simonaiacob
* Russian - @NikGreens
* Spanish - @FlorianLeChat
* Swedish - @reportxx
* Turkish - @emintufan, @ersen0
* Vietnamese - @unbaseduser
### 12.2 (2022-01-16)
* Move completed tasks to bottom
* Add option to disable collapsing app bars
* Uncheck parent tasks when subtask is unchecked
* Fix crash on completion sound
* Update translations
* Chinese (Simplified) - Eric
* Danish - @Tntdruid
* Dutch - @fvbommel, @mm4c
* French - @FlorianLeChat
* German - @qwerty287
* Russian - @NikGreens
* Spanish - @FlorianLeChat
* Turkish - @ersen0
* Ukrainian - @IhorHordiichuk
* Vietnamese - @unbaseduser
### 12.1 (2022-01-09)
* Group overdue tasks when sorting by due date
* Update translations
* Basque - Sergio Varela
* Chinese (Simplified) - Eric
* French - @FlorianLeChat
* Norwegian Bokmål - @comradekingu
* Spanish - @FlorianLeChat
* Vietnamese - @unbaseduser
### 12.0 (2022-01-08)
* New bottom app bar
* Choose top or bottom app bar in settings
* Miscellaneous design updates
* Improve privacy and security by removing RECORD_AUDIO and
WRITE_EXTERNAL_STORAGE permissions
* Attaching an audio note will launch your device's audio recorder
* Translation updates
* Catalan - @Solatec
* Dutch - @mm4c
* German - @qwerty287
* Italian - @ppasserini, @Fs00
* Portuguese - @SantosSi
* Romanian - @simonaiacob
* Russian - Nikita Epifanov
* Ukrainian - @IhorHordiichuk
### 11.13 (2021-12-31)
* Add option to play a sound when a task is completed
* Accept audio attachments shared from other apps
* Removed native EteSync v1 support
* EteSync v1 accounts can still be synchronized with the EteSync app
* Bug fixes
* Translation updates
* Bulgarian - @StoyanDimitrov
* Chinese (Simplified) - @sr093906
* Chinese (Traditional) - @dixon777
* Finnish - @CSharpest, Rami Lehtinen
* French - @FlorianLeChat
* Hungarian - kaciokos
* Italian - J. Lavoie, @Fs00
* Norwegian Bokmål - @comradekingu
* Persian - @Ahmadhosseinbor
* Spanish - @aplopez, @FlorianLeChat
* Ukrainian - @IhorHordiichuk
### 11.12.3 (2021-11-22)
* Fix reminders
* Update translations
* Indonesian - when we were sober
* Kurdish (Northern) - Pêşeroja paşerojê
* Romanian - @Steinhagen
### 11.12.2 (2021-11-13)
* Fix reminders
* Fix reminder preference backup
* Update translations
* Interlingua - @softinterlingua
* Tamil - @balogic
### 11.12.1 (2021-11-05)
* Fix reminders
* Update translations
* Bulgarian - @StoyanDimitrov
* Croatian - @milotype
* Norwegian Bokmål - @HumanNr4584093104
* Romanian - Simona Iacob
* Russian - @NikGreens
* Tamil - @balogic
* Turkish - @ersen0
### 11.12 (2021-10-26)
* Add option to notify at start date
* Widget tweaks for Android 12
* Fix crash when deleting tasks (Thanks @fschrempf!)
* Fix truncated calendar picker
* Update translations
* Basque - Sergio Varela
* Brazilian Portuguese - @laralem
* Bulgarian - @StoyanDimitrov
* Catalan - @Solatec
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - @qwerty287
* Hungarian - kaciokos
* Lithuanian - @70h
* Polish - @dominik-korsa
* Simplified Chinese - @sr093906, @Geeyun-JY3
* Ukrainian - @IhorHordiichuk
* Vietnamese - bruh
### 11.11 (2021-09-21)
* Add 'Due now' filter criteria - Thanks @tkterris!
* Fix crash on Android 12 - Thanks @tkterris!
* Fix preference display issue - Thanks @Groctel!
* Target Android 12
* Ignore link clicks during multi-select
* Update translations
* Arabic - @mhmdanas, @machiav3lli
* Basque - @Thadah
* Brazilian Portuguese - @laralem
* Bulgarian - @StoyanDimitrov
* Croatian - @milotype
* Czech - @vitSkalicky
* Danish - @Tntdruid
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - @machiav3lli, J. Lavoie
* Greek - @giorgio93p
* Indonesian - @erigmac
* Italian - J. Lavoie, @Fs00
* Japanese - さとうまこと
* Lithuanian - @70h
* Norwegian Bokmål - @comradekingu
* Portuguese - @laralem
* Romanian - Simona Iacob
* Russian - @tolstovka, @zhelemysh, @ToxesFoxes
* Simplified Chinese - @sr093906, @Geeyun-JY3
* Sinhala - @Dilshan-H
* Spanish - @FlorianLeChat, @Groctel, @berman00
* Swedish - @bittin
* Turkish - @ersen0
* Ukrainian - @IhorHordiichuk
* Vietnamese - bruh
### 11.10.2 (2021-07-15)
* Fix location-based reminders
* Fix preference backup
* Update translations
* Arabic - git ty, @mhmdanas
* Basque - Sergio Varela
* Croatian - @milotype
* Czech - @vitSkalicky, @p-bo
* Dutch - Beardhatcode, @fvbommel
* French - @FlorianLeChat
* German - K. Herbert, @franconian, @ecxod, @bluedeepimpact
* Indonesian - when we were sober
* Interlingua - @softinterlingua
* Italian - J. Lavoie
* Lithuanian - @70h
* Norwegian Bokmål - @Jerome2103
* Portuguese - @laralem
* Russian - @KovalevArtem, @Blueberryy
* Simplified Chinese - @sr093906, @Geeyun-JY3
* Sinhala - HelaBasa
* Spanish - @FlorianLeChat, @fitojb
* Turkish - Oğuz Ersen, @emintufan
* Ukrainian - @IhorHordiichuk
* Urdu - Maaz
* Vietnamese - bruh
### 11.10.1 (2021-05-26)
* Improve Android 12 compatibility
* Update status bar styles
* Update translations
* Arabic - @mhmdanas
* Basque - Sergio Varela
* Catalan - @toram
* Chinese (Traditional) - @kisaragi-hiu
* Croatian - @ggdorman
* Czech - @vitSkalicky
* Esperanto - @J053Fabi0, @jakubfabijan
* French - K. Herbert, J. Lavoie
* German - K. Herbert
* Greek - Eugenia Russell
* Hungarian - @gthrepwood
* Indonesian - @andhikapangestu29
* Korean - Sunjae Choi
* Portuguese (Brazil) - @laralem
* Portuguese - @SantosSi, @laralem
* Russian - Nikita Epifanov
* Sinhala - @Dilshan-H
* Spanish - @fitojb
* Ukrainian - @IhorHordiichuk
* Urdu - Maaz
* Vietnamese - bruh
### 11.10 (2021-04-19)
* Markdown support ([Documentation](https://tasks.org/docs/markdown))
* Samsung DeX support - Thanks @mhmdanas!
* Update to Google Play Billing v3
* Remove background sync for legacy EteSync v1 accounts
* Update translations
* Arabic - @mhmdanas
* Brazilian Portuguese - @daylightdev
* Dutch - @fvbommel
* French - @FlorianLeChat, J. Lavoie
* German - J. Lavoie
* Greek - Michalis, Eugenia Russell
* Indonesian - @liimee
* Italian - J. Lavoie, @Fs00
* Japanese - @kisaragi-hiu
* Kannada - @shashank-p
* Russian - @zhelemysh, Nikita Epifanov
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - Oğuz Ersen
* Ukrainian - @IhorHordiichuk
* Urdu - Maaz
### 11.9.2 (2021-03-29)
* Fix date translation issue - Thanks @mhmdanas!
* Fix misc translation strings - Thanks J. Lavoie!
* Update translations
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - @franconian, Achim Schumacher, J. Lavoie
* Hungarian - kaciokos
* Indonesian - when we were sober
* Italian - @Fs00
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - @emintufan
* Ukrainian - @IhorHordiichuk
### 11.9.1 (2021-03-25)
* Open documentation links in custom tabs
* Fix crash in Mapbox reverse geocoder
* Increase 'Add subtask' touch target
* Update translations
* Arabic - @mhmdanas
* German - Achim Schumacher
* Hungarian - kaciokos
* Italian - @Fs00
* Turkish - @emintufan
### 11.9 (2021-03-20)
* New calendar and clock pickers
* New preference to default to text input for date and time
* Fix issue causing Tasks to use wrong search provider
* Fix crash when Nextcloud/ownCloud don't send list owner
* Update translations
* Basque - Sergio Varela
* Croatian - @milotype
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - Achim Schumacher
* Hungarian - kaciokos
* Indonesian - when we were sober
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Ukrainian - @IhorHordiichuk
### 11.8 (2021-03-15)
* CalDAV: Send shared list invites
* Compatible with Tasks.org, Nextcloud, ownCloud, and sabre/dav
* Show shared list invite status in list settings
* Fix drawer count when list is shared with 2+ users
* Removed legacy EteSync v1 list management features
* Dropped support for Android 6.0
* Update translations
* Arabic - @mhmdanas
* Dutch - @fvbommel
* Esperanto - @jakubfabijan
* French - @FlorianLeChat
* German - @Jerome2103
* Hungarian - kaciokos
* Indonesian - when we were sober, @andhikapangestu29
* Norwegian Bokmål - @comradekingu
* Polish - @doegedomita
* Portuguese - @Jerome2103
* Spanish - @FlorianLeChat
* Turkish - Oğuz Ersen
* Ukrainian - @IhorHordiichuk
### 11.7 (2021-03-08)
* CalDAV: Display shared list members in list settings
* Compatible with Tasks.org, Nextcloud, ownCloud, OpenXchange, and sabre/dav
* CalDAV: List owners can remove shared list members from list
* Compatible with Tasks.org, Nextcloud, ownCloud, and sabre/dav
* Fix time zone issue in recurrence picker
* Update translations
* Arabic - @mhmdanas
* Basque - Sergio Varela
* Dutch - @fvbommel
* French - @FlorianLeChat
* Hungarian - kaciokos
* Indonesian - @putulopi
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - @emintufan, Oğuz Ersen
* Ukrainian - @IhorHordiichuk
### 11.6.1 (2021-03-11)
* F-Droid: Fix OpenStreetMap crash
### 11.6 (2021-03-04)
* CalDAV: Display indicator in drawer when a list is shared with other users
* Compatible with Tasks.org, Nextcloud, ownCloud, OpenXchange, and sabre/dav
* CalDAV: Don't upload changes to read-only lists
([#931](https://github.com/tasks/tasks/issues/931))
* Remove unnecessary icon-mirroring for RTL users
([#1385](https://github.com/tasks/tasks/issues/1385) and
[#1391](https://github.com/tasks/tasks/pull/1391)) - Thanks to @mhmdanas
* Update translations
* Arabic - @mhmdanas
* Basque - Sergio Varela
* Bulgarian - @StoyanDimitrov
* Czech - @vitSkalicky
* Dutch - @fvbommel
* French - @FlorianLeChat
* Hungarian - kaciokos
* Indonesian - @putulopi
* Russian - Nikita Epifanov
* Simplified Chinese - @sr093906
* Sinhala - HelaBasa
* Spanish - @FlorianLeChat
* Ukrainian - @IhorHordiichuk
### 11.5.2 (2021-02-25)
* Fix CalDAV sync error
* Report errors when generating recurrence dates
### 11.5.1 (2021-02-24)
* Fix 'repeat until' date
* Fix repeat dates for UTC+13
([#1374](https://github.com/tasks/tasks/issues/1374))
* F-Droid: Handle null name in Nominatim reverse geocoder
([#1380](https://github.com/tasks/tasks/issues/1380))
* Update translations
* Basque - Sergio Varela
* Croatian - @ggdorman
* Dutch - @fvbommel
* French - @FlorianLeChat
* Hungarian - kaciokos
* Norwegian Bokmål - @comradekingu
* Polish - @alex-ter
* Russian - Nikita Epifanov
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Turkish - Oğuz Ersen
* Ukrainian - @IhorHordiichuk
* Urdu - Maaz
### 11.5 (2021-02-17)
* Sync snooze time with Tasks.org, DAVx⁵, CalDAV, EteSync, and DecSync
* Compatible with Thunderbird
* New map theme preference
* 10 new icons
* F-Droid: Use Nominatim for reverse geocoding
* Google Play: Use OpenStreetMap tiles when Play Services not available
* Google Play: Use Android location services when Play Services not available
* Tasks.org accounts: Use Google Places for map search
* Update translations
* Dutch - @fvbommel
* French - @FlorianLeChat
* Hungarian - kaciokos
* Indonesian - when we were sober
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Ukrainian - @IhorHordiichuk
### 11.4 (2021-02-09)
* Sync collapsed subtask state with Tasks.org, DAVx⁵, CalDAV, EteSync, and
DecSync ([#1339](https://github.com/tasks/tasks/issues/1339))
* Compatible with Nextcloud and ownCloud
* F-Droid: Add location based reminders ([#770](https://github.com/tasks/tasks/issues/770))
* F-Droid: Replace Mapbox tiles with OpenStreetMap tiles ([#922](https://github.com/tasks/tasks/issues/922))
* Fix default start date ([#1350](https://github.com/tasks/tasks/issues/1350))
### 11.3.4 (2021-02-03)
* Adjust start times by one second during sync
([#1326](https://github.com/tasks/tasks/issues/1326))
* Can now sync start time = due time with DAVx⁵, EteSync app, and DecSync CC
* All day start date must come before all day due date with DAVx⁵, EteSync
app, and DecSync CC
* 'Show unstarted' toggled on by default
### 11.3.3 (2021-01-30)
* Fix all-day due date synchronization
([#1325](https://github.com/tasks/tasks/issues/1325))
### 11.3.2 (2021-01-28)
* Fix recurrence sync issue
([#1323](https://github.com/tasks/tasks/issues/1323))
### 11.3.1 (2021-01-27)
* Improve support for recurring tasks with subtasks
* Subtasks will be unchecked after completing a recurring task
* Clear completed will not delete subtasks of recurring tasks
* Improve widget sort header when space is limited
* Add option to hide widget title
* Fix timezone conversions during synchronization
* Add Esperanto translations - @jakubfabijan
### 11.3 (2021-01-20)
* 'Hide until' is now 'Start date'
* Synchronize start dates with Tasks.org, DAVx⁵, CalDAV, EteSync, and DecSync
* New start date picker
* New start date custom filter criteria
* Add sort 'By start date'
* Display start dates as chips
* Don't perform background sync when data saver enabled
* Preference changes
* Add app and widget preferences to disable start date chips
* Synchronization accounts displayed on main preference screen
* Removed background sync and metered connection options (now respecting data
saver mode)
* Removed Google Tasks 'Custom order synchronization fix' (automatically
performing full sync if 'My order' enabled)
* Remove support for legacy XML backup format ([more info](https://github.com/tasks/tasks/issues/1565))
* Bug fixes
### 11.2.2 (2021-01-07)
* Rename 'Lists' to 'Local lists' to clarify that they are not synchronized
* Tasks.org sign in improvements
* Miscellaneous improvements - Thanks @mhmdanas!
### 11.2.1 (2021-01-05)
* Fix Portuguese translation issue
* Report OpenTask sync errors
* Report Tasks.org sign in errors
* Don't crash on widget configuration error
* Purchase dialog changes
### 11.2 (2020-12-30)
* [Synchronize your Tasks.org account with third-party task and calendar apps, like Outlook,
Thunderbird, or Apple Reminders](https://tasks.org/passwords)
* Miscellaneous improvements - Thanks @mhmdanas!
### 11.1.1 (2020-12-24)
* Fix compatibility issues with third-party clients
* Completed tasks without completion dates
([222a34f](https://github.com/tasks/tasks/commit/222a34fc263816bb23f633bc9c79de78aeb3968d))
* Tasks with start date but no due date
([7a1d566](https://github.com/tasks/tasks/commit/7a1d566bfb613b95d3fe1df46d8fa67200c91021))
* Miscellaneous improvements - Thanks @mhmdanas!
### 11.1 (2020-12-21)
* Add [DecSync CC synchronization](https://tasks.org/decsync)
* Fix rescheduling remotely completed recurring task
([5eb9370](https://github.com/tasks/tasks/commit/5eb9370294ef707b3e667c4a42851030419920d8))
* Miscellaneous code improvements - Thanks @mhmdanas!
### 11.0.1 (2020-12-17)
* Fix EteSync client issue with v2 accounts
([b761309](https://github.com/tasks/tasks/commit/b76130902ae0be6e1d580d588798a9ed0d7ff385))
* Fix multi-select 'Pick time' crash
* Fix default hide until due time
([#842](https://github.com/tasks/tasks/issues/842#issuecomment-746358382))
* Add Croatian translations - Garden Hose
* Add Urdu translations - Maaz
### 11.0 (2020-12-10)
* New Tasks.org synchronization service
* Multi-select rescheduling
* New task default settings
* Default tags
* Default recurrence
* Default location
* Hide until due time
* New custom filter criteria
* Hidden tasks
* Completed tasks
* Subtasks
* Parent tasks
* Recurring tasks
* Added EteSync v2 support
* Deprecated EteSync v1 support
* v1 accounts cannot be added to Tasks.org
* v1 accounts can be added to the EteSync Android client
* Add ability to delete comments (Thanks to @romedius!)
* Add option to always display date (Thanks to @T0M0F!)
* Copy subtasks when copying tasks (Thanks to @supermzn!)
* Fix ring five times cutoff (Thanks to @przemhb!)
* Bug fixes
* Translation updates
* Arabic - @mhmdanas
* Basque - @osoitz, @ppasserini
* Dutch - @fvbommel
* French - @FlorianLeChat
* German - @franconian, J. Lavoie, @myabc
* Hebrew - @yarons
* Hungarian - kaciokos
* Indonesian - @andikatuluspangestu
* Italian - @ppasserini, @Fs00, @pjammo
* Korean - Sunjae Choi, @Hwaro-K
* Norwegian Bokmål - @comradekingu
* Polish - @alex-ter
* Russian - Nikita Epifanov
* Simplified Chinese - @sr093906
* Spanish - @FlorianLeChat
* Traditional Chinese - @realpineapplemilk
* Turkish - @emintufan, Oğuz Ersen
### 10.4.1 (2020-11-09)
* Fix Mapbox Maps crash on Android 11 (F-Droid only)
### 10.4 (2020-10-09)
* New widget configuration options
* Sort
* Show hidden
* Show completed
* Header spacing
* Bug fixes
### 10.3 (2020-10-02)
* Collapsible sort groups in widget
* Add 'System default' widget theme
* Bug fixes
### 10.2 (2020-09-25)
* Display list, tag, and place chips on widgets
* Add option to disable list, tag, and place chips on widgets
### 10.1 (2020-09-23)
* Android 11 support
* Backup improvements
* Swipe-to-refresh initiates DAVx5/EteSync sync
* Show indicator when DAVx5/EteSync are synchronizing
* Bug fixes
### 10.0.3 (2020-09-16)
* Fix crash from calendar event snackbar
* Fix crash when setting Google Maps markers
* Fix invalid calendar entry creation
### 10.0.2 (2020-09-14)
* Fix crash from corrupted custom filter
* Fix crash in 'Astrid manual sorting' mode
* Fix missing 'Calendar event created' snackbar
### 10.0.1 (2020-09-05)
* Bug fixes
* Translation updates
* Czech - @vitSkalicky
* Danish - @ChMunk
### 10.0 (2020-08-31)
* PRO: DAVx⁵ support (requires [DAVx⁵ beta](https://tasks.org/davx5))
* PRO: EteSync client support
* [ToDo Agenda](https://play.google.com/store/apps/details?id=org.andstatus.todoagenda) integration
* Changed backstack behavior to follow Android conventions
* Major internal changes! Please report any bugs!
* Remove Mapbox tiles (Google Play only)
* Added 'Astrid manual sort' information to backup file
* Bug fixes
* Performance improvements
* Security improvements
[Older releases](https://github.com/tasks/tasks/blob/main/V06_09_CHANGELOG.md)

@ -1,32 +1,41 @@
@file:Suppress("UnstableApiUsage")
import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.application)
id("com.google.gms.google-services")
id("com.android.application")
id("checkstyle")
id("com.google.firebase.crashlytics")
kotlin("android")
kotlin("kapt")
id("com.cookpad.android.plugin.license-tools") version "1.2.6"
id("com.github.ben-manes.versions") version "0.38.0"
id("com.vanniktech.android.junit.jacoco") version "0.16.0"
id("dagger.hilt.android.plugin")
id("com.google.android.gms.oss-licenses-plugin")
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.compose.compiler)
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
repositories {
mavenCentral()
google()
maven {
url = uri("https://jitpack.io")
content {
includeGroup("com.gitlab.abaker")
includeModule("com.gitlab.bitfireAT", "cert4android")
includeModule("com.github.dmfs.opentasks", "opentasks-provider")
includeModule("com.github.QuadFlask", "colorpicker")
}
}
jcenter {
content {
includeModule("com.twofortyfouram", "android-plugin-api-for-locale")
includeModule("org.jetbrains.kotlinx", "kotlinx-collections-immutable-jvm")
includeModule("com.android.billingclient", "billing")
}
}
}
composeCompiler {
enableStrongSkippingMode = true
}
android {
val commonTest = "src/commonTest/java"
sourceSets["test"].java.srcDir(commonTest)
sourceSets["androidTest"].java.srcDirs("src/androidTest/java", commonTest)
bundle {
language {
enableSplit = false
@ -37,25 +46,32 @@ android {
viewBinding = true
dataBinding = true
compose = true
buildConfig = true
}
lint {
disable("InvalidPeriodicWorkRequestInterval")
lintConfig = file("lint.xml")
textOutput = File("stdout")
textOutput("stdout")
textReport = true
}
compileSdk = libs.versions.android.compileSdk.get().toInt()
compileSdkVersion(Versions.targetSdk)
defaultConfig {
testApplicationId = "org.tasks.test"
applicationId = "org.tasks"
versionCode = libs.versions.versionCode.get().toInt()
versionName = libs.versions.versionName.get()
targetSdk = libs.versions.android.targetSdk.get().toInt()
minSdk = libs.versions.android.minSdk.get().toInt()
versionCode = 110903
versionName = "11.9.2"
targetSdkVersion(Versions.targetSdk)
minSdkVersion(Versions.minSdk)
testInstrumentationRunner = "org.tasks.TestRunner"
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
}
}
}
signingConfigs {
@ -74,16 +90,22 @@ android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.compose
}
flavorDimensions += listOf("store")
kotlinOptions {
jvmTarget = "1.8"
}
@Suppress("LocalVariableName")
buildTypes {
debug {
configure<CrashlyticsExtension> {
getByName("debug") {
firebaseCrashlytics {
mappingFileUploadEnabled = false
}
val tasks_mapbox_key_debug: String? by project
@ -94,47 +116,39 @@ android {
resValue("string", "tasks_caldav_url", tasks_caldav_url ?: "https://caldav.tasks.org")
resValue("string", "tasks_nominatim_url", tasks_caldav_url ?: "https://nominatim.tasks.org")
resValue("string", "tasks_places_url", tasks_caldav_url ?: "https://places.tasks.org")
enableUnitTestCoverage = project.hasProperty("coverage")
isTestCoverageEnabled = project.hasProperty("coverage")
}
release {
getByName("release") {
val tasks_mapbox_key: String? by project
val tasks_google_key: String? by project
resValue("string", "mapbox_key", tasks_mapbox_key ?: "")
resValue("string", "google_key", tasks_google_key ?: "")
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard.pro")
signingConfig = signingConfigs.getByName("release")
}
}
flavorDimensions("store")
productFlavors {
create("generic") {
dimension = "store"
dimension("store")
}
create("googleplay") {
isDefault = true
dimension = "store"
}
}
packaging {
resources {
excludes += setOf("META-INF/*.kotlin_module", "META-INF/INDEX.LIST")
dimension("store")
}
}
testOptions {
managedDevices {
localDevices {
create("pixel2api30") {
device = "Pixel 2"
apiLevel = 30
systemImageSource = "aosp-atd"
}
}
}
packagingOptions {
exclude("META-INF/*.kotlin_module")
}
}
namespace = "org.tasks"
configure<CheckstyleExtension> {
configFile = project.file("google_checks.xml")
toolVersion = "8.16"
}
configurations.all {
@ -151,136 +165,97 @@ val genericImplementation by configurations
val googleplayImplementation by configurations
dependencies {
implementation(projects.data)
implementation(projects.kmp)
implementation(projects.icons)
coreLibraryDesugaring(libs.desugar.jdk.libs)
implementation(libs.bitfire.dav4jvm) {
exclude(group = "junit")
exclude(group = "org.ogce", module = "xpp3")
}
implementation(libs.bitfire.ical4android) {
exclude(group = "commons-logging")
exclude(group = "org.json", module = "json")
exclude(group = "org.codehaus.groovy", module = "groovy")
exclude(group = "org.codehaus.groovy", module = "groovy-dateutil")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
implementation("com.gitlab.abaker:dav4jvm:deb2c9aef8")
implementation("com.gitlab.abaker:ical4android:0e928b567c")
implementation("com.gitlab.bitfireAT:cert4android:26a91a729f")
implementation("com.github.dmfs.opentasks:opentasks-provider:1.2.4") {
exclude("com.github.dmfs.opentasks", "opentasks-contract")
}
implementation(libs.bitfire.cert4android)
implementation(libs.dmfs.opentasks.provider) {
exclude("com.github.tasks.opentasks", "opentasks-contract")
}
implementation(libs.dmfs.rfc5545.datetime)
implementation(libs.dmfs.recur)
implementation(libs.dmfs.jems)
implementation(libs.dagger.hilt)
ksp(libs.dagger.hilt.compiler)
ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.work)
implementation("com.google.dagger:hilt-android:${Versions.hilt}")
kapt("com.google.dagger:hilt-compiler:${Versions.hilt}")
kapt("androidx.hilt:hilt-compiler:${Versions.hilt_androidx}")
implementation("androidx.hilt:hilt-work:${Versions.hilt_androidx}")
implementation(libs.androidx.datastore)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.room)
implementation(libs.androidx.appcompat)
implementation(libs.iconics)
implementation(libs.markwon)
implementation(libs.markwon.editor)
implementation(libs.markwon.linkify)
implementation(libs.markwon.strikethrough)
implementation(libs.markwon.tables)
implementation(libs.markwon.tasklist)
implementation("androidx.fragment:fragment-ktx:1.3.2")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}")
implementation("androidx.room:room-ktx:${Versions.room}")
kapt("androidx.room:room-compiler:${Versions.room}")
implementation("androidx.appcompat:appcompat:1.3.0-rc01")
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
implementation("androidx.paging:paging-runtime:2.1.2")
implementation("io.noties.markwon:core:${Versions.markwon}")
debugImplementation(libs.facebook.flipper)
debugImplementation(libs.facebook.flipper.network)
debugImplementation(libs.facebook.soloader)
debugImplementation(libs.leakcanary)
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation(libs.kotlin.reflect)
debugImplementation("com.facebook.flipper:flipper:${Versions.flipper}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${Versions.flipper}")
debugImplementation("com.facebook.soloader:soloader:0.10.1")
debugImplementation("com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}")
debugImplementation("androidx.compose.ui:ui-tooling:${Versions.compose}")
debugImplementation("org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}")
implementation(libs.kotlin.jdk8)
implementation(libs.kotlinx.immutable)
implementation(libs.kotlinx.serialization)
implementation(libs.okhttp)
implementation(libs.persistent.cookiejar)
implementation(libs.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.androidx.preference)
implementation(libs.timber)
implementation(libs.dashclock.api)
implementation(libs.locale) {
implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.3")
implementation("com.squareup.okhttp3:okhttp:${Versions.okhttp}")
implementation("com.google.code.gson:gson:2.8.6")
implementation("com.google.android.material:material:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta01")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.preference:preference:1.1.1")
implementation("com.jakewharton.timber:timber:4.7.1")
implementation("com.google.android.apps.dashclock:dashclock-api:2.0.0")
implementation("com.twofortyfouram:android-plugin-api-for-locale:1.0.2") {
isTransitive = false
}
implementation(libs.jchronic) {
implementation("com.rubiconproject.oss:jchronic:0.2.6") {
isTransitive = false
}
implementation(libs.shortcut.badger)
implementation(libs.google.api.tasks)
implementation(libs.google.api.drive)
implementation(libs.google.oauth2)
implementation(libs.androidx.work)
implementation(libs.etebase)
implementation(libs.colorpicker)
implementation(libs.appauth)
implementation(libs.osmdroid)
implementation(libs.retrofit)
implementation(libs.retrofit.moshi)
implementation(libs.androidx.recyclerview)
implementation(platform(libs.androidx.compose))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")
implementation("androidx.compose.runtime:runtime-livedata")
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.material.icons.extended)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation("androidx.compose.ui:ui-viewbinding")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation(libs.coil.compose)
implementation(libs.coil.video)
implementation(libs.coil.svg)
implementation(libs.coil.gif)
implementation("me.leolin:ShortcutBadger:1.1.22@aar")
implementation("com.google.apis:google-api-services-tasks:v1-rev20210109-1.31.0")
implementation("com.google.apis:google-api-services-drive:v3-rev20210228-1.31.0")
implementation("com.google.auth:google-auth-library-oauth2-http:0.25.2")
implementation("androidx.work:work-runtime:${Versions.work}")
implementation("androidx.work:work-runtime-ktx:${Versions.work}")
implementation("com.etesync:journalmanager:1.1.1")
implementation("com.etebase:client:2.3.2")
implementation("com.github.QuadFlask:colorpicker:0.0.15")
implementation("net.openid:appauth:0.8.1")
implementation("org.osmdroid:osmdroid-android:6.1.10@aar")
implementation(libs.accompanist.flowlayout)
implementation(libs.accompanist.permissions)
implementation(libs.accompanist.systemuicontroller)
implementation("androidx.compose.ui:ui:${Versions.compose}")
implementation("androidx.compose.foundation:foundation:${Versions.compose}")
implementation("androidx.compose.material:material:${Versions.compose}")
implementation("androidx.compose.runtime:runtime-livedata:${Versions.compose}")
implementation("com.google.android.material:compose-theme-adapter:${Versions.compose}")
implementation("androidx.activity:activity-compose:1.3.0-alpha05")
releaseCompileOnly("androidx.compose.ui:ui-tooling:${Versions.compose}")
googleplayImplementation(platform(libs.firebase))
googleplayImplementation(libs.firebase.crashlytics)
googleplayImplementation(libs.firebase.analytics) {
googleplayImplementation("com.google.firebase:firebase-crashlytics:${Versions.crashlytics}")
googleplayImplementation("com.google.firebase:firebase-analytics:${Versions.analytics}") {
exclude("com.google.android.gms", "play-services-ads-identifier")
}
googleplayImplementation(libs.firebase.config.ktx)
googleplayImplementation(libs.play.services.location)
googleplayImplementation(libs.play.services.maps)
googleplayImplementation(libs.play.billing.ktx)
googleplayImplementation(libs.play.review)
googleplayImplementation(libs.play.services.oss.licenses)
googleplayImplementation(libs.horologist.datalayer.phone)
googleplayImplementation(libs.horologist.datalayer.grpc)
googleplayImplementation(libs.horologist.datalayer.core)
googleplayImplementation(libs.play.services.wearable)
googleplayImplementation(projects.wearDatalayer)
googleplayImplementation("com.google.firebase:firebase-config-ktx:${Versions.remote_config}")
googleplayImplementation("com.google.android.gms:play-services-location:18.0.0")
googleplayImplementation("com.google.android.gms:play-services-maps:17.0.0")
googleplayImplementation("com.android.billingclient:billing:1.2.2")
androidTestImplementation(libs.dagger.hilt.testing)
kspAndroidTest(libs.dagger.hilt.compiler)
kspAndroidTest(libs.androidx.hilt.compiler)
androidTestImplementation(libs.mockito.android)
androidTestImplementation(libs.make.it.easy)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.okhttp.mockwebserver)
androidTestImplementation("com.google.dagger:hilt-android-testing:${Versions.hilt}")
kaptAndroidTest("com.google.dagger:hilt-compiler:${Versions.hilt}")
kaptAndroidTest("androidx.hilt:hilt-compiler:${Versions.hilt_androidx}")
androidTestImplementation("org.mockito:mockito-android:${Versions.mockito}")
androidTestImplementation("com.natpryce:make-it-easy:${Versions.make_it_easy}")
androidTestImplementation("androidx.test:runner:${Versions.androidx_test}")
androidTestImplementation("androidx.test:rules:${Versions.androidx_test}")
androidTestImplementation("androidx.test.ext:junit:1.1.2")
androidTestImplementation("com.squareup.okhttp3:mockwebserver:${Versions.okhttp}")
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.make.it.easy)
testImplementation(libs.androidx.test.core)
testImplementation(libs.mockito.core)
testImplementation(libs.xpp3)
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3")
testImplementation("com.natpryce:make-it-easy:${Versions.make_it_easy}")
testImplementation("androidx.test:core:${Versions.androidx_test}")
testImplementation("org.mockito:mockito-core:${Versions.mockito}")
testImplementation("org.ogce:xpp3:1.1.6")
}
apply(mapOf("plugin" to "com.google.gms.google-services"))

@ -0,0 +1,263 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!-- https://raw.githubusercontent.com/checkstyle/checkstyle/checkstyle-8.16/src/main/resources/google_checks.xml -->
<!--
Checkstyle configuration that checks the Google coding conventions from Google Java Style
that can be found at https://google.github.io/styleguide/javaguide.html.
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
To completely disable a check, just comment it out or delete it from the file.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
<module name = "Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="TreeWalker">
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="LineLength">
<property name="max" value="100"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="2"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="2"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="2"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
</module>
<module name="MethodParamPad"/>
<module name="NoWhitespaceBefore">
<property name="tokens"
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="JavadocTagContinuationIndentation">
<property name="severity" value="ignore" />
</module>
<module name="SummaryJavadoc">
<property name="severity" value="ignore" />
<property name="forbiddenSummaryFragments"
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<module name="JavadocParagraph">
<property name="severity" value="ignore" />
</module>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="severity" value="ignore" />
<property name="scope" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="minLineCount" value="2"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc">
<property name="severity" value="ignore"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="CommentsIndentation"/>
</module>
</module>

@ -0,0 +1,918 @@
- artifact: com.gitlab.abaker:dav4jvm:+
name: dav4jvm
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
license: Mozilla Public License, Version 2.0
licenseUrl: https://www.mozilla.org/en-US/MPL/2.0/
- artifact: com.gitlab.abaker:ical4android:+
name: ical4android
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
license: GNU General Public License, Version 3.0
licenseUrl: https://www.gnu.org/licenses/gpl.txt
- artifact: com.gitlab.bitfireAT:cert4android:+
name: cert4android
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
licenseUrl: https://www.gnu.org/licenses/gpl.txt
license: GNU General Public License, Version 3.0
- artifact: androidx.coordinatorlayout:coordinatorlayout:+
name: Android Support Library Coordinator Layout
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.constraintlayout:constraintlayout:+
name: Android ConstraintLayout
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
- artifact: androidx.sqlite:sqlite:+
name: Android DB
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.apis:google-api-services-drive:+
name: Drive API v3-rev136-1.25.0
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.fragment:fragment:+
name: Android Support Library fragment
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.vectordrawable:vectordrawable-animated:+
name: Android Support AnimatedVectorDrawable
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.core:core:+
name: Android Support Library compat
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.arch.core:core-common:+
name: Android Arch-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.room:room-common:+
name: Android Room-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.room:room-runtime:+
name: Android Room-Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.code.gson:gson:+
name: Gson
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: me.leolin:ShortcutBadger:+
name: ShortcutBadger
copyrightHolder: Leo Lin
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/leolin310148/ShortcutBadger
- artifact: androidx.lifecycle:lifecycle-runtime:+
name: Android Lifecycle Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.versionedparcelable:versionedparcelable:+
name: VersionedParcelable and friends
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.viewpager:viewpager:+
name: Android Support Library View Pager
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-livedata:+
name: Android Lifecycle LiveData
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: commons-codec:commons-codec:+
name: Apache Commons Codec
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://commons.apache.org/proper/commons-codec/
- artifact: androidx.annotation:annotation:+
name: Android Support Library Annotations
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.interpolator:interpolator:+
name: Android Support Library Interpolators
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: javax.inject:javax.inject:+
name: javax.inject
copyrightHolder: The JSR-330 Expert Group
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://code.google.com/p/atinject/
- artifact: com.twofortyfouram:android-plugin-api-for-locale:+
name: android-plugin-api-for-locale
copyrightHolder: two forty four a.m. LLC.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-viewmodel:+
name: Android Lifecycle ViewModel
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.dagger:dagger:+
name: Dagger
copyrightHolder: The Dagger Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/google/dagger
- artifact: com.google.guava:guava:+
name: Guava Google Core Libraries for Java
copyrightHolder: The Guava Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.jetbrains:annotations:+
name: JetBrains Java Annotations
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JetBrains/java-annotations
- artifact: org.apache.commons:commons-lang3:+
name: Apache Commons Lang
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://commons.apache.org/proper/commons-lang/
- artifact: androidx.loader:loader:+
name: Android Support Library loader
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.cursoradapter:cursoradapter:+
name: Android Support Library Cursor Adapter
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-livedata-core:+
name: Android Lifecycle LiveData Core
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.customview:customview:+
name: Android Support Library Custom View
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: io.reactivex.rxjava2:rxandroid:+
name: RxAndroid
copyrightHolder: The RxAndroid authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/ReactiveX/RxAndroid
- artifact: androidx.swiperefreshlayout:swiperefreshlayout:+
name: Android Support Library Custom View
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-extensions:+
name: Android Lifecycle Extensions
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.arch.core:core-runtime:+
name: Android Arch-Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: org.apache.commons:commons-collections4:+
name: Apache Commons Collections
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://commons.apache.org/proper/commons-collections/
- artifact: org.mnode.ical4j:ical4j:+
name: ical4j
copyrightHolder: Ben Fortuna
license: BSD 3-Clause
licenseUrl: https://opensource.org/licenses/BSD-3-Clause
url: http://ical4j.github.io
forceGenerate: true
- artifact: androidx.recyclerview:recyclerview:+
name: Android Support RecyclerView v7
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.collection:collection:+
name: Android Support Library collections
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.cardview:cardview:+
name: Android Support CardView v7
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.rubiconproject.oss:jchronic:+
name: jchronic
copyrightHolder: The jchronic authors
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: http://github.com/samtingleff/jchronic
- artifact: androidx.sqlite:sqlite-framework:+
name: Android Support SQLite - Framework Implementation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.android.material:material:+
name: Material Components for Android
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.google.android.apps.dashclock:dashclock-api:+
name: DashClock API
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://dashclock.com/api
- artifact: androidx.vectordrawable:vectordrawable:+
name: Android Support VectorDrawable
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: org.reactivestreams:reactive-streams:+
name: reactive-streams
copyrightHolder: Public domain
license: CC0
licenseUrl: http://creativecommons.org/publicdomain/zero/1.0/
url: http://www.reactive-streams.org/
- artifact: androidx.work:work-runtime:+
name: Android WorkManager Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.appcompat:appcompat:+
name: Android AppCompat Library v7
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-common:+
name: Android Lifecycle-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-process:+
name: Android Lifecycle Process
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-service:+
name: Android Lifecycle Service
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.transition:transition:+
name: Android Transition Support Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.jakewharton.timber:timber:+
name: Timber
copyrightHolder: Jake Wharton
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JakeWharton/timber
- artifact: com.google.oauth-client:google-oauth-client:+
name: Google OAuth Client Library for Java
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.drawerlayout:drawerlayout:+
name: Android Support Library Drawer Layout
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: io.reactivex.rxjava2:rxjava:+
name: RxJava
copyrightHolder: RxJava Contributors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/ReactiveX/RxJava
- artifact: com.google.apis:google-api-services-tasks:+
name: Tasks API v1-rev55-1.25.0
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.google.api-client:google-api-client:+
name: Google APIs Client Library for Java
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.jetbrains.kotlin:kotlin-stdlib:+
name: org.jetbrains.kotlin:kotlin-stdlib
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: com.google.http-client:google-http-client:+
name: Google HTTP Client Library for Java
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.slf4j:slf4j-jdk14:+
name: SLF4J JDK14 Binding
copyrightHolder: QOS.ch
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: http://www.slf4j.org
- artifact: com.squareup.okhttp3:okhttp:+
name: OkHttp
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.slf4j:slf4j-api:+
name: SLF4J API Module
copyrightHolder: QOS.ch
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: http://www.slf4j.org
- artifact: org.jetbrains.kotlin:kotlin-stdlib-common:+
name: org.jetbrains.kotlin:kotlin-stdlib-common
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: com.squareup.okhttp3:logging-interceptor:+
name: OkHttp Logging Interceptor
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.jetbrains.kotlin:kotlin-stdlib-jdk7:+
name: org.jetbrains.kotlin:kotlin-stdlib-jdk7
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: io.grpc:grpc-context:+
name: io.grpc:grpc-context
copyrightHolder: The gRPC Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/grpc/grpc-java
- artifact: com.google.guava:listenablefuture:+
name: Guava ListenableFuture only
copyrightHolder: The Guava Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: io.opencensus:opencensus-api:+
name: OpenCensus API
copyrightHolder: OpenCensus Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/census-instrumentation/opencensus-java
- artifact: com.google.guava:failureaccess:+
name: Guava InternalFutureFailureAccess and InternalFutures
copyrightHolder: The Guava Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: io.opencensus:opencensus-contrib-http-util:+
name: OpenCensus contrib-http-util
copyrightHolder: OpenCensus Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/census-instrumentation/opencensus-java
- artifact: androidx.core:core-ktx:+
name: Core Kotlin Extensions
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.appcompat:appcompat-resources:+
name: Android Resources Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.viewpager2:viewpager2:+
name: AndroidX Widget ViewPager2
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.savedstate:savedstate:+
name: Activity
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.activity:activity:+
name: Activity
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.paging:paging-runtime:+
name: Android Paging-Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.paging:paging-common:+
name: Android Paging-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: org.conscrypt:conscrypt-android:+
name: org.conscrypt:conscrypt-android
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0
url: https://conscrypt.org/
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-android:+
name: kotlinx-coroutines-android
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: androidx.databinding:databinding-adapters:+
name: databinding-adapters
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-viewmodel-ktx:+
name: Android Lifecycle ViewModel Kotlin Extensions
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.annotation:annotation-experimental:+
name: Experimental annotation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:+
name: kotlinx-coroutines-core-jvm
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: androidx.databinding:databinding-common:+
name: Data Binding Base Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/studio
- artifact: androidx.databinding:databinding-runtime:+
name: databinding-runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.databinding:viewbinding:+
name: viewbinding
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.madgag.spongycastle:core:+
name: Spongy Castle Core
copyrightHolder: The Legion of the Bouncy Castle Inc.
license: Bouncy Castle Licence
licenseUrl: http://www.bouncycastle.org/licence.html
url: http://rtyley.github.io/spongycastle/
- artifact: com.etesync:journalmanager:+
name: EteSync JVM
copyrightHolder: Tom Hacohen
license: LGPL-3.0-only
licenseUrl: https://spdx.org/licenses/LGPL-3.0-only.html
url: https://www.etesync.com
- artifact: com.madgag.spongycastle:prov:+
name: Spongy Castle
copyrightHolder: The Legion of the Bouncy Castle Inc.
license: Bouncy Castle Licence
licenseUrl: http://www.bouncycastle.org/licence.html
url: http://rtyley.github.io/spongycastle/
- artifact: androidx.preference:preference:+
name: AndroidX Preference
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.lifecycle:lifecycle-viewmodel-savedstate:+
name: Android Lifecycle ViewModel with SavedState
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.github.QuadFlask:colorpicker:+
name: QuadFlask/colorpicker
copyrightHolder: QuadFlask
license: The Apache Software License, Version 2.0
url: https://github.com/QuadFlask/colorpicker
- artifact: com.google.auth:google-auth-library-credentials:+
name: Google Auth Library for Java - Credentials
copyrightHolder: Google Inc.
license: BSD 3-Clause
- artifact: com.google.auth:google-auth-library-oauth2-http:+
name: Google Auth Library for Java - OAuth2 HTTP
copyrightHolder: Google Inc.
license: BSD 3-Clause
- artifact: com.google.auto.value:auto-value-annotations:+
name: AutoValue Annotations
copyrightHolder: Google LLC
license: The Apache Software License, Version 2.0
url: https://github.com/google/auto/tree/master/value
- artifact: com.sun.mail:android-mail:+
name: android-mail
copyrightHolder: Oracle and/or its affiliates
license: Eclipse Public License, Version 2.0
- artifact: commons-io:commons-io:+
name: commons-io
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
url: http://commons.apache.org/proper/commons-io/
- artifact: com.sun.mail:android-activation:+
name: android-activation
copyrightHolder: Oracle and/or its affiliates
license: Eclipse Public License, Version 2.0
- artifact: androidx.hilt:hilt-work:+
name: hilt-work
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.hilt:hilt-common:+
name: hilt-common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.dagger:hilt-android:+
name: hilt-android
copyrightHolder: The Dagger Authors
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/google/dagger
- artifact: com.google.dagger:dagger-lint-aar:+
name: dagger-lint-aar
copyrightHolder: The Dagger Authors
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/google/dagger
- artifact: androidx.room:room-ktx:+
name: room-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-runtime-ktx:+
name: lifecycle-runtime-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.fragment:fragment-ktx:+
name: fragment-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.activity:activity-ktx:+
name: activity-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.collection:collection-ktx:+
name: collection-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-livedata-core-ktx:+
name: lifecycle-livedata-core-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.work:work-runtime-ktx:+
name: work-runtime-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.github.dmfs.opentasks:opentasks-provider:+
name: opentasks-provider
copyrightHolder: dmfs GmbH
license: The Apache Software License, Version 2.0
licenseUrl: https://api.github.com/licenses/apache-2.0
url: https://github.com/dmfs/opentasks
- artifact: com.github.dmfs.opentasks:opentasks-contract:+
name: opentasks-contract
copyrightHolder: dmfs GmbH
license: The Apache Software License, Version 2.0
licenseUrl: https://api.github.com/licenses/apache-2.0
url: https://github.com/dmfs/opentasks
forceGenerate: true
- artifact: org.dmfs:lib-recur:+
name: lib-recur
copyrightHolder: Marten Gajda
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/license/LICENSE-2.0.txt
url: https://github.com/dmfs/lib-recur
- artifact: org.dmfs:rfc5545-datetime:+
name: rfc5545-datetime
copyrightHolder: Marten Gajda
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/dmfs/rfc5545-datetime
- artifact: org.dmfs:jems:+
name: jems
copyrightHolder: dmfs GmbH
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/license/LICENSE-2.0.txt
url: https://github.com/dmfs/jems
- artifact: org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:+
name: kotlinx-collections-immutable-jvm
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
- artifact: com.squareup.okio:okio:+
name: okio
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/square/okio/
- artifact: com.google.dagger:hilt-core:+
name: hilt-core
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/google/dagger
- artifact: io.noties.markwon:core:+
name: core
copyrightHolder: Dimitry Ivanov
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/noties/Markwon
- artifact: com.atlassian.commonmark:commonmark:+
name: commonmark
copyrightHolder: Atlassian and others
license: BSD 2-Clause
- artifact: androidx.browser:browser:+
name: browser
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.etebase:client:+
name: client
copyrightHolder: Tom Hacohen
license: LGPL-3.0-only
licenseUrl: https://spdx.org/licenses/LGPL-3.0-only.html
url: https://www.etebase.com
- artifact: net.openid:appauth:+
name: appauth
copyrightHolder: The AppAuth for Android Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/openid/AppAuth-Android
- artifact: androidx.concurrent:concurrent-futures:+
name: concurrent-futures
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-livedata-ktx:+
name: lifecycle-livedata-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.dynamicanimation:dynamicanimation:+
name: dynamicanimation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.legacy:legacy-support-core-utils:+
name: legacy-support-core-utils
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.documentfile:documentfile:+
name: documentfile
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.localbroadcastmanager:localbroadcastmanager:+
name: localbroadcastmanager
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.print:print:+
name: print
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.google.http-client:google-http-client-gson:+
name: google-http-client-gson
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
- artifact: org.osmdroid:osmdroid-android:+
name: osmdroid-android
copyrightHolder: The OsmDroid Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0
url: https://github.com/osmdroid/osmdroid
- artifact: androidx.savedstate:savedstate-ktx:+
name: savedstate-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/savedstate#1.1.0
- artifact: androidx.tracing:tracing:+
name: tracing
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/tracing#1.0.0
- artifact: androidx.databinding:databinding-ktx:+
name: databinding-ktx
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.jetbrains.kotlin:kotlin-stdlib-jdk8:+
name: kotlin-stdlib-jdk8
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: androidx.compose.ui:ui:+
name: ui
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-ui#1.0.0-beta01
- artifact: androidx.compose.animation:animation-core:+
name: animation-core
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-animation#1.0.0-beta01
- artifact: androidx.compose.ui:ui-text:+
name: ui-text
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-ui#1.0.0-beta01
- artifact: androidx.compose.runtime:runtime-saveable:+
name: runtime-saveable
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.0.0-beta01
- artifact: androidx.compose.ui:ui-graphics:+
name: ui-graphics
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-ui#1.0.0-beta01
- artifact: androidx.compose.ui:ui-unit:+
name: ui-unit
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-ui#1.0.0-beta01
- artifact: androidx.compose.ui:ui-geometry:+
name: ui-geometry
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-ui#1.0.0-beta01
- artifact: androidx.compose.ui:ui-util:+
name: ui-util
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-ui#1.0.0-beta01
- artifact: androidx.compose.runtime:runtime:+
name: runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.0.0-beta01
- artifact: androidx.autofill:autofill:+
name: autofill
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.compose.foundation:foundation:+
name: foundation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.0.0-beta01
- artifact: androidx.compose.animation:animation:+
name: animation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-animation#1.0.0-beta01
- artifact: androidx.compose.foundation:foundation-layout:+
name: foundation-layout
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.0.0-beta01
- artifact: androidx.compose.material:material:+
name: material
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-material#1.0.0-beta01
- artifact: androidx.compose.material:material-ripple:+
name: material-ripple
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-material#1.0.0-beta01
- artifact: androidx.compose.material:material-icons-core:+
name: material-icons-core
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-material#1.0.0-beta01
- artifact: androidx.compose.ui:ui-tooling:+
skip: true
- artifact: androidx.compose.ui:ui-tooling-data:+
skip: true
- artifact: androidx.compose.runtime:runtime-livedata:+
name: runtime-livedata
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.0.0-beta01
- artifact: com.google.android.material:compose-theme-adapter:+
name: compose-theme-adapter
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/material-components/material-components-android-compose-theme-adapter/
- artifact: androidx.constraintlayout:constraintlayout-core:+
name: constraintlayout-core
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
- artifact: androidx.activity:activity-compose:+
name: activity-compose
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/activity#1.3.0-alpha04

17
app/proguard.pro vendored

@ -16,7 +16,9 @@
-dontwarn javax.inject.**
-dontwarn com.google.j2objc.annotations.**
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn com.google.errorprone.annotations.**
-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
-dontwarn com.google.errorprone.annotations.concurrent.LazyInit
-dontwarn com.google.errorprone.annotations.ForOverride
# https://github.com/square/okhttp/blob/0b74bba08805c28f6aede626cf06f213ef6480f2/README.md
-dontwarn okhttp3.**
@ -47,15 +49,4 @@
-dontnote java.nio.file.Files, java.nio.file.Path
-dontnote **.ILicensingService
-dontnote sun.misc.Unsafe
-dontwarn sun.misc.Unsafe
# errors from upgrading to AGP 8
-dontwarn java.beans.Transient
-dontwarn org.joda.convert.FromString
-dontwarn org.joda.convert.ToString
-dontwarn org.json.JSONString
# material icons
-keep class androidx.compose.material.icons.outlined.** { *; }
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite { <fields>; }
-dontwarn sun.misc.Unsafe

@ -5,372 +5,451 @@
*/
package com.todoroo.andlib.utility
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.tasks.SuspendFreeze
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.TestUtilities.withLocale
import org.tasks.Freeze.Companion.freezeAt
import org.tasks.date.DateTimeUtils
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.extensions.Context.is24HourOverride
import org.tasks.kmp.formatDayOfWeek
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.TextStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.time.DateTime
import java.util.Locale
import java.time.format.FormatStyle
import java.util.*
@RunWith(AndroidJUnit4::class)
class DateUtilitiesTest {
@After
fun after() {
is24HourOverride = null
DateUtilities.is24HourOverride = null
}
@Test
fun testGet24HourTime() {
is24HourOverride = true
assertEquals("09:05", getTimeString(DateTime(2014, 1, 4, 9, 5, 36).millis, is24HourFormat))
assertEquals("13:00", getTimeString(DateTime(2014, 1, 4, 13, 0, 1).millis, is24HourFormat))
DateUtilities.is24HourOverride = true
assertEquals("09:05", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 9, 5, 36)))
assertEquals("13:00", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 13, 0, 1)))
}
@Test
fun testGetTime() {
is24HourOverride = false
assertEquals("9:05 AM", getTimeString(DateTime(2014, 1, 4, 9, 5, 36).millis, is24HourFormat))
assertEquals("1:05 PM", getTimeString(DateTime(2014, 1, 4, 13, 5, 36).millis, is24HourFormat))
DateUtilities.is24HourOverride = false
assertEquals("9:05 AM", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 9, 5, 36)))
assertEquals("1:05 PM", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 13, 5, 36)))
}
@Test
fun testGetTimeWithNoMinutes() {
is24HourOverride = false
assertEquals("1 PM", getTimeString(DateTime(2014, 1, 4, 13, 0, 59).millis, is24HourFormat)) // derp?
DateUtilities.is24HourOverride = false
assertEquals("1 PM", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 13, 0, 59))) // derp?
}
@Test
fun testGetDateStringWithYear() = runBlocking {
assertEquals("Jan 4, 2014", getRelativeDay(DateTime(2014, 1, 4, 0, 0, 0).millis))
fun testGetDateStringWithYear() {
assertEquals("Jan 4, 2014", DateUtilities.getDateString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 0, 0, 0)))
}
@Test
fun testGetDateStringHidingYear() = runBlocking {
fun testGetDateStringHidingYear() {
freezeAt(DateTimeUtils.newDate(2014, 2, 1)) {
assertEquals("Jan 1", getRelativeDay(DateTime(2014, 1, 1).millis))
assertEquals("Jan 1", DateUtilities.getDateString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 1)))
}
}
@Test
fun testGetDateStringWithDifferentYear() = runBlocking {
fun testGetDateStringWithDifferentYear() {
freezeAt(DateTimeUtils.newDate(2013, 12, 1)) {
assertEquals("Jan 1, 2014", getRelativeDay(DateTime(2014, 1, 1, 0, 0, 0).millis))
assertEquals("Jan 1, 2014", DateUtilities.getDateString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 1, 0, 0, 0)))
}
}
@Test
fun testGetWeekdayLongString() = withLocale(Locale.US) {
assertEquals("Sunday", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 29).millis, TextStyle.FULL))
assertEquals("Monday", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 30).millis, TextStyle.FULL))
assertEquals("Tuesday", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 31).millis, TextStyle.FULL))
assertEquals("Wednesday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 1).millis, TextStyle.FULL))
assertEquals("Thursday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 2).millis, TextStyle.FULL))
assertEquals("Friday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 3).millis, TextStyle.FULL))
assertEquals("Saturday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 4).millis, TextStyle.FULL))
fun testGetWeekdayLongString() {
assertEquals("Sunday", DateUtilities.getWeekday(DateTimeUtils.newDate(2013, 12, 29), Locale.US))
assertEquals("Monday", DateUtilities.getWeekday(DateTimeUtils.newDate(2013, 12, 30), Locale.US))
assertEquals("Tuesday", DateUtilities.getWeekday(DateTimeUtils.newDate(2013, 12, 31), Locale.US))
assertEquals("Wednesday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 1), Locale.US))
assertEquals("Thursday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 2), Locale.US))
assertEquals("Friday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 3), Locale.US))
assertEquals("Saturday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 4), Locale.US))
}
@Test
fun testGetWeekdayShortString() = withLocale(Locale.US) {
assertEquals("Sun", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 29).millis, TextStyle.SHORT))
assertEquals("Mon", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 30).millis, TextStyle.SHORT))
assertEquals("Tue", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 31).millis, TextStyle.SHORT))
assertEquals("Wed", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 1).millis, TextStyle.SHORT))
assertEquals("Thu", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 2).millis, TextStyle.SHORT))
assertEquals("Fri", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 3).millis, TextStyle.SHORT))
assertEquals("Sat", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 4).millis, TextStyle.SHORT))
fun testGetWeekdayShortString() {
assertEquals("Sun", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2013, 12, 29), Locale.US))
assertEquals("Mon", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2013, 12, 30), Locale.US))
assertEquals("Tue", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2013, 12, 31), Locale.US))
assertEquals("Wed", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 1), Locale.US))
assertEquals("Thu", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 2), Locale.US))
assertEquals("Fri", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 3), Locale.US))
assertEquals("Sat", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 4), Locale.US))
}
@Test
fun getRelativeFullDate() = withLocale(Locale.US) {
fun getRelativeFullDate() {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"Sunday, January 14",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"Sunday, January 14",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.US,
FormatStyle.FULL))
}
}
@Test
fun getRelativeFullDateWithYear() = withLocale(Locale.US) {
fun getRelativeFullDateWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"Sunday, January 14, 2018",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"Sunday, January 14, 2018",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.US,
FormatStyle.FULL))
}
}
@Test
fun getRelativeFullDateTime() = withLocale(Locale.US) {
fun getRelativeFullDateTime() {
freezeAt(DateTime(2018, 1, 1)) {
assertMatches(
"Sunday, January 14( at)? 1:43 PM",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 43, 1).millis, is24HourFormat, DateStyle.FULL)
)
"Sunday, January 14( at)? 1:43 PM",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 43, 1).millis,
Locale.US,
FormatStyle.FULL))
}
}
@Test
@Ignore("Fails on CI - need to investigate")
fun getRelativeDateTimeWithAlwaysDisplayFullDateOption() = withLocale(Locale.US) {
fun getRelativeDateTimeWithAlwaysDisplayFullDateOption() {
freezeAt(DateTime(2020, 1, 1)) {
assertMatches(
"Thursday, January 2 at 11:50 AM",
getRelativeDateTime(DateTime(2020, 1, 2, 11, 50, 1).millis, is24HourFormat, DateStyle.FULL, true, false)
)
"Thursday, January 2 at 11:50 AM",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2020, 1, 2, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL,
true,
false
))
}
}
@Test
fun getRelativeFullDateTimeWithYear() = withLocale(Locale.US) {
fun getRelativeFullDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"Sunday, January 14, 2018( at)? 11:50 AM",
getRelativeDateTime(DateTime(2018, 1, 14, 11, 50, 1).millis, is24HourFormat, DateStyle.FULL)
)
"Sunday, January 14, 2018( at)? 11:50 AM",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL))
}
}
@Test
fun getRelativeDayWithAlwaysDisplayFullDateOption() = withLocale(Locale.US) {
fun getRelativeDayWithAlwaysDisplayFullDateOption() {
freezeAt(DateTime(2020, 1, 1)) {
assertEquals(
"Thursday, January 2",
getRelativeDay(DateTime(2020, 1, 2, 11, 50, 1).millis, DateStyle.FULL, alwaysDisplayFullDate = true, lowercase = true)
DateUtilities.getRelativeDay(
ApplicationProvider.getApplicationContext(),
DateTime(2020, 1, 2, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL,
true,
true
)
)
}
}
@Test
fun getRelativeDayWithoutAlwaysDisplayFullDateOption() = withLocale(Locale.US) {
fun getRelativeDayWithoutAlwaysDisplayFullDateOption() {
freezeAt(DateTime(2020, 1, 1)) {
assertEquals(
"tomorrow",
getRelativeDay(DateTime(2020, 1, 2, 11, 50, 1).millis, DateStyle.FULL, lowercase = true)
DateUtilities.getRelativeDay(
ApplicationProvider.getApplicationContext(),
DateTime(2020, 1, 2, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL,
false,
true
)
)
}
}
@Test
fun germanDateNoYear() = withLocale(Locale.GERMAN) {
fun germanDateNoYear() {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"Sonntag, 14. Januar",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"Sonntag, 14. Januar",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.GERMAN,
FormatStyle.FULL))
}
}
@Test
fun germanDateWithYear() = withLocale(Locale.GERMAN) {
fun germanDateWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"Sonntag, 14. Januar 2018",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"Sonntag, 14. Januar 2018",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.GERMAN,
FormatStyle.FULL))
}
}
@Test
fun koreanDateNoYear() = withLocale(Locale.KOREAN) {
fun koreanDateNoYear() {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1월 14일 일요일",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"1월 14일 일요일",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.KOREAN,
FormatStyle.FULL))
}
}
@Test
fun koreanDateWithYear() = withLocale(Locale.KOREAN) {
fun koreanDateWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018년 1월 14일 일요일",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"2018년 1월 14일 일요일",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.KOREAN,
FormatStyle.FULL))
}
}
@Test
fun japaneseDateNoYear() = withLocale(Locale.JAPANESE) {
fun japaneseDateNoYear() {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1月14日日曜日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"1月14日日曜日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.JAPANESE,
FormatStyle.FULL))
}
}
@Test
fun japaneseDateWithYear() = withLocale(Locale.JAPANESE) {
fun japaneseDateWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018年1月14日日曜日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"2018年1月14日日曜日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.JAPANESE,
FormatStyle.FULL))
}
}
@Test
fun chineseDateNoYear() = withLocale(Locale.CHINESE) {
fun chineseDateNoYear() {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1月14日星期日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"1月14日星期日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.CHINESE,
FormatStyle.FULL))
}
}
@Test
fun chineseDateWithYear() = withLocale(Locale.CHINESE) {
SuspendFreeze.freezeAt(DateTime(2017, 12, 12)) {
fun chineseDateWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018年1月14日星期日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
"2018年1月14日星期日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.CHINESE,
FormatStyle.FULL))
}
}
@Test
fun chineseDateTimeNoYear() = withLocale(Locale.CHINESE) {
fun chineseDateTimeNoYear() {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1月14日星期日 上午11:53",
getRelativeDateTime(DateTime(2018, 1, 14, 11, 53, 1).millis, is24HourFormat, DateStyle.FULL)
)
"1月14日星期日 上午11:53",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 11, 53, 1).millis,
Locale.CHINESE,
FormatStyle.FULL))
}
}
@Test
fun chineseDateTimeWithYear() = withLocale(Locale.CHINESE) {
fun chineseDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018年1月14日星期日 下午1:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"2018年1月14日星期日 下午1:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.CHINESE,
FormatStyle.FULL))
}
}
@Test
fun frenchDateTimeWithYear() = withLocale(Locale.FRENCH) {
fun frenchDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"dimanche 14 janvier 2018( à)? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"dimanche 14 janvier 2018( à)? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.FRENCH,
FormatStyle.FULL))
}
}
@Test
fun indiaDateTimeWithYear() = withLocale(Locale.forLanguageTag("hi-IN")) {
fun indiaDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"रविवार, 14 जनवरी 2018( को)? 1:45 pm",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"रविवार, 14 जनवरी 2018( को)? 1:45 pm",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("hi-IN"),
FormatStyle.FULL))
}
}
@Test
fun russiaDateTimeNoYear() = withLocale(Locale.forLanguageTag("ru")) {
fun russiaDateTimeNoYear() {
freezeAt(DateTime(2018, 12, 12)) {
assertMatches(
"воскресенье, 14 января,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"воскресенье, 14 января,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("ru"),
FormatStyle.FULL))
}
}
@Test
fun russiaDateTimeWithYear() = withLocale(Locale.forLanguageTag("ru")) {
fun russiaDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"воскресенье, 14 января 2018 г.,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"воскресенье, 14 января 2018 г.,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("ru"),
FormatStyle.FULL))
}
}
@Test
fun brazilDateTimeNoYear() = withLocale(Locale.forLanguageTag("pt-br")) {
fun brazilDateTimeNoYear() {
freezeAt(DateTime(2018, 12, 12)) {
assertEquals(
"domingo, 14 de janeiro 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"domingo, 14 de janeiro 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("pt-br"),
FormatStyle.FULL))
}
}
@Test
fun brazilDateTimeWithYear() = withLocale(Locale.forLanguageTag("pt-br")) {
fun brazilDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"domingo, 14 de janeiro de 2018 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"domingo, 14 de janeiro de 2018 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("pt-br"),
FormatStyle.FULL))
}
}
@Test
fun spainDateTimeNoYear() = withLocale(Locale.forLanguageTag("es")) {
fun spainDateTimeNoYear() {
freezeAt(DateTime(2018, 12, 12)) {
assertMatches(
"domingo, 14 de enero,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"domingo, 14 de enero,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("es"),
FormatStyle.FULL))
}
}
@Test
fun spainDateTimeWithYear() = withLocale(Locale.forLanguageTag("es")) {
fun spainDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"domingo, 14 de enero de 2018,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"domingo, 14 de enero de 2018,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("es"),
FormatStyle.FULL))
}
}
@Test
fun hebrewDateTimeNoYear() = withLocale(Locale.forLanguageTag("iw")) {
fun hebrewDateTimeNoYear() {
freezeAt(DateTime(2018, 12, 12)) {
assertMatches(
"יום ראשון, 14 בינואר( בשעה)? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"יום ראשון, 14 בינואר( בשעה)? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("iw"),
FormatStyle.FULL))
}
}
@Test
fun hebrewDateTimeWithYear() = withLocale(Locale.forLanguageTag("iw")) {
fun hebrewDateTimeWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"יום ראשון, 14 בינואר 2018( בשעה)? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
"יום ראשון, 14 בינואר 2018( בשעה)? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("iw"),
FormatStyle.FULL))
}
}
private fun assertMatches(regex: String, actual: String) =
assertTrue("expected=$regex\nactual=$actual", actual.matches(Regex(regex)))
private val is24HourFormat: Boolean
get() = InstrumentationRegistry.getInstrumentation().targetContext.is24HourFormat
private fun assertMatches(regex: String, actual: String) = assertTrue(actual.matches(Regex(regex)))
}

@ -1,17 +1,16 @@
package com.todoroo.andlib.utility
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.tasks.Freeze
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.time.DateTime
import java.util.Locale
import java.time.format.FormatStyle
import java.util.*
@RunWith(AndroidJUnit4::class)
class RelativeDayTest {
@ -68,12 +67,12 @@ class RelativeDayTest {
checkRelativeDay(DateTime().plusDays(7), "January 7, 2014", "Jan 7, 2014")
}
private fun checkRelativeDay(now: DateTime, full: String, abbreviated: String) = runBlocking {
private fun checkRelativeDay(now: DateTime, full: String, abbreviated: String) {
assertEquals(
full,
getRelativeDay(now.millis, DateStyle.LONG))
DateUtilities.getRelativeDay(ApplicationProvider.getApplicationContext(), now.millis, Locale.US, FormatStyle.LONG))
assertEquals(
abbreviated,
getRelativeDay(now.millis))
DateUtilities.getRelativeDay(ApplicationProvider.getApplicationContext(), now.millis, Locale.US, FormatStyle.MEDIUM))
}
}

@ -2,10 +2,9 @@ package com.todoroo.astrid.adapter
import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import org.tasks.filters.CaldavFilter
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.dao.TaskDao
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskMover
import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -15,9 +14,9 @@ import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.injection.InjectingTestCase
@ -41,11 +40,10 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var preferences: Preferences
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var taskMover: TaskMover
private lateinit var adapter: CaldavManualSortTaskAdapter
private val tasks = ArrayList<TaskContainer>()
private val filter = CaldavFilter(CaldavCalendar(name = "calendar", uuid = "1234"))
private val filter = CaldavFilter(CaldavCalendar("calendar", "1234"))
private val dataSource = object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position]
@ -58,7 +56,7 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() {
preferences.clear()
preferences.setBoolean(R.string.p_manual_sort, true)
tasks.clear()
adapter = CaldavManualSortTaskAdapter(googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
adapter = CaldavManualSortTaskAdapter(googleTaskDao, caldavDao, taskDao, localBroadcastManager)
adapter.setDataSource(dataSource)
}
@ -218,7 +216,7 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() {
}
private fun move(from: Int, to: Int, indent: Int = 0) = runBlocking {
tasks.addAll(taskDao.fetchTasks { getQuery(preferences, filter) })
tasks.addAll(taskDao.fetchTasks { getQuery(preferences, filter, it) })
val adjustedTo = if (from < to) to + 1 else to // match DragAndDropRecyclerAdapter behavior
adapter.moved(from, adjustedTo, indent)
}
@ -226,7 +224,7 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() {
private fun checkOrder(dateTime: DateTime, index: Int) = checkOrder(dateTime.toAppleEpoch(), index)
private fun checkOrder(order: Long?, index: Int) = runBlocking {
val sortOrder = taskDao.fetch(adapter.getTask(index).id)!!.order
val sortOrder = caldavDao.getTask(adapter.getTask(index).id)!!.order
if (order == null) {
assertNull(sortOrder)
} else {

@ -3,7 +3,6 @@ package com.todoroo.astrid.adapter
import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.service.TaskMover
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -12,9 +11,6 @@ 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
@ -28,7 +24,6 @@ class CaldavTaskAdapterTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var taskMover: TaskMover
private lateinit var adapter: TaskAdapter
private val tasks = ArrayList<TaskContainer>()
@ -38,7 +33,7 @@ class CaldavTaskAdapterTest : InjectingTestCase() {
super.setUp()
tasks.clear()
adapter = TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
adapter = TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager)
adapter.setDataSource(object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position]
@ -199,18 +194,22 @@ class CaldavTaskAdapterTest : InjectingTestCase() {
private fun addTask(vararg properties: PropertyValue<in TaskContainer?, *>) = runBlocking {
val t = newTaskContainer(*properties)
tasks.add(t)
val task = t.task
taskDao.createNew(task)
val caldavTask = CaldavTask(task = t.id, calendar = "calendar")
val caldavTask = CaldavTask(t.id, "calendar")
if (task.parent > 0) {
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.parent)
}
tasks.add(
t.copy(
caldavTask = caldavTask.copy(
id = caldavDao.insert(caldavTask)
)
)
)
}
}
caldavTask.id = caldavDao.insert(caldavTask)
t.caldavTask = caldavTask.toSubset()
}
private fun CaldavTask.toSubset(): SubsetCaldav {
val result = SubsetCaldav()
result.cd_id = id
result.cd_calendar = calendar
result.cd_remote_parent = remoteParent
return result
}
}

@ -2,9 +2,9 @@ package com.todoroo.astrid.adapter
import com.natpryce.makeiteasy.MakeItEasy.with
import com.natpryce.makeiteasy.PropertyValue
import org.tasks.filters.GtasksFilter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.service.TaskMover
import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -13,17 +13,18 @@ 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.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Task
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.GoogleTaskListMaker.REMOTE_ID
import org.tasks.makers.GoogleTaskListMaker.newGoogleTaskList
import org.tasks.makers.GoogleTaskMaker
import org.tasks.makers.GoogleTaskMaker.LIST
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.newTask
import org.tasks.preferences.Preferences
@ -37,11 +38,10 @@ class GoogleTaskManualSortAdapterTest : InjectingTestCase() {
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var preferences: Preferences
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var taskMover: TaskMover
private lateinit var adapter: GoogleTaskManualSortAdapter
private val tasks = ArrayList<TaskContainer>()
private val filter = GtasksFilter(CaldavCalendar(uuid = "1234"))
private val filter = GtasksFilter(newGoogleTaskList(with(REMOTE_ID, "1234")))
private val dataSource = object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position]
@ -416,33 +416,33 @@ class GoogleTaskManualSortAdapterTest : InjectingTestCase() {
preferences.clear()
preferences.setBoolean(R.string.p_manual_sort, true)
tasks.clear()
adapter = GoogleTaskManualSortAdapter(googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
adapter = GoogleTaskManualSortAdapter(googleTaskDao, caldavDao, taskDao, localBroadcastManager)
adapter.setDataSource(dataSource)
}
private fun move(from: Int, to: Int, indent: Int = 0) = runBlocking {
tasks.addAll(taskDao.fetchTasks { getQuery(preferences, filter) })
tasks.addAll(taskDao.fetchTasks { getQuery(preferences, filter, it) })
val adjustedTo = if (from < to) to + 1 else to
adapter.moved(from, adjustedTo, indent)
}
private fun checkOrder(order: Long, index: Int, parent: Long = 0) = runBlocking {
val googleTask = taskDao.fetch(adapter.getTask(index).id)!!
val googleTask = googleTaskDao.getByTaskId(adapter.getTask(index).id)!!
assertEquals(order, googleTask.order)
assertEquals(parent, googleTask.parent)
}
private fun addTask(vararg properties: PropertyValue<in Task?, *>): Long = runBlocking {
val task = newTask(*properties)
val parent = task.parent
task.parent = 0
taskDao.createNew(task)
googleTaskDao.insertAndShift(
task,
newCaldavTask(
with(TASK, task.id),
with(CALENDAR, "1234"),
),
false
)
newGoogleTask(
with(TASK, task.id),
with(LIST, "1234"),
with(GoogleTaskMaker.PARENT, parent)),
false)
task.id
}
}

@ -0,0 +1,78 @@
package com.todoroo.astrid.adapter
import android.content.Context
import androidx.test.core.app.ApplicationProvider
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 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.LocalBroadcastManager
import org.tasks.R
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
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.preferences.Preferences
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class NonRecursiveQueryTest : InjectingTestCase() {
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
private lateinit var adapter: TaskAdapter
private val tasks = ArrayList<TaskContainer>()
private val filter = BuiltInFilterExposer.getMyTasksFilter(ApplicationProvider.getApplicationContext<Context>().resources)
private val dataSource = object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position]
override fun getTaskCount() = tasks.size
}
@Before
override fun setUp() {
super.setUp()
preferences.clear()
preferences.setBoolean(R.string.p_use_paged_queries, true)
tasks.clear()
adapter = TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager)
adapter.setDataSource(dataSource)
}
@Test
fun ignoreSubtasks() {
val parent = addTask()
val child = addTask(with(PARENT, parent))
query()
assertEquals(child, tasks[1].id)
assertEquals(parent, tasks[1].parent)
assertEquals(0, tasks[1].indent)
}
private fun addTask(vararg properties: PropertyValue<in Task?, *>): Long = runBlocking {
val task = newTask(*properties)
taskDao.createNew(task)
task.id
}
private fun query() = runBlocking {
tasks.addAll(taskDao.fetchTasks { getQuery(preferences, filter, it) })
}
}

@ -1,9 +1,12 @@
package com.todoroo.astrid.adapter
import android.content.Context
import androidx.test.core.app.ApplicationProvider
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.service.TaskMover
import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -11,12 +14,10 @@ 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.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskDao
import org.tasks.data.entity.Task
import org.tasks.filters.MyTasksFilter
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskMaker.PARENT
@ -32,11 +33,10 @@ class OfflineSubtaskTest : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var taskMover: TaskMover
private lateinit var adapter: TaskAdapter
private val tasks = ArrayList<TaskContainer>()
private val filter = runBlocking { MyTasksFilter.create() }
private val filter = BuiltInFilterExposer.getMyTasksFilter(ApplicationProvider.getApplicationContext<Context>().resources)
private val dataSource = object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position]
@ -48,7 +48,7 @@ class OfflineSubtaskTest : InjectingTestCase() {
super.setUp()
preferences.clear()
tasks.clear()
adapter = TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
adapter = TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager)
adapter.setDataSource(dataSource)
}
@ -84,6 +84,6 @@ class OfflineSubtaskTest : InjectingTestCase() {
}
private fun query() = runBlocking {
tasks.addAll(taskDao.fetchTasks { getQuery(preferences, filter) })
tasks.addAll(taskDao.fetchTasks { getQuery(preferences, filter, it) })
}
}

@ -2,7 +2,9 @@ package com.todoroo.astrid.adapter
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 dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -11,9 +13,7 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.data.entity.Task
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.filters.TodayFilter
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.TaskMaker.DUE_DATE
@ -76,7 +76,7 @@ class RecursiveLoopTest : InjectingTestCase() {
}
private suspend fun getTasks() = taskDao.fetchTasks {
getQuery(preferences, TodayFilter.create())
getQuery(preferences, BuiltInFilterExposer.getTodayFilter(context.resources), it)
}
private suspend fun addTask(vararg properties: PropertyValue<in Task?, *>): Long {

@ -1,290 +1,52 @@
package com.todoroo.astrid.alarms
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.dao.TaskDao
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.SuspendFreeze.Companion.freezeAt
import org.tasks.data.createDueDate
import org.tasks.data.dao.TaskDao
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.Notification
import org.tasks.data.entity.Task
import org.tasks.data.Alarm
import org.tasks.data.AlarmDao
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.REMINDER_LAST
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils2
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class AlarmJobServiceTest : InjectingTestCase() {
@Inject lateinit var alarmDao: AlarmDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var jobs: NotificationQueue
@Inject lateinit var alarmService: AlarmService
@Test
fun testNoAlarms() = runBlocking {
testResults(emptyList(), 0)
}
@Test
fun futureAlarmWithNoPastAlarm() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 18).millis
)
)
)
alarmService.synchronizeAlarms(1, mutableSetOf(Alarm(type = Alarm.TYPE_REL_END)))
testResults(emptyList(), DateTime(2024, 5, 18, 18, 0).millis)
}
}
@Test
fun pastAlarmWithNoFutureAlarm() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 17).millis
)
)
)
alarmService.synchronizeAlarms(1, mutableSetOf(Alarm(type = Alarm.TYPE_REL_END)))
testResults(
listOf(
Notification(
taskId = 1L,
timestamp = DateTimeUtils2.currentTimeMillis(),
type = Alarm.TYPE_REL_END
)
),
0
)
}
}
@Test
fun pastRecurringAlarmWithFutureRecurrence() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 17).millis
)
)
)
alarmService.synchronizeAlarms(
1,
mutableSetOf(
Alarm(
type = Alarm.TYPE_REL_END,
repeat = 1,
interval = TimeUnit.HOURS.toMillis(6)
)
)
)
testResults(
listOf(
Notification(
taskId = 1L,
timestamp = DateTimeUtils2.currentTimeMillis(),
type = Alarm.TYPE_REL_END
)
),
DateTime(2024, 5, 18, 0, 0).millis
)
}
}
@Test
fun pastAlarmsRemoveSnoozed() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 17).millis
)
)
)
alarmService.synchronizeAlarms(
1,
mutableSetOf(
Alarm(type = Alarm.TYPE_REL_END),
Alarm(time = DateTimeUtils2.currentTimeMillis(), type = Alarm.TYPE_SNOOZE)
)
)
testResults(
listOf(
Notification(
taskId = 1L,
timestamp = DateTimeUtils2.currentTimeMillis(),
type = Alarm.TYPE_REL_END
)
),
0
)
assertEquals(
listOf(Alarm(id = 1, task = 1, time = 0, type = Alarm.TYPE_REL_END)),
alarmService.getAlarms(1)
)
}
}
@Test
fun alarmsOneMinuteApart() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY_TIME,
DateTime(2024, 5, 17, 23, 20).millis
)
)
)
alarmService.synchronizeAlarms(1, mutableSetOf(Alarm(type = Alarm.TYPE_REL_END)))
taskDao.insert(Task())
alarmService.synchronizeAlarms(
taskId = 2,
alarms = mutableSetOf(
Alarm(
type = Alarm.TYPE_SNOOZE,
time = DateTime(2024, 5, 17, 23, 21).millis)
)
)
fun scheduleAlarm() = runBlocking {
val task = newTask()
taskDao.createNew(task)
val alarmTime = DateTime(2017, 9, 24, 19, 57)
val alarm = Alarm(task.id, alarmTime.millis)
alarm.id = alarmDao.insert(alarm)
alarmService.scheduleAllAlarms()
testResults(
listOf(
Notification(
taskId = 1L,
timestamp = DateTimeUtils2.currentTimeMillis(),
type = Alarm.TYPE_REL_END
)
),
DateTime(2024, 5, 17, 23, 21).millis
)
}
}
@Test
fun futureSnoozeOverrideOverdue() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 17).millis
)
)
)
alarmService.synchronizeAlarms(
1,
mutableSetOf(
Alarm(type = Alarm.TYPE_REL_END),
Alarm(
time = DateTimeUtils2.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5),
type = Alarm.TYPE_SNOOZE
)
)
)
testResults(
emptyList(),
DateTimeUtils2.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5)
)
}
assertEquals(listOf(AlarmEntry(alarm)), jobs.getJobs())
}
@Test
fun ignoreStaleAlarm() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 17).millis
),
reminderLast = DateTime(2024, 5, 17, 18, 0).millis,
)
)
alarmService.synchronizeAlarms(
1,
mutableSetOf(Alarm(type = Alarm.TYPE_REL_END))
)
testResults(
emptyList(),
0
)
}
}
@Test
fun dontScheduleForCompletedTask() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 17).millis
),
completionDate = DateTime(2024, 5, 17, 14, 0).millis,
)
)
alarmService.synchronizeAlarms(
1,
mutableSetOf(Alarm(type = Alarm.TYPE_REL_END))
)
testResults(
emptyList(),
0
)
}
}
@Test
fun dontScheduleForDeletedTask() = runBlocking {
freezeAt(DateTime(2024, 5, 17, 23, 20)) {
taskDao.insert(
Task(
dueDate = createDueDate(
Task.URGENCY_SPECIFIC_DAY,
DateTime(2024, 5, 17).millis
),
deletionDate = DateTime(2024, 5, 17, 14, 0).millis,
)
)
alarmService.synchronizeAlarms(
1,
mutableSetOf(Alarm(type = Alarm.TYPE_REL_END))
)
testResults(
emptyList(),
0
)
}
}
val alarmTime = DateTime(2017, 9, 24, 19, 57)
val task = newTask(with(REMINDER_LAST, alarmTime.endOfMinute()))
taskDao.createNew(task)
alarmDao.insert(Alarm(task.id, alarmTime.millis))
alarmService.scheduleAllAlarms()
private suspend fun testResults(notifications: List<Notification>, nextAlarm: Long) {
val actualNextAlarm = alarmService.triggerAlarms {
assertEquals(notifications, it)
it.forEach { taskDao.setLastNotified(it.taskId, DateTimeUtils2.currentTimeMillis()) }
}
assertEquals(nextAlarm, actualNextAlarm)
assertTrue(jobs.getJobs().isEmpty())
}
}

@ -5,7 +5,8 @@
*/
package com.todoroo.astrid.dao
import org.tasks.data.entity.Task
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
@ -14,7 +15,6 @@ 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 = currentTimeMillis() + 10000
task.hideUntil = DateUtilities.now() + 10000
taskDao.createNew(task)
// create task with deadlines
task = Task()
task.title = "deadlineInFuture"
task.dueDate = currentTimeMillis() + 10000
task.dueDate = DateUtilities.now() + 10000
taskDao.createNew(task)
task = Task()
task.title = "deadlineInPast"
task.dueDate = currentTimeMillis() - 10000
task.dueDate = DateUtilities.now() - 10000
taskDao.createNew(task)
// create completed task
task = Task()
task.title = "completed"
task.completionDate = currentTimeMillis() - 10000
task.completionDate = DateUtilities.now() - 10000
taskDao.createNew(task)
// check is active

@ -11,12 +11,14 @@ import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskListDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskListDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GtaskListMaker.ID
import org.tasks.makers.GtaskListMaker.NAME
import org.tasks.makers.GtaskListMaker.REMOTE_ID
import org.tasks.makers.GtaskListMaker.newGtaskList
import org.tasks.makers.RemoteGtaskListMaker
import org.tasks.makers.RemoteGtaskListMaker.newRemoteList
import javax.inject.Inject
@ -27,7 +29,6 @@ class GtasksListServiceTest : InjectingTestCase() {
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var caldavDao: CaldavDao
private lateinit var gtasksListService: GtasksListService
@ -43,14 +44,13 @@ class GtasksListServiceTest : InjectingTestCase() {
newRemoteList(
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.NAME, "Default")))
assertEquals(
CaldavCalendar(id = 1, account = "account", uuid = "1", name = "Default"),
googleTaskListDao.getById(1L)
)
newGtaskList(with(ID, 1L), with(REMOTE_ID, "1"), with(NAME, "Default")),
googleTaskListDao.getById(1L))
}
@Test
fun testGetListByRemoteId() = runBlocking {
val list = CaldavCalendar(uuid = "1")
val list = newGtaskList(with(REMOTE_ID, "1"))
list.id = googleTaskListDao.insertOrReplace(list)
assertEquals(list, googleTaskListDao.getByRemoteId("1"))
}
@ -62,24 +62,22 @@ class GtasksListServiceTest : InjectingTestCase() {
@Test
fun testDeleteMissingList() = runBlocking {
googleTaskListDao.insertOrReplace(CaldavCalendar(id = 1, account = "account", uuid = "1"))
googleTaskListDao.insertOrReplace(newGtaskList(with(ID, 1L), with(REMOTE_ID, "1")))
val taskList = newRemoteList(with(RemoteGtaskListMaker.REMOTE_ID, "2"))
setLists(taskList)
assertEquals(
listOf(CaldavCalendar(id = 2, account = "account", uuid = "2", name = "Default")),
googleTaskListDao.getLists("account")
)
listOf(newGtaskList(with(ID, 2L), with(REMOTE_ID, "2"))),
googleTaskListDao.getLists("account"))
}
@Test
fun testUpdateListName() = runBlocking {
googleTaskListDao.insertOrReplace(
CaldavCalendar(id = 1, uuid = "1", name = "oldName", account = "account")
)
newGtaskList(with(ID, 1L), with(REMOTE_ID, "1"), with(NAME, "oldName")))
setLists(
newRemoteList(
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.NAME, "newName")))
assertEquals("newName", googleTaskListDao.getById(1)!!.name)
assertEquals("newName", googleTaskListDao.getById(1)!!.title)
}
@Test
@ -89,11 +87,8 @@ class GtasksListServiceTest : InjectingTestCase() {
}
private suspend fun setLists(vararg list: TaskList) {
val account = CaldavAccount(
username = "account",
uuid = "account",
)
caldavDao.insert(account)
val account = GoogleTaskAccount("account")
googleTaskListDao.insert(account)
gtasksListService.updateLists(account, listOf(*list))
}
}

@ -0,0 +1,81 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.gtasks
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class GtasksMetadataServiceTest : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var googleTaskDao: GoogleTaskDao
private var task: Task? = null
private var metadata: GoogleTask? = null
@Test
fun testMetadataFound() = runBlocking {
givenTask(taskWithMetadata(null))
whenSearchForMetadata()
thenExpectMetadataFound()
}
@Test
fun testMetadataDoesntExist() = runBlocking {
givenTask(taskWithoutMetadata())
whenSearchForMetadata()
thenExpectNoMetadataFound()
}
private fun thenExpectNoMetadataFound() {
assertNull(metadata)
}
private fun thenExpectMetadataFound() {
assertNotNull(metadata)
}
// --- helpers
private suspend fun whenSearchForMetadata() {
metadata = googleTaskDao.getByTaskId(task!!.id)
}
private suspend fun taskWithMetadata(id: String?): Task {
val task = Task()
task.title = "cats"
taskDao.createNew(task)
val metadata = GoogleTask(task.id, "")
if (id != null) {
metadata.remoteId = id
}
metadata.task = task.id
googleTaskDao.insert(metadata)
return task
}
private fun givenTask(taskToTest: Task) {
task = taskToTest
}
private suspend fun taskWithoutMetadata(): Task {
val task = Task()
task.title = "dogs"
taskDao.createNew(task)
return task
}
}

@ -1,7 +1,7 @@
package com.todoroo.astrid.model
import com.todoroo.astrid.dao.TaskDao
import org.tasks.data.entity.Task
import com.todoroo.astrid.data.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.DateTimeUtils2.currentTimeMillis
import org.tasks.time.DateTimeUtils
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -23,7 +23,7 @@ class TaskTest : InjectingTestCase() {
freezeClock {
val task = Task()
taskDao.createNew(task)
assertEquals(currentTimeMillis(), task.creationDate)
assertEquals(DateTimeUtils.currentTimeMillis(), task.creationDate)
}
}

@ -0,0 +1,327 @@
package com.todoroo.astrid.reminders
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.tasks.Freeze
import org.tasks.R
import org.tasks.data.TaskDao
import org.tasks.date.DateTimeUtils
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.jobs.NotificationQueue
import org.tasks.jobs.ReminderEntry
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.CREATION_TIME
import org.tasks.makers.TaskMaker.DELETION_TIME
import org.tasks.makers.TaskMaker.DUE_DATE
import org.tasks.makers.TaskMaker.DUE_TIME
import org.tasks.makers.TaskMaker.ID
import org.tasks.makers.TaskMaker.RANDOM_REMINDER_PERIOD
import org.tasks.makers.TaskMaker.REMINDERS
import org.tasks.makers.TaskMaker.REMINDER_LAST
import org.tasks.makers.TaskMaker.SNOOZE_TIME
import org.tasks.makers.TaskMaker.newTask
import org.tasks.preferences.Preferences
import org.tasks.reminders.Random
import org.tasks.time.DateTime
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class ReminderServiceTest : InjectingTestCase() {
@Inject lateinit var preferences: Preferences
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var jobs: NotificationQueue
private lateinit var service: ReminderService
private lateinit var random: RandomStub
@Before
override fun setUp() {
super.setUp()
random = RandomStub()
preferences.clear()
service = ReminderService(preferences, jobs, random, taskDao)
}
@Test
fun dontScheduleDueDateReminderWhenFlagNotSet() {
service.scheduleAlarm(newTask(with(ID, 1L), with(DUE_TIME, DateTimeUtils.newDateTime())))
assertTrue(jobs.isEmpty())
}
@Test
fun dontScheduleDueDateReminderWhenTimeNotSet() {
service.scheduleAlarm(newTask(with(ID, 1L), with(REMINDERS, Task.NOTIFY_AT_DEADLINE)))
assertTrue(jobs.isEmpty())
}
@Test
fun schedulePastDueDate() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTimeUtils.newDateTime().minusDays(1)),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1, task.dueDate, ReminderService.TYPE_DUE))
}
@Test
fun scheduleFutureDueDate() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTimeUtils.newDateTime().plusDays(1)),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1, task.dueDate, ReminderService.TYPE_DUE))
}
@Test
fun scheduleReminderAtDefaultDueTime() {
val now = DateTimeUtils.newDateTime()
val task = newTask(with(ID, 1L), with(DUE_DATE, now), with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1, now.startOfDay().withHourOfDay(18).millis, ReminderService.TYPE_DUE))
}
@Test
fun dontScheduleReminderForCompletedTask() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTimeUtils.newDateTime().plusDays(1)),
with(COMPLETION_TIME, DateTimeUtils.newDateTime()),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
assertTrue(jobs.isEmpty())
}
@Test
fun dontScheduleReminderForDeletedTask() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTimeUtils.newDateTime().plusDays(1)),
with(DELETION_TIME, DateTimeUtils.newDateTime()),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
assertTrue(jobs.isEmpty())
}
@Test
fun dontScheduleDueDateReminderWhenAlreadyReminded() {
val now = DateTimeUtils.newDateTime()
val task = newTask(
with(ID, 1L),
with(DUE_TIME, now),
with(REMINDER_LAST, now.plusSeconds(1)),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
assertTrue(jobs.isEmpty())
}
@Test
fun ignoreStaleSnoozeTime() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTimeUtils.newDateTime()),
with(SNOOZE_TIME, DateTimeUtils.newDateTime().minusMinutes(5)),
with(REMINDER_LAST, DateTimeUtils.newDateTime().minusMinutes(4)),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1, task.dueDate, ReminderService.TYPE_DUE))
}
@Test
fun dontIgnoreMissedSnoozeTime() {
val dueDate = DateTimeUtils.newDateTime()
val task = newTask(
with(ID, 1L),
with(DUE_TIME, dueDate),
with(SNOOZE_TIME, dueDate.minusMinutes(4)),
with(REMINDER_LAST, dueDate.minusMinutes(5)),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1, task.reminderSnooze, ReminderService.TYPE_SNOOZE))
}
@Test
fun scheduleInitialRandomReminder() {
random.seed = 0.3865f
Freeze.freezeClock {
val now = DateTimeUtils.newDateTime()
val task = newTask(
with(ID, 1L),
with(REMINDER_LAST, null as DateTime?),
with(CREATION_TIME, now.minusDays(1)),
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_WEEK))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, now.minusDays(1).millis + 584206592, ReminderService.TYPE_RANDOM))
}
}
@Test
fun scheduleNextRandomReminder() {
random.seed = 0.3865f
Freeze.freezeClock {
val now = DateTimeUtils.newDateTime()
val task = newTask(
with(ID, 1L),
with(REMINDER_LAST, now.minusDays(1)),
with(CREATION_TIME, now.minusDays(30)),
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_WEEK))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, now.minusDays(1).millis + 584206592, ReminderService.TYPE_RANDOM))
}
}
@Test
fun scheduleOverdueRandomReminder() {
random.seed = 0.3865f
Freeze.freezeClock {
val now = DateTimeUtils.newDateTime()
val task = newTask(
with(ID, 1L),
with(REMINDER_LAST, now.minusDays(14)),
with(CREATION_TIME, now.minusDays(30)),
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_WEEK))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, now.millis + 10148400, ReminderService.TYPE_RANDOM))
}
}
@Test
fun scheduleOverdueNoLastReminder() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTime(2017, 9, 22, 15, 30)),
with(REMINDER_LAST, null as DateTime?),
with(REMINDERS, Task.NOTIFY_AFTER_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, DateTime(2017, 9, 23, 15, 30, 1, 0).millis, ReminderService.TYPE_OVERDUE))
}
@Test
fun scheduleOverduePastLastReminder() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTime(2017, 9, 22, 15, 30)),
with(REMINDER_LAST, DateTime(2017, 9, 24, 12, 0)),
with(REMINDERS, Task.NOTIFY_AFTER_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, DateTime(2017, 9, 24, 15, 30, 1, 0).millis, ReminderService.TYPE_OVERDUE))
}
@Test
fun scheduleOverdueBeforeLastReminder() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTime(2017, 9, 22, 12, 30)),
with(REMINDER_LAST, DateTime(2017, 9, 24, 15, 0)),
with(REMINDERS, Task.NOTIFY_AFTER_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, DateTime(2017, 9, 25, 12, 30, 1, 0).millis, ReminderService.TYPE_OVERDUE))
}
@Test
fun scheduleOverdueWithNoDueTime() {
preferences.setInt(R.string.p_rmd_time, TimeUnit.HOURS.toMillis(15).toInt())
val task = newTask(
with(ID, 1L),
with(DUE_DATE, DateTime(2017, 9, 22)),
with(REMINDER_LAST, DateTime(2017, 9, 23, 12, 17, 59, 999)),
with(REMINDERS, Task.NOTIFY_AFTER_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, DateTime(2017, 9, 23, 15, 0, 0, 0).millis, ReminderService.TYPE_OVERDUE))
}
@Test
fun scheduleSubsequentOverdueReminder() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTime(2017, 9, 22, 15, 30)),
with(REMINDER_LAST, DateTime(2017, 9, 23, 15, 30, 59, 999)),
with(REMINDERS, Task.NOTIFY_AFTER_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, DateTime(2017, 9, 24, 15, 30, 1, 0).millis, ReminderService.TYPE_OVERDUE))
}
@Test
fun scheduleOverdueAfterLastReminder() {
val task = newTask(
with(ID, 1L),
with(DUE_TIME, DateTime(2017, 9, 22, 15, 30)),
with(REMINDER_LAST, DateTime(2017, 9, 23, 12, 17, 59, 999)),
with(REMINDERS, Task.NOTIFY_AFTER_DEADLINE))
service.scheduleAlarm(task)
verify(ReminderEntry(1L, DateTime(2017, 9, 23, 15, 30, 1, 0).millis, ReminderService.TYPE_OVERDUE))
}
@Test
fun snoozeOverridesAll() {
val now = DateTimeUtils.newDateTime()
val task = newTask(
with(ID, 1L),
with(DUE_TIME, now),
with(SNOOZE_TIME, now.plusMonths(12)),
with(REMINDERS, Task.NOTIFY_AT_DEADLINE or Task.NOTIFY_AFTER_DEADLINE),
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_HOUR))
service.scheduleAlarm(task)
verify(ReminderEntry(1, now.plusMonths(12).millis, ReminderService.TYPE_SNOOZE))
}
private fun verify(vararg reminders: ReminderEntry) = assertEquals(reminders.toList(), jobs.getJobs())
internal class RandomStub : Random() {
var seed = 1.0f
override fun nextFloat() = seed
}
}

@ -1,68 +1,50 @@
package com.todoroo.astrid.repeats
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskCompleter
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.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.data.dao.TaskDao
import org.tasks.data.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.time.DateTimeUtils2.currentTimeMillis
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)
@HiltAndroidTest
class RepeatWithSubtasksTests : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var taskCompleter: TaskCompleter
@Inject lateinit var repeat: RepeatTaskHelper
@Test
fun uncompleteGrandchildren() = runBlocking {
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(),
)
)
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())
))
assertTrue(taskDao.fetch(child)!!.isCompleted)
taskCompleter.setComplete(grandparent)
repeat.handleRepeat(taskDao.fetch(grandparent)!!)
assertFalse(taskDao.fetch(child)!!.isCompleted)
}
@Test
fun uncompleteGoogleTaskChildren() = runBlocking {
val parent = taskDao.createNew(
Task(
recurrence = "RRULE:FREQ=DAILY"
)
)
val child = taskDao.createNew(
Task(
parent = parent,
completionDate = currentTimeMillis(),
)
)
assertTrue(taskDao.fetch(child)!!.isCompleted)
val parent = taskDao.createNew(newTask(with(RECUR, "RRULE:FREQ=DAILY")))
val child = taskDao.createNew(newTask(
with(PARENT, parent),
with(COMPLETION_TIME, DateTime())
))
taskCompleter.setComplete(parent)
repeat.handleRepeat(taskDao.fetch(parent)!!)
assertFalse(taskDao.fetch(child)!!.isCompleted)
}

@ -5,14 +5,14 @@
*/
package com.todoroo.astrid.service
import org.tasks.data.entity.Task
import com.todoroo.astrid.data.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.dao.TagDataDao
import org.tasks.data.TagDataDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import java.util.*

@ -1,12 +1,10 @@
package com.todoroo.astrid.service
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 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 dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -14,7 +12,6 @@ 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
@ -38,7 +35,7 @@ class TaskCreatorTest : InjectingTestCase() {
assertEquals(DateTime(2021, 2, 4).millis, task.hideUntil)
assertEquals(
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
Task.createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
task.dueDate
)
}
@ -66,7 +63,7 @@ class TaskCreatorTest : InjectingTestCase() {
assertEquals(DateTime(2021, 2, 4).millis, task.hideUntil)
assertEquals(
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 4).millis),
Task.createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 4).millis),
task.dueDate
)
}
@ -96,7 +93,7 @@ class TaskCreatorTest : InjectingTestCase() {
}
assertEquals(
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
Task.createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
task.dueDate
)
}

@ -1,15 +1,25 @@
package com.todoroo.astrid.service
import org.tasks.data.entity.Task
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.core.BuiltInFilterExposer.Companion.getMyTasksFilter
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.dao.TaskDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GoogleTaskMaker
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
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)
@ -17,26 +27,111 @@ import javax.inject.Inject
class TaskDeleterTest : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Test
fun markTaskAsDeleted() = runBlocking {
val task = Task()
taskDao.createNew(task)
fun clearCompletedTask() = runBlocking {
val task = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
taskDeleter.markDeleted(task)
clearCompleted()
assertTrue(taskDao.fetch(task.id)!!.isDeleted)
assertTrue(taskDao.fetch(task)!!.isDeleted)
}
@Test
fun dontDeleteReadOnlyTasks() = runBlocking {
val task = Task(
readOnly = true
)
taskDao.createNew(task)
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())
))
taskDeleter.markDeleted(task)
clearCompleted()
assertFalse(taskDao.fetch(task.id)!!.isDeleted)
assertFalse(taskDao.fetch(child)!!.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)
}
@Test
fun dontClearCompletedGoogleTaskWithRecurringParent() = runBlocking {
val parent = taskDao.createNew(newTask(with(RECUR, "RRULE:FREQ=DAILY;INTERVAL=1")))
val child = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
googleTaskDao.insert(newGoogleTask(with(TASK, child), with(GoogleTaskMaker.PARENT, parent)))
clearCompleted()
assertFalse(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearCompletedGoogleTaskWithNonRecurringParent() = runBlocking {
val parent = taskDao.createNew(newTask())
val child = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
googleTaskDao.insert(newGoogleTask(with(TASK, child), with(GoogleTaskMaker.PARENT, parent)))
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearCompletedGoogleTaskWithCompletedRecurringParent() = runBlocking {
val parent = taskDao.createNew(newTask(
with(RECUR, "RRULE:FREQ=DAILY;INTERVAL=1"),
with(COMPLETION_TIME, DateTime())
))
val child = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
googleTaskDao.insert(newGoogleTask(with(TASK, child), with(GoogleTaskMaker.PARENT, parent)))
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
private suspend fun clearCompleted() =
taskDeleter.clearCompleted(getMyTasksFilter(context.resources))
}

@ -1,32 +1,34 @@
package com.todoroo.astrid.service
import com.natpryce.makeiteasy.MakeItEasy.with
import org.tasks.filters.CaldavFilter
import org.tasks.filters.GtasksFilter
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.dao.TaskDao
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.Before
import org.junit.Test
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.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.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.jobs.WorkManager
import org.tasks.makers.CaldavTaskMaker
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.GoogleTaskMaker.LIST
import org.tasks.makers.GoogleTaskMaker.PARENT
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.GtaskListMaker
import org.tasks.makers.GtaskListMaker.newGtaskList
import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.ID
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.newTask
import javax.inject.Inject
@ -40,28 +42,18 @@ class TaskMoverTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var taskMover: TaskMover
@Before
fun setup() {
runBlocking {
caldavDao.insert(CaldavCalendar(uuid = "1", account = "account1"))
caldavDao.insert(CaldavCalendar(uuid = "2", account = "account2"))
}
}
@Test
fun moveBetweenGoogleTaskLists() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
moveToGoogleTasks("2", 1)
assertEquals("2", googleTaskDao.getByTaskId(1)?.calendar)
assertEquals("2", googleTaskDao.getByTaskId(1)!!.listId)
}
@Test
fun deleteGoogleTaskAfterMove() = runBlocking {
createTasks(1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
moveToGoogleTasks("2", 1)
val deleted = googleTaskDao.getDeletedByTaskId(1)
assertEquals(1, deleted.size.toLong())
@ -71,25 +63,23 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveChildrenBetweenGoogleTaskLists() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
moveToGoogleTasks("2", 1)
val deleted = googleTaskDao.getDeletedByTaskId(2)
assertEquals(1, deleted.size.toLong())
assertEquals(2, deleted[0].task)
assertTrue(deleted[0].deleted > 0)
assertEquals(1L, taskDao.fetch(2)?.parent)
assertEquals("2", googleTaskDao.getByTaskId(2)?.calendar)
val task = googleTaskDao.getByTaskId(2)!!
assertEquals(1, task.parent)
assertEquals("2", task.listId)
}
@Test
fun moveBetweenCaldavList() = runBlocking {
createTasks(1)
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1)
assertEquals("2", caldavDao.getTask(1)!!.calendar)
}
@ -97,7 +87,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun deleteCaldavTaskAfterMove() = runBlocking {
createTasks(1)
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1)
val deleted = caldavDao.getMoved("1")
assertEquals(1, deleted.size.toLong())
@ -113,14 +103,14 @@ class TaskMoverTest : InjectingTestCase() {
caldavDao.insert(
listOf(
newCaldavTask(
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(TASK, 2L),
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(TASK, 3L),
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "b"))))
moveToCaldavList("2", 1)
@ -133,16 +123,13 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveGoogleTaskChildrenToCaldav() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
setAccountType("account2", TYPE_CALDAV)
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
moveToCaldavList("1", 1)
val task = caldavDao.getTask(2)
assertEquals("1", task!!.calendar)
assertEquals(1L, taskDao.fetch(2)?.parent)
assertEquals(1, taskDao.fetch(2)!!.parent)
}
@Test
@ -151,7 +138,8 @@ class TaskMoverTest : InjectingTestCase() {
createSubtask(2, 1)
createSubtask(3, 2)
moveToGoogleTasks("1", 1)
assertEquals(1L, taskDao.fetch(3)?.parent)
assertEquals(1, googleTaskDao.getByTaskId(3)!!.parent)
assertEquals(0, taskDao.fetch(3)!!.parent)
}
@Test
@ -159,7 +147,7 @@ class TaskMoverTest : InjectingTestCase() {
createTasks(1)
createSubtask(2, 1)
moveToGoogleTasks("1", 2)
assertEquals(0L, taskDao.fetch(2)?.parent)
assertEquals(0, taskDao.fetch(2)!!.parent)
}
@Test
@ -178,31 +166,30 @@ class TaskMoverTest : InjectingTestCase() {
caldavDao.insert(
listOf(
newCaldavTask(
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(TASK, 2L),
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(TASK, 3L),
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "b"))))
moveToGoogleTasks("1", 1)
val task = taskDao.fetch(3L)
assertEquals(1L, task?.parent)
val task = googleTaskDao.getByTaskId(3L)!!
assertEquals(1, task.parent)
}
@Test
fun moveGoogleTaskChildWithoutParent() = runBlocking {
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
moveToGoogleTasks("2", 2)
assertEquals(0L, taskDao.fetch(2)?.parent)
assertEquals("2", googleTaskDao.getByTaskId(2)?.calendar)
val task = googleTaskDao.getByTaskId(2)!!
assertEquals(0L, task.parent)
assertEquals("2", task.listId)
}
@Test
@ -212,9 +199,9 @@ class TaskMoverTest : InjectingTestCase() {
caldavDao.insert(
listOf(
newCaldavTask(
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(TASK, 2L),
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "a"))))
moveToCaldavList("2", 2)
@ -225,19 +212,17 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveGoogleTaskToCaldav() = runBlocking {
createTasks(1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
moveToCaldavList("2", 1)
assertEquals("2", caldavDao.getTask(1)!!.calendar)
}
@Test
fun moveCaldavToGoogleTask() = runBlocking {
setAccountType("account1", TYPE_CALDAV)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1)
assertEquals("2", googleTaskDao.getByTaskId(1L)?.calendar)
assertEquals("2", googleTaskDao.getByTaskId(1L)!!.listId)
}
@Test
@ -246,15 +231,14 @@ class TaskMoverTest : InjectingTestCase() {
createSubtask(2, 1)
createSubtask(3, 2)
moveToCaldavList("1", 1)
assertEquals("1", caldavDao.getTask(3)?.calendar)
assertEquals(2L, taskDao.fetch(3)?.parent)
assertEquals("1", caldavDao.getTask(3)!!.calendar)
assertEquals(2, taskDao.fetch(3)!!.parent)
}
@Test
fun moveToSameGoogleTaskListIsNoop() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
createTasks(1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
moveToGoogleTasks("1", 1)
assertTrue(googleTaskDao.getDeletedByTaskId(1).isEmpty())
assertEquals(1, googleTaskDao.getAllByTaskId(1).size.toLong())
@ -263,7 +247,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveToSameCaldavListIsNoop() = runBlocking {
createTasks(1)
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("1", 1)
assertTrue(caldavDao.getMoved("1").isEmpty())
assertEquals(1, caldavDao.getTasks(1).size.toLong())
@ -271,10 +255,9 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun dontDuplicateWhenParentAndChildGoogleTaskMoved() = runBlocking {
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
moveToGoogleTasks("2", 1, 2)
assertEquals(1, googleTaskDao.getAllByTaskId(2).filter { it.deleted == 0L }.size)
}
@ -286,9 +269,9 @@ class TaskMoverTest : InjectingTestCase() {
caldavDao.insert(
listOf(
newCaldavTask(
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(TASK, 2L),
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "a"))))
moveToCaldavList("2", 1, 2)
@ -302,23 +285,14 @@ class TaskMoverTest : InjectingTestCase() {
}
private suspend fun createSubtask(id: Long, parent: Long) {
taskDao.createNew(newTask(with(ID, id), with(PARENT, parent)))
taskDao.createNew(newTask(with(ID, id), with(TaskMaker.PARENT, parent)))
}
private suspend fun moveToGoogleTasks(list: String, vararg tasks: Long) {
taskMover.move(tasks.toList(), GtasksFilter(CaldavCalendar(uuid = list)))
taskMover.move(tasks.toList(), GtasksFilter(newGtaskList(with(GtaskListMaker.REMOTE_ID, list))))
}
private suspend fun moveToCaldavList(calendar: String, vararg tasks: Long) {
taskMover.move(tasks.toList(), CaldavFilter(CaldavCalendar(name = "", uuid = calendar)))
}
private suspend fun setAccountType(account: String, type: Int) {
caldavDao.insert(
CaldavAccount(
uuid = account,
accountType = type,
)
)
taskMover.move(tasks.toList(), CaldavFilter(CaldavCalendar("", calendar)))
}
}

@ -5,7 +5,7 @@
*/
package com.todoroo.astrid.service
import org.tasks.data.entity.Task
import com.todoroo.astrid.data.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.dao.TagDataDao
import org.tasks.data.TagDataDao
import org.tasks.date.DateTimeUtils
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule

@ -1,62 +0,0 @@
@file:Suppress("ClassName")
package com.todoroo.astrid.service
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.todoroo.astrid.service.Upgrade_11_12_3.Companion.LEGACY_PREFERENCE
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.tasks.TestUtilities.newPreferences
import org.tasks.preferences.Preferences
@RunWith(AndroidJUnit4::class)
class Upgrade_11_12_3_Test {
private lateinit var preferences: Preferences
private lateinit var upgrader: Upgrade_11_12_3
@Test
fun migrateNoDefaultReminders() {
preferences.setString(LEGACY_PREFERENCE, "0")
upgrader.migrateDefaultReminderPreference()
assertEquals(emptySet<String>(), preferences.defaultRemindersSet)
assertEquals(0, preferences.defaultReminders)
}
@Test
fun migrateWhenDue() {
preferences.setString(LEGACY_PREFERENCE, "2")
upgrader.migrateDefaultReminderPreference()
assertEquals(setOf("2"), preferences.defaultRemindersSet)
assertEquals(2, preferences.defaultReminders)
}
@Test
fun migrateWhenOverdue() {
preferences.setString(LEGACY_PREFERENCE, "4")
upgrader.migrateDefaultReminderPreference()
assertEquals(setOf("4"), preferences.defaultRemindersSet)
assertEquals(4, preferences.defaultReminders)
}
@Test
fun migrateWhenDueAndOverdue() {
preferences.setString(LEGACY_PREFERENCE, "6")
upgrader.migrateDefaultReminderPreference()
assertEquals(setOf("2", "4"), preferences.defaultRemindersSet)
assertEquals(6, preferences.defaultReminders)
}
@Before
fun setUp() {
preferences = newPreferences(ApplicationProvider.getApplicationContext())
preferences.clear()
upgrader = Upgrade_11_12_3(preferences)
}
}

@ -3,23 +3,21 @@
package com.todoroo.astrid.service
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.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.SuspendFreeze.Companion.freezeAt
import org.tasks.TestUtilities.assertEquals
import org.tasks.caldav.VtodoCache
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.TaskDao
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Task
import org.tasks.data.CaldavDao
import org.tasks.data.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.VTODO
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker.DUE_DATE
import org.tasks.makers.TaskMaker.HIDE_TYPE
@ -36,26 +34,11 @@ class Upgrade_11_3_Test : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var openTaskDao: TestOpenTaskDao
@Inject lateinit var upgrader: Upgrade_11_3
@Inject lateinit var vtodoCache: VtodoCache
private lateinit var calendar: CaldavCalendar
@Before
override fun setUp() {
super.setUp()
calendar = CaldavCalendar()
runBlocking {
caldavDao.insert(calendar)
}
}
@Test
fun applyRemoteiCalendarStartDate() = runBlocking {
val taskId = taskDao.insert(newTask())
val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_WITH_START_DATE)
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE)))
upgrader.applyiCalendarStartDates()
assertEquals(DateTime(2021, 1, 21), taskDao.fetch(taskId)?.hideUntil)
@ -67,10 +50,7 @@ class Upgrade_11_3_Test : InjectingTestCase() {
with(DUE_DATE, DateTime(2021, 1, 20)),
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE)
))
val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_WITH_START_DATE)
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE)))
upgrader.applyiCalendarStartDates()
assertEquals(DateTime(2021, 1, 20), taskDao.fetch(taskId)?.hideUntil)
@ -84,9 +64,7 @@ class Upgrade_11_3_Test : InjectingTestCase() {
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE),
with(MODIFICATION_TIME, DateTime(2021, 1, 21, 9, 50, 4, 348))
))
val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_WITH_START_DATE)
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE)))
freezeAt(upgradeTime) {
upgrader.applyiCalendarStartDates()
@ -99,9 +77,7 @@ class Upgrade_11_3_Test : InjectingTestCase() {
fun dontTouchWhenNoiCalendarStartDate() = runBlocking {
val modificationTime = DateTime(2021, 1, 21, 9, 50, 4, 348)
val taskId = taskDao.insert(newTask(with(MODIFICATION_TIME, modificationTime)))
val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_NO_START_DATE)
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_NO_START_DATE)))
upgrader.applyiCalendarStartDates()

@ -1,13 +1,13 @@
package com.todoroo.astrid.subtasks
import org.tasks.data.entity.Task
import com.todoroo.astrid.data.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.entity.TaskListMetadata
import org.tasks.data.TaskListMetadata
import org.tasks.injection.ProductionModule
@UninstallModules(ProductionModule::class)

@ -1,12 +1,12 @@
package com.todoroo.astrid.subtasks
import org.tasks.data.entity.Task
import com.todoroo.astrid.data.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.entity.TaskListMetadata
import org.tasks.data.TaskListMetadata
import org.tasks.injection.ProductionModule
@UninstallModules(ProductionModule::class)

@ -1,27 +1,27 @@
package com.todoroo.astrid.subtasks
import androidx.test.InstrumentationRegistry
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.core.BuiltInFilterExposer
import com.todoroo.astrid.dao.TaskDao
import kotlinx.coroutines.runBlocking
import com.todoroo.astrid.data.Task
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.tasks.data.dao.TaskListMetadataDao
import org.tasks.data.entity.Task
import org.tasks.filters.AstridOrderingFilter
import org.tasks.filters.MyTasksFilter
import org.tasks.data.TaskListMetadataDao
import org.tasks.injection.InjectingTestCase
import org.tasks.preferences.Preferences
import javax.inject.Inject
abstract class SubtasksTestCase : InjectingTestCase() {
lateinit var updater: SubtasksFilterUpdater
lateinit var filter: AstridOrderingFilter
lateinit var filter: Filter
@Inject lateinit var taskListMetadataDao: TaskListMetadataDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences
override fun setUp() {
super.setUp()
filter = runBlocking { MyTasksFilter.create() }
filter = BuiltInFilterExposer.getMyTasksFilter(InstrumentationRegistry.getTargetContext().resources)
preferences.clear(SubtasksFilterUpdater.ACTIVE_TASKS_ORDER)
updater = SubtasksFilterUpdater(taskListMetadataDao, taskDao)
}

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

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

@ -1 +0,0 @@
../../../../test/java/org/tasks/Freeze.kt

@ -1 +0,0 @@
../../../../test/java/org/tasks/SuspendFreeze.kt

@ -1 +0,0 @@
../../../../test/java/org/tasks/TestUtilities.kt

@ -1,22 +1,20 @@
package org.tasks.caldav
import com.natpryce.makeiteasy.MakeItEasy.with
import org.tasks.data.UUIDHelper
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.assertEquals
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.ETAG
import org.tasks.makers.CaldavTaskMaker.OBJECT
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker.newTask
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
@ -25,13 +23,12 @@ class CaldavSynchronizerTest : CaldavTest() {
@Before
override fun setUp() = runBlocking {
super.setUp()
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))
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)
}
}
@ -46,13 +43,11 @@ 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().apply {
account = this@CaldavSynchronizerTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@CaldavSynchronizerTest.account.url}test-shared/"
})
enqueue(OC_SHARE_PROPFIND)
sync()
@ -60,17 +55,16 @@ class CaldavSynchronizerTest : CaldavTest() {
@Test
fun dontFetchTaskIfEtagMatches() = runBlocking {
val calendar = CaldavCalendar(
account = this@CaldavSynchronizerTest.account.uuid,
uuid = UUIDHelper.newUUID(),
url = "${this@CaldavSynchronizerTest.account.url}test-shared/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@CaldavSynchronizerTest.account.uuid
uuid = UUIDHelper.newUUID()
url = "${this@CaldavSynchronizerTest.account.url}test-shared/"
caldavDao.insert(this)
}
caldavDao.insert(newCaldavTask(
with(TASK, taskDao.insert(newTask())),
with(OBJECT, "3164728546640386952.ics"),
with(ETAG, "43b3ffaac5131880e4dd07a79adba82a"),
with(CALENDAR, calendar.uuid)
with(OBJECT, "3164728546640386952.ics"),
with(ETAG, "43b3ffaac5131880e4dd07a79adba82a"),
with(CALENDAR, calendar.uuid)
))
enqueue(OC_SHARE_PROPFIND, OC_SHARE_REPORT)

@ -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.entity.CaldavAccount
import org.tasks.data.dao.CaldavDao
import org.tasks.data.CaldavAccount
import org.tasks.data.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 org.tasks.data.UUIDHelper
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.assertEquals
import org.junit.Test
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.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.injection.ProductionModule
@UninstallModules(ProductionModule::class)
@ -79,14 +79,13 @@ class ServerDetectionTest : CaldavTest() {
vararg headers: Pair<String, String>,
accountType: Int = TYPE_CALDAV
) {
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))
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
}
this.headers.putAll(headers)
enqueue(NO_CALENDARS)

@ -1,16 +1,16 @@
package org.tasks.caldav
import org.tasks.data.UUIDHelper
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.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
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.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_WRITE
import org.tasks.data.PrincipalDao
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@ -22,20 +22,19 @@ class SharingMailboxDotOrgTest : CaldavTest() {
@Test
fun ownerAccess() = runBlocking {
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = "3",
password = encryption.encrypt("password"),
url = server.url("/caldav/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = "3"
password = encryption.encrypt("password")
url = server.url("/caldav/").toString()
id = caldavDao.insert(this)
}
val calendar = CaldavCalendar().apply {
account = this@SharingMailboxDotOrgTest.account.uuid
ctag = "1614876450015"
url = "${this@SharingMailboxDotOrgTest.account.url}MzM/"
caldavDao.insert(this)
}
val calendar = CaldavCalendar(
account = this@SharingMailboxDotOrgTest.account.uuid,
ctag = "1614876450015",
url = "${this@SharingMailboxDotOrgTest.account.url}MzM/",
)
caldavDao.insert(calendar)
enqueue(SHARE_OWNER)
sync()
@ -46,20 +45,19 @@ class SharingMailboxDotOrgTest : CaldavTest() {
@Test
fun principalForSharee() = runBlocking {
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = "3",
password = encryption.encrypt("password"),
url = server.url("/caldav/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = "3"
password = encryption.encrypt("password")
url = server.url("/caldav/").toString()
id = caldavDao.insert(this)
}
val calendar = CaldavCalendar().apply {
account = this@SharingMailboxDotOrgTest.account.uuid
ctag = "1614876450015"
url = "${this@SharingMailboxDotOrgTest.account.url}MzM/"
caldavDao.insert(this)
}
val calendar = CaldavCalendar(
account = this@SharingMailboxDotOrgTest.account.uuid,
ctag = "1614876450015",
url = "${this@SharingMailboxDotOrgTest.account.url}MzM/",
)
caldavDao.insert(calendar)
enqueue(SHARE_OWNER)
sync()

@ -1,16 +1,16 @@
package org.tasks.caldav
import org.tasks.data.UUIDHelper
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.Test
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.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.injection.ProductionModule
import javax.inject.Inject
@ -21,25 +21,24 @@ class SharingOwncloudTest : CaldavTest() {
@Inject lateinit var principalDao: PrincipalDao
private suspend fun setupAccount(user: String) {
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))
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)
}
}
@Test
fun calendarOwner() = runBlocking {
setupAccount("user1")
val calendar = CaldavCalendar(
account = this@SharingOwncloudTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@SharingOwncloudTest.account.url}test-shared/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingOwncloudTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@SharingOwncloudTest.account.url}test-shared/"
caldavDao.insert(this)
}
enqueue(OC_OWNER)
sync()
@ -50,12 +49,12 @@ class SharingOwncloudTest : CaldavTest() {
@Test
fun readOnly() = runBlocking {
setupAccount("user2")
val calendar = CaldavCalendar(
account = this@SharingOwncloudTest.account.uuid,
ctag = "http://sabre.io/ns/sync/2",
url = "${this@SharingOwncloudTest.account.url}test-shared_shared_by_user1/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingOwncloudTest.account.uuid
ctag = "http://sabre.io/ns/sync/2"
url = "${this@SharingOwncloudTest.account.url}test-shared_shared_by_user1/"
caldavDao.insert(this)
}
enqueue(OC_READ_ONLY)
sync()
@ -66,12 +65,12 @@ class SharingOwncloudTest : CaldavTest() {
@Test
fun principalForSharee() = runBlocking {
setupAccount("user1")
val calendar = CaldavCalendar(
account = this@SharingOwncloudTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@SharingOwncloudTest.account.url}test-shared/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingOwncloudTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@SharingOwncloudTest.account.url}test-shared/"
caldavDao.insert(this)
}
enqueue(OC_OWNER)
sync()
@ -90,12 +89,12 @@ class SharingOwncloudTest : CaldavTest() {
@Test
fun principalForOwner() = runBlocking {
setupAccount("user2")
val calendar = CaldavCalendar(
account = this@SharingOwncloudTest.account.uuid,
ctag = "http://sabre.io/ns/sync/2",
url = "${this@SharingOwncloudTest.account.url}test-shared_shared_by_user1/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingOwncloudTest.account.uuid
ctag = "http://sabre.io/ns/sync/2"
url = "${this@SharingOwncloudTest.account.url}test-shared_shared_by_user1/"
caldavDao.insert(this)
}
enqueue(OC_READ_ONLY)
sync()

@ -1,18 +1,18 @@
package org.tasks.caldav
import org.tasks.data.UUIDHelper
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.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
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.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.injection.ProductionModule
import javax.inject.Inject
@ -23,25 +23,24 @@ class SharingSabredavTest : CaldavTest() {
@Inject lateinit var principalDao: PrincipalDao
private suspend fun setupAccount(user: String) {
account = CaldavAccount(
uuid = UUIDHelper.newUUID(),
username = user,
password = encryption.encrypt("password"),
url = server.url("/calendars/$user/").toString(),
).let {
it.copy(id = caldavDao.insert(it))
account = CaldavAccount().apply {
uuid = UUIDHelper.newUUID()
username = user
password = encryption.encrypt("password")
url = server.url("/calendars/$user/").toString()
id = caldavDao.insert(this)
}
}
@Test
fun calendarOwner() = runBlocking {
setupAccount("user1")
val calendar = CaldavCalendar(
account = this@SharingSabredavTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@SharingSabredavTest.account.url}940468858232147861/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingSabredavTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@SharingSabredavTest.account.url}940468858232147861/"
caldavDao.insert(this)
}
enqueue(SD_OWNER)
sync()
@ -55,12 +54,12 @@ class SharingSabredavTest : CaldavTest() {
@Test
fun calendarSharee() = runBlocking {
setupAccount("user2")
val calendar = CaldavCalendar(
account = this@SharingSabredavTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@SharingSabredavTest.account.url}c3853d69-cb7a-476c-a23b-30ffd70f110b/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingSabredavTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@SharingSabredavTest.account.url}c3853d69-cb7a-476c-a23b-30ffd70f110b/"
caldavDao.insert(this)
}
enqueue(SD_SHAREE)
sync()
@ -74,13 +73,12 @@ class SharingSabredavTest : CaldavTest() {
@Test
fun excludeCurrentUserPrincipalFromSharees() = runBlocking {
setupAccount("user1")
caldavDao.insert(
CaldavCalendar(
account = account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${account.url}940468858232147861/",
)
)
CaldavCalendar().apply {
account = this@SharingSabredavTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@SharingSabredavTest.account.url}940468858232147861/"
caldavDao.insert(this)
}
enqueue(SD_OWNER)
sync()
@ -91,12 +89,12 @@ class SharingSabredavTest : CaldavTest() {
@Test
fun principalForSharee() = runBlocking {
setupAccount("user1")
val calendar = CaldavCalendar(
account = this@SharingSabredavTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@SharingSabredavTest.account.url}940468858232147861/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingSabredavTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@SharingSabredavTest.account.url}940468858232147861/"
caldavDao.insert(this)
}
enqueue(SD_OWNER)
sync()
@ -113,12 +111,12 @@ class SharingSabredavTest : CaldavTest() {
@Test
fun principalForOwner() = runBlocking {
setupAccount("user2")
val calendar = CaldavCalendar(
account = this@SharingSabredavTest.account.uuid,
ctag = "http://sabre.io/ns/sync/1",
url = "${this@SharingSabredavTest.account.url}c3853d69-cb7a-476c-a23b-30ffd70f110b/",
)
caldavDao.insert(calendar)
val calendar = CaldavCalendar().apply {
account = this@SharingSabredavTest.account.uuid
ctag = "http://sabre.io/ns/sync/1"
url = "${this@SharingSabredavTest.account.url}c3853d69-cb7a-476c-a23b-30ffd70f110b/"
caldavDao.insert(this)
}
enqueue(SD_SHAREE)
sync()

@ -2,6 +2,7 @@ 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
@ -9,15 +10,11 @@ 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)
@ -104,24 +101,22 @@ 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 =
currentTimeMillis()
}!!)
caldavDao.update(caldavDao.getTask(tasks[0].id).apply { this?.deleted = now() }!!)
caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())
assertNull(taskDao.fetch(tasks[0].id)!!.order)
assertNull(caldavDao.getTasks(tasks[0].id)[0].order)
}
@Test
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 = currentTimeMillis() }!!)
taskDao.update(taskDao.fetch(tasks[0].id).apply { this?.deletionDate = now() }!!)
caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())
assertNull(taskDao.fetch(tasks[0].id)!!.order)
assertNull(caldavDao.getTasks(tasks[0].id)[0].order)
}
@Test
@ -139,11 +134,10 @@ class CaldavDaoShiftTests : InjectingTestCase() {
}
private suspend fun checkOrder(dateTime: DateTime?, task: TaskContainer) {
val order = taskDao.fetch(task.id)!!.order
if (dateTime == null) {
assertNull(order)
assertNull(caldavDao.getTask(task.id)!!.order)
} else {
assertEquals(dateTime.toAppleEpoch(), order)
assertEquals(dateTime.toAppleEpoch(), caldavDao.getTask(task.id)!!.order)
}
}
@ -151,18 +145,22 @@ class CaldavDaoShiftTests : InjectingTestCase() {
private suspend fun addTask(calendar: String, vararg properties: PropertyValue<in TaskContainer?, *>) {
val t = TaskContainerMaker.newTaskContainer(*properties)
tasks.add(t)
val task = t.task
taskDao.createNew(task)
val caldavTask = CaldavTask(task = t.id, calendar = calendar)
val caldavTask = CaldavTask(t.id, calendar)
if (task.parent > 0) {
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.parent)
}
tasks.add(
t.copy(
caldavTask = caldavTask.copy(
id = caldavDao.insert(caldavTask)
)
)
)
caldavTask.id = caldavDao.insert(caldavTask)
t.caldavTask = caldavTask.toSubset()
}
}
private fun CaldavTask.toSubset(): SubsetCaldav {
val result = SubsetCaldav()
result.cd_id = id
result.cd_calendar = calendar
result.cd_remote_parent = remoteParent
return result
}
}

@ -2,18 +2,12 @@ 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.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.*
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
@ -33,7 +27,8 @@ class CaldavDaoTests : InjectingTestCase() {
fun insertNewTaskAtTopOfEmptyList() = runBlocking {
val task = newTask()
taskDao.createNew(task)
caldavDao.insert(task, CaldavTask(task = task.id, calendar = "calendar"), true)
val caldavTask = CaldavTask(task.id, "calendar")
caldavDao.insert(task, caldavTask, true)
checkOrder(null, task.id)
}
@ -45,8 +40,9 @@ class CaldavDaoTests : InjectingTestCase() {
val second = newTask(with(CREATION_TIME, created.plusSeconds(1)))
taskDao.createNew(first)
taskDao.createNew(second)
caldavDao.insert(first, CaldavTask(task = first.id, calendar = "calendar"), true)
caldavDao.insert(second, CaldavTask(task = second.id, calendar = "calendar"), true)
caldavDao.insert(first, CaldavTask(first.id, "calendar"), true)
caldavDao.insert(second, CaldavTask(second.id, "calendar"), true)
checkOrder(null, first.id)
checkOrder(created.minusSeconds(1), second.id)
@ -59,8 +55,9 @@ class CaldavDaoTests : InjectingTestCase() {
val second = newTask(with(CREATION_TIME, created.plusSeconds(1)))
taskDao.createNew(first)
taskDao.createNew(second)
caldavDao.insert(first, CaldavTask(task = first.id, calendar = "calendar"), false)
caldavDao.insert(second, CaldavTask(task = second.id, calendar = "calendar"), false)
caldavDao.insert(first, CaldavTask(first.id, "calendar"), false)
caldavDao.insert(second, CaldavTask(second.id, "calendar"), false)
checkOrder(null, first.id)
checkOrder(null, second.id)
@ -73,8 +70,9 @@ class CaldavDaoTests : InjectingTestCase() {
val second = newTask(with(CREATION_TIME, created))
taskDao.createNew(first)
taskDao.createNew(second)
caldavDao.insert(first, CaldavTask(task = first.id, calendar = "calendar"), false)
caldavDao.insert(second, CaldavTask(task = second.id, calendar = "calendar"), false)
caldavDao.insert(first, CaldavTask(first.id, "calendar"), false)
caldavDao.insert(second, CaldavTask(second.id, "calendar"), false)
checkOrder(null, first.id)
checkOrder(created.plusSeconds(1), second.id)
@ -84,14 +82,16 @@ class CaldavDaoTests : InjectingTestCase() {
fun insertNewTaskAtBottomOfEmptyList() = runBlocking {
val task = newTask()
taskDao.createNew(task)
caldavDao.insert(task, CaldavTask(task = task.id, calendar = "calendar"), false)
val caldavTask = CaldavTask(task.id, "calendar")
caldavDao.insert(task, caldavTask, false)
checkOrder(null, task.id)
}
@Test
fun noResultsForEmptyAccounts() = runBlocking {
val caldavAccount = CaldavAccount(uuid = UUIDHelper.newUUID())
val caldavAccount = CaldavAccount()
caldavAccount.uuid = UUIDHelper.newUUID()
caldavDao.insert(caldavAccount)
assertTrue(caldavDao.getCaldavFilters(caldavAccount.uuid!!).isEmpty())
}
@ -99,11 +99,11 @@ class CaldavDaoTests : InjectingTestCase() {
private suspend fun checkOrder(dateTime: DateTime, task: Long) = checkOrder(dateTime.toAppleEpoch(), task)
private suspend fun checkOrder(order: Long?, task: Long) {
val sortOrder = taskDao.fetch(task)!!.order
val sortOrder = caldavDao.getTask(task)!!.order
if (order == null) {
assertNull(sortOrder)
} else {
assertEquals(order, sortOrder)
}
}
}
}

@ -2,18 +2,13 @@ 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.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.*
import org.junit.Test
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.data.CaldavDao.Companion.LOCAL
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
@ -21,7 +16,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.DateTimeUtils2.currentTimeMillis
import org.tasks.time.DateTimeUtils
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -48,7 +43,7 @@ class DeletionDaoTests : InjectingTestCase() {
deletionDao.markDeleted(listOf(task.id))
task = taskDao.fetch(task.id)!!
assertTrue(task.modificationDate > task.creationDate)
assertTrue(task.modificationDate < currentTimeMillis())
assertTrue(task.modificationDate < DateTimeUtils.currentTimeMillis())
}
@Test
@ -58,15 +53,15 @@ class DeletionDaoTests : InjectingTestCase() {
deletionDao.markDeleted(listOf(task.id))
task = taskDao.fetch(task.id)!!
assertTrue(task.deletionDate > task.creationDate)
assertTrue(task.deletionDate < currentTimeMillis())
assertTrue(task.deletionDate < DateTimeUtils.currentTimeMillis())
}
@Test
fun purgeDeletedLocalTask() = runBlocking {
val task = newTask(with(DELETION_TIME, newDateTime()))
taskDao.createNew(task)
caldavDao.insert(CaldavCalendar(name = "", uuid = "1234", account = LOCAL))
caldavDao.insert(CaldavTask(task = task.id, calendar = "1234"))
caldavDao.insert(CaldavCalendar("", "1234").apply { account = LOCAL })
caldavDao.insert(CaldavTask(task.id, "1234"))
deletionDao.purgeDeleted()
@ -77,8 +72,8 @@ class DeletionDaoTests : InjectingTestCase() {
fun dontPurgeActiveTasks() = runBlocking {
val task = newTask()
taskDao.createNew(task)
caldavDao.insert(CaldavCalendar(name = "", uuid = "1234", account = LOCAL))
caldavDao.insert(CaldavTask(task = task.id, calendar = "1234"))
caldavDao.insert(CaldavCalendar("", "1234").apply { account = LOCAL })
caldavDao.insert(CaldavTask(task.id, "1234"))
deletionDao.purgeDeleted()
@ -89,11 +84,11 @@ class DeletionDaoTests : InjectingTestCase() {
fun dontPurgeDeletedCaldavTask() = runBlocking {
val task = newTask(with(DELETION_TIME, newDateTime()))
taskDao.createNew(task)
caldavDao.insert(CaldavCalendar(name = "", uuid = "1234", account = UUIDHelper.newUUID()))
caldavDao.insert(CaldavTask(task = task.id, calendar = "1234"))
caldavDao.insert(CaldavCalendar("", "1234").apply { account = UUIDHelper.newUUID() })
caldavDao.insert(CaldavTask(task.id, "1234"))
deletionDao.purgeDeleted()
assertNotNull(taskDao.fetch(task.id))
}
}
}

@ -9,20 +9,15 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
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.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.GoogleTaskMaker.LIST
import org.tasks.makers.GoogleTaskMaker.PARENT
import org.tasks.makers.GoogleTaskMaker.REMOTE_ID
import org.tasks.makers.GoogleTaskMaker.REMOTE_PARENT
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.GtaskListMaker.newGtaskList
import org.tasks.makers.TaskMaker.newTask
import javax.inject.Inject
@ -32,135 +27,140 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var caldavDao: CaldavDao
@Before
override fun setUp() {
super.setUp()
runBlocking {
caldavDao.insert(CaldavAccount(uuid = "account", accountType = TYPE_GOOGLE_TASKS))
caldavDao.insert(CaldavCalendar(account = "account", uuid = "calendar"))
googleTaskListDao.insert(newGtaskList())
}
}
@Test
fun insertAtTopOfEmptyList() = runBlocking {
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("1")
assertEquals(1, tasks.size.toLong())
val task = tasks[0]
assertEquals("1234", googleTaskDao.getByTaskId(task.id)?.remoteId)
assertEquals(0L, task.order)
assertEquals("1234", task.remoteId)
assertEquals(0, task.order)
}
@Test
fun insertAtBottomOfEmptyList() = runBlocking {
insertBottom(newCaldavTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
insertBottom(newGoogleTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("1")
assertEquals(1, tasks.size.toLong())
val task = tasks[0]
assertEquals("1234", googleTaskDao.getByTaskId(task.id)?.remoteId)
assertEquals(0L, task.order)
assertEquals("1234", task.remoteId)
assertEquals(0, task.order)
}
@Test
fun getPreviousIsNullForTopTask() = runBlocking {
insert(newCaldavTask())
googleTaskDao.insertAndShift(newGoogleTask(), true)
assertNull(googleTaskDao.getPrevious("1", 0, 0))
}
@Test
fun getPrevious() = runBlocking {
insertTop(newCaldavTask())
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
assertEquals("1234", googleTaskDao.getPrevious("calendar", 0, 1))
insertTop(newGoogleTask())
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
assertEquals("1234", googleTaskDao.getPrevious("1", 0, 1))
}
@Test
fun insertAtTopOfList() = runBlocking {
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
insertTop(newCaldavTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
insertTop(newGoogleTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("1")
assertEquals(2, tasks.size.toLong())
val top = tasks[0]
assertEquals("5678", googleTaskDao.getByTaskId(top.id)?.remoteId)
assertEquals(0L, top.order)
assertEquals("5678", top.remoteId)
assertEquals(0, top.order)
}
@Test
fun insertAtTopOfListShiftsExisting() = runBlocking {
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
insertTop(newCaldavTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
insertTop(newGoogleTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("1")
assertEquals(2, tasks.size.toLong())
val bottom = tasks[1]
assertEquals("1234", googleTaskDao.getByTaskId(bottom.id)?.remoteId)
assertEquals(1L, bottom.order)
assertEquals("1234", bottom.remoteId)
assertEquals(1, bottom.order)
}
@Test
fun getTaskFromRemoteId() = runBlocking {
insert(newCaldavTask(with(REMOTE_ID, "1234")))
assertEquals(1L, googleTaskDao.getTask("1234"))
googleTaskDao.insert(newGoogleTask(with(REMOTE_ID, "1234"), with(TASK, 4)))
assertEquals(4L, googleTaskDao.getTask("1234"))
}
@Test
fun getRemoteIdForTask() = runBlocking {
insert(newCaldavTask(with(REMOTE_ID, "1234")))
assertEquals("1234", googleTaskDao.getRemoteId(1L))
googleTaskDao.insert(newGoogleTask(with(REMOTE_ID, "1234"), with(TASK, 4)))
assertEquals("1234", googleTaskDao.getRemoteId(4L))
}
@Test
fun moveDownInList() = runBlocking {
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "1")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "2")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "3")), false)
val two = getByRemoteId("2")
googleTaskDao.move(taskDao.fetch(two.task)!!, "calendar", 0, 0)
assertEquals(0L, getOrder("2"))
assertEquals(1L, getOrder("1"))
assertEquals(2L, getOrder("3"))
googleTaskDao.move(two, 0, 0)
assertEquals(0, googleTaskDao.getByRemoteId("2")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("1")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("3")!!.order)
}
@Test
fun moveUpInList() = runBlocking {
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "1")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "2")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "3")), false)
val one = getByRemoteId("1")
googleTaskDao.move(taskDao.fetch(one.task)!!, "calendar", 0, 1)
assertEquals(0L, getOrder("2"))
assertEquals(1L, getOrder("1"))
assertEquals(2L, getOrder("3"))
googleTaskDao.move(one, 0, 1)
assertEquals(0, googleTaskDao.getByRemoteId("2")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("1")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("3")!!.order)
}
@Test
fun moveToTop() = runBlocking {
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "1")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "2")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "3")), false)
val three = getByRemoteId("3")
googleTaskDao.move(taskDao.fetch(three.task)!!, "calendar", 0, 0)
assertEquals(0L, getOrder("3"))
assertEquals(1L, getOrder("1"))
assertEquals(2L, getOrder("2"))
googleTaskDao.move(three, 0, 0)
assertEquals(0, googleTaskDao.getByRemoteId("3")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("1")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("2")!!.order)
}
@Test
fun moveToBottom() = runBlocking {
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "1")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "2")), false)
googleTaskDao.insertAndShift(newGoogleTask(with(REMOTE_ID, "3")), false)
val one = getByRemoteId("1")
googleTaskDao.move(taskDao.fetch(one.task)!!, "calendar", 0, 2)
assertEquals(0L, getOrder("2"))
assertEquals(1L, getOrder("3"))
assertEquals(2L, getOrder("1"))
googleTaskDao.move(one, 0, 2)
assertEquals(0, googleTaskDao.getByRemoteId("2")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("3")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("1")!!.order)
}
@Test
fun findChildrenInList() = runBlocking {
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
assertEquals(listOf(2L), googleTaskDao.getChildren(listOf(1L, 2L)))
}
@Test
fun dontAllowEmptyParent() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "1234")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234")))
googleTaskDao.updatePosition("1234", "", "0")
@ -169,7 +169,7 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Test
fun updatePositionWithNullParent() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "1234")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234")))
googleTaskDao.updatePosition("1234", null, "0")
@ -178,7 +178,7 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Test
fun updatePosition() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "1234")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234")))
googleTaskDao.updatePosition("1234", "abcd", "0")
@ -187,87 +187,86 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Test
fun updateParents() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_PARENT, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_PARENT, "123")))
caldavDao.updateParents()
googleTaskDao.updateParents()
assertEquals(1, taskDao.fetch(2)!!.parent)
assertEquals(1, googleTaskDao.getByTaskId(2)!!.parent)
}
@Test
fun updateParentsByList() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_PARENT, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_PARENT, "123")))
caldavDao.updateParents("calendar")
googleTaskDao.updateParents("1")
assertEquals(1, taskDao.fetch(2)!!.parent)
assertEquals(1, googleTaskDao.getByTaskId(2)!!.parent)
}
@Test
fun updateParentsMustMatchList() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "2"), with(REMOTE_PARENT, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "2"), with(REMOTE_PARENT, "123")))
caldavDao.updateParents()
googleTaskDao.updateParents()
assertEquals(0, taskDao.fetch(2)!!.parent)
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
}
@Test
fun updateParentsByListMustMatchList() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "2"), with(REMOTE_PARENT, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "2"), with(REMOTE_PARENT, "123")))
caldavDao.updateParents("2")
googleTaskDao.updateParents("2")
assertEquals(0, taskDao.fetch(2)!!.parent)
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
}
@Test
fun ignoreEmptyStringWhenUpdatingParents() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
caldavDao.updateParents()
googleTaskDao.updateParents()
assertEquals(0, taskDao.fetch(2)!!.parent)
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
}
@Test
fun ignoreEmptyStringWhenUpdatingParentsForList() = runBlocking {
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
caldavDao.updateParents("1")
assertEquals(0, taskDao.fetch(2)!!.parent)
}
googleTaskDao.updateParents("1")
private suspend fun getOrder(remoteId: String): Long? {
return taskDao.fetch(googleTaskDao.getByRemoteId(remoteId)!!.task)?.order
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
}
private suspend fun insertTop(googleTask: CaldavTask) {
private suspend fun insertTop(googleTask: GoogleTask) {
insert(googleTask, true)
}
private suspend fun insertBottom(googleTask: CaldavTask) {
private suspend fun insertBottom(googleTask: GoogleTask) {
insert(googleTask, false)
}
private suspend fun insert(googleTask: CaldavTask, top: Boolean = false) {
private suspend fun insert(googleTask: GoogleTask, top: Boolean) {
val task = newTask()
taskDao.createNew(task)
googleTaskDao.insertAndShift(
task,
googleTask.copy(task = task.id),
top
)
googleTask.task = task.id
googleTaskDao.insertAndShift(googleTask, top)
}
private suspend fun getByRemoteId(remoteId: String): CaldavTask {
return googleTaskDao.getByRemoteId(remoteId)!!
private suspend fun getByRemoteId(remoteId: String): SubsetGoogleTask {
val googleTask = googleTaskDao.getByRemoteId(remoteId)!!
val result = SubsetGoogleTask()
result.gt_id = googleTask.id
result.gt_list_id = googleTask.listId
result.gt_order = googleTask.order
result.gt_parent = googleTask.parent
return result
}
}

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

Loading…
Cancel
Save