mirror of https://github.com/tasks/tasks
Compare commits
No commits in common. 'main' and '5.0.0' have entirely different histories.
@ -1,4 +0,0 @@
|
||||
github: abaker
|
||||
liberapay: tasks
|
||||
patreon: tasks
|
||||
custom: tasks.org/donate
|
||||
@ -1,52 +0,0 @@
|
||||
name: Assemble bundle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
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@v6
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- 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@v6
|
||||
with:
|
||||
name: release
|
||||
path: |
|
||||
app/build/outputs/**
|
||||
wear/build/outputs/**
|
||||
@ -1,96 +0,0 @@
|
||||
name: Run automated checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Lint checks
|
||||
run: bundle exec fastlane lint
|
||||
|
||||
- name: Archive lint reports
|
||||
uses: actions/upload-artifact@v6
|
||||
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@v6
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- 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@v6
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: test-reports-${{ matrix.flavor }}
|
||||
path: app/build/reports/**
|
||||
@ -1,47 +0,0 @@
|
||||
name: Update Dependency Diff
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'gradle/libs.versions.toml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'gradle/libs.versions.toml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-deps:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Update dependency diffs
|
||||
run: ./update_dependency_diff
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add deps_*.txt
|
||||
git diff --staged --quiet || git commit -m "Update dependency diffs"
|
||||
git push
|
||||
@ -1,32 +0,0 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FASTLANE: ${{ secrets.FASTLANE }}
|
||||
|
||||
jobs:
|
||||
bundle:
|
||||
uses: ./.github/workflows/bundle.yml
|
||||
secrets: inherit
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ bundle ]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Fastlane key
|
||||
run: |
|
||||
echo "$FASTLANE" > ./fastlane.json
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: release
|
||||
path: .
|
||||
- name: Deploy
|
||||
run: bundle exec fastlane deploy
|
||||
@ -1,14 +1,7 @@
|
||||
.kotlin
|
||||
.idea
|
||||
*.iml
|
||||
.gradle
|
||||
build/
|
||||
*.apk
|
||||
*.apks
|
||||
*.aab
|
||||
local.properties
|
||||
Thumbs.db
|
||||
/captures/
|
||||
/fastlane/report.xml
|
||||
/compose-metrics/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@ -0,0 +1,22 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Convert2streamapi" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Guava" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
|
||||
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StaticPseudoFunctionalStyleMethod" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="TryWithIdenticalCatches" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
@ -1 +0,0 @@
|
||||
3.4.8
|
||||
@ -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>
|
||||
@ -0,0 +1,28 @@
|
||||
language: android
|
||||
sudo: required
|
||||
jdk: oraclejdk8
|
||||
env:
|
||||
matrix:
|
||||
- ANDROID_TARGET=android-21 ANDROID_ABI=armeabi-v7a
|
||||
android:
|
||||
components:
|
||||
- tools # https://github.com/travis-ci/travis-ci/issues/5049
|
||||
- tools # https://github.com/travis-ci/travis-ci/issues/6040
|
||||
- android-26
|
||||
- sys-img-armeabi-v7a-android-21
|
||||
- platform-tools
|
||||
- build-tools-26.0.1
|
||||
- extra-android-m2repository
|
||||
- extra-google-m2repository
|
||||
licenses:
|
||||
- 'android-sdk-license-.+'
|
||||
|
||||
before_install:
|
||||
- echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI
|
||||
- emulator -avd test -no-skin -no-audio -no-window &
|
||||
- ./.wait_for_emulator.sh
|
||||
- adb shell input keyevent 82 &
|
||||
|
||||
script:
|
||||
- ./gradlew :app:lintGoogleplayDebug
|
||||
- ./gradlew :app:connectedGoogleplayDebugAndroidTest
|
||||
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
bootanim=""
|
||||
failcounter=0
|
||||
until [[ "$bootanim" =~ "stopped" ]] || [[ "$bootanim" =~ "running" ]]; do
|
||||
bootanim=`adb -e shell getprop init.svc.bootanim 2>&1`
|
||||
echo "$bootanim"
|
||||
if [[ "$bootanim" =~ "not found" ]]; then
|
||||
let "failcounter += 1"
|
||||
if [[ $failcounter -gt 3 ]]; then
|
||||
echo "Failed to start emulator"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Done"
|
||||
@ -1,989 +0,0 @@
|
||||
### 14.8.5 (2026-01-09)
|
||||
|
||||
* Widget performance improvements
|
||||
* What's New now opens changelog on GitHub
|
||||
* Fix automatically opening keyboard for new tasks [#4035](https://github.com/tasks/tasks/issues/4035)
|
||||
* Fix parent-child cycle causing crash [#4065](https://github.com/tasks/tasks/issues/4065)
|
||||
* Fix state restoration issue [#4025](https://github.com/tasks/tasks/issues/4025)
|
||||
* Fix all day calendar entries created on previous day
|
||||
* Fix Microsoft To Do and Google Task sync errors
|
||||
* Fix multiple icons missing from widget
|
||||
* Update translations
|
||||
* Arabic - @fahedoudeh
|
||||
* Portuguese - Paulo
|
||||
* Vietnamese - @ngocanhtve
|
||||
|
||||
### 14.8.4 (2025-12-20)
|
||||
|
||||
* Fix flashing widgets [#3902](https://github.com/tasks/tasks/issues/3902)
|
||||
* Fix random reminder scheduling
|
||||
* Fix random reminders firing immediately on recurring tasks [#3904](https://github.com/tasks/tasks/issues/3904)
|
||||
* Fix deadlock when adding new task
|
||||
* Fix crash in settings when backup location unavailable [#3989](https://github.com/tasks/tasks/issues/3989)
|
||||
* Fix Hebrew and Indonesian support [#3928](https://github.com/tasks/tasks/issues/3928)
|
||||
* Update translations
|
||||
* Asturian - Xana
|
||||
* Bosnian - @hasak
|
||||
* Finnish - @pHamala
|
||||
* Indonesian - @erigmac
|
||||
* Japanese - @array, Norara
|
||||
* Persian - @theuser17
|
||||
* Romanian - @ygorigor
|
||||
|
||||
### 14.8.3 (2025-09-16)
|
||||
|
||||
* Fix crash on Android 10 and below
|
||||
|
||||
### 14.8.2 (2025-09-14)
|
||||
|
||||
* Fix blank widgets on Android 16 QPR1 [#3847](https://github.com/tasks/tasks/issues/3847)
|
||||
* Fix all-day calendar events [#1534](https://github.com/tasks/tasks/issues/1534)
|
||||
* Fix alarm synchronization [#3859](https://github.com/tasks/tasks/issues/3859)
|
||||
* Fix sync failure when migrating data from EteSync to CalDAV [#3869](https://github.com/tasks/tasks/issues/3869)
|
||||
* Fix removing values from Microsoft To Do [#3862](https://github.com/tasks/tasks/issues/3862)
|
||||
* Fix share invites for Nextcloud [#2386](https://github.com/tasks/tasks/issues/2386)
|
||||
* Fix failure to delete source data when moving to Google Tasks [#3867](https://github.com/tasks/tasks/issues/3867)
|
||||
* Fix crash when clearing completed while grouping by lists
|
||||
* Update translations
|
||||
* Croatian - @milotype
|
||||
* Dutch - @fvbommel
|
||||
* German - @MisterTechnik
|
||||
* Italian - @glemco
|
||||
* Serbian - @vale-decem
|
||||
|
||||
### 14.8.1 (2025-08-24)
|
||||
|
||||
* System bar scrim improvements
|
||||
* Recover from Google Task 'Bad request' errors
|
||||
* Improve layout on Z Folds
|
||||
* Crash fixes
|
||||
* Update translations
|
||||
* Brazilian Portuguese - odnankenobi
|
||||
* Catalan - @Crashillo, @ferranpujolcamins
|
||||
* Danish - ERYpTION
|
||||
* Esperanto - Don Zouras
|
||||
* Galician - @Crashillo, @delthia
|
||||
* Hungarian - @Antmajgra, @gthrepwood
|
||||
* Italian - @ppasserini
|
||||
* Korean - Jiho Min
|
||||
* Polish - @Antmajgra
|
||||
* Portuguese - @Crashillo
|
||||
* Russian - Алексей Ежков
|
||||
* Spanish - @Crashillo
|
||||
|
||||
### 14.8 (2025-08-02)
|
||||
|
||||
* Synchronize **list** icons for Tasks.org and CalDAV accounts
|
||||
* Does not apply to Microsoft To Do, Google Tasks, DAVx5, EteSync, or DecSync
|
||||
CC accounts
|
||||
* Does not apply to tags or filters
|
||||
* CalDAV server must support extensible properties, e.g. Nextcloud or sabre/dav
|
||||
* Target Android 15
|
||||
* Return to previous view after searching
|
||||
* Remove shadow from date picker sheet
|
||||
* Fix updating list names and colors for Tasks.org and CalDAV accounts
|
||||
* Update translations
|
||||
* Bulgarian - 109247019824
|
||||
* Chinese (Simplified) - Sketch6580
|
||||
* Czech - @Fjuro
|
||||
* Dutch - @fvbommel
|
||||
* Estonian - Priit Jõerüüt
|
||||
* French - @FlorianLeChat
|
||||
* German - @Colorful Rhino
|
||||
* Hebrew - Xo
|
||||
* Italian - @ppasserini
|
||||
* Turkish - @emintufan
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 14.7.4 (2025-07-12)
|
||||
|
||||
* @devn1x: Fix escaping quotes in iCalendar [#3645](https://github.com/tasks/tasks/pull/3645)
|
||||
* Limit widget to 25 items on Android 16+
|
||||
* Android 16 nerfed widget performance 😢
|
||||
* Fix bug when reconfiguring widget
|
||||
* Fix default widget group sort order
|
||||
* Update translations
|
||||
* Catalan - pitroig
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Croatian - @milotype
|
||||
* German - @Kachelkaiser
|
||||
* Serbian - @vale-decem
|
||||
* Swedish - Nick Wick
|
||||
* Tamil - @TamilNeram
|
||||
|
||||
### 14.7.3 (2025-06-13)
|
||||
|
||||
* Fix dynamic color
|
||||
* Fix Microsoft To Do sync failure
|
||||
* Fix crash after deleting last list
|
||||
* Fix notifications when 'Alarms & reminders' not allowed
|
||||
* Update translations
|
||||
* Bulgarian - 109247019824
|
||||
* Dutch - @fvbommel
|
||||
* Esperanto - Don Zouras
|
||||
* French - @FlorianLeChat
|
||||
* Hebrew - Xo
|
||||
* Japanese - M_Haruki
|
||||
* Persian - @theuser17
|
||||
* Portuguese - @nero-bratti
|
||||
* Romanian - @ygorigor
|
||||
* Russian - @yurtpage
|
||||
* Spanish - @orionn333
|
||||
* Swedish - @Nicklasfox
|
||||
* Turkish - @emintufan
|
||||
|
||||
### 14.7.2 (2025-05-23)
|
||||
|
||||
* Remove Microsoft Authentication Library from F-Droid builds [#3581](https://github.com/tasks/tasks/issues/3581)
|
||||
* Remove contacts permission added by Microsoft Authentication Library
|
||||
* Enable video attachments
|
||||
* Fix wallpaper theme
|
||||
* Fix handling multiple attachments
|
||||
* Update translations
|
||||
* Arabic - abdelbasset jabrane
|
||||
* Bulgarian - 109247019824
|
||||
* Catalan - @Crashillo
|
||||
* Czech - @Fjuro
|
||||
* Danish - @catsnote
|
||||
* Dutch - @fvbommel
|
||||
* Esperanto - Don Zouras
|
||||
* Estonian - Priit Jõerüüt
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Turkish - @emintufan
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 14.7.1 (2025-05-04)
|
||||
|
||||
* Fix app closing itself automatically [#3366](https://github.com/tasks/tasks/issues/3366)
|
||||
* Automatically set default list when connecting Microsoft To Do account
|
||||
* Update translations
|
||||
* Arabic - abdelbasset jabrane, @kemo-1
|
||||
* Brazilian Portuguese - Jose Delvani
|
||||
* Chinese (Simplified) - Sketch6580
|
||||
* French - @FlorianLeChat
|
||||
* German - @Kachelkaiser
|
||||
|
||||
### 14.7 (2025-05-03)
|
||||
|
||||
* Add support for Microsoft To Do work & school accounts [#3267](https://github.com/tasks/tasks/issues/3267)
|
||||
* Add ability to rename or delete local account
|
||||
* Prompt to sign in or import backup on first launch
|
||||
* @BeaterGhalio: Fix back button closing app after search [#3426](https://github.com/tasks/tasks/issues/3426)
|
||||
* @codokie: Automirrored icons fix [#3499](https://github.com/tasks/tasks/pull/3499)
|
||||
* @codokie: Fix ltr-rtl alignment for text input [#3489](https://github.com/tasks/tasks/pull/3489)
|
||||
* Use system language picker on Android 33+
|
||||
* Don't show 'due date' as a start date option for DAVx5, EteSync, DecSync CC [#1558](https://github.com/tasks/tasks/issues/1558)
|
||||
* Prevent attempts to delete or rename Microsoft To Do default list
|
||||
* Don't handle system 'Clear storage' button
|
||||
* Update minimum Android version to 8
|
||||
* Fix backup import dropping tags [#3556](https://github.com/tasks/tasks/issues/3556)
|
||||
* Fix start date chip when grouping by start date [#3509](https://github.com/tasks/tasks/issues/3509)
|
||||
* Update translations
|
||||
* Brazilian Portuguese - @sobeitnow0, dedakir923
|
||||
* Czech - @Fjuro
|
||||
* Dutch - Jay Tromp
|
||||
* German - min7-i
|
||||
* Hebrew - Xo
|
||||
* Portuguese - @wm-pucrs
|
||||
* Russian - @hady-exc, Maksim_220 Кабанов
|
||||
* Slovak - @jose1711
|
||||
* Spanish - Nucl3arSnake, @diamondtipdr
|
||||
* Tamil - @TamilNeram
|
||||
|
||||
### 14.6.2 (2025-04-06)
|
||||
|
||||
* Show error indicators if 'When started' or 'When due' reminders are used
|
||||
without start or due times
|
||||
* Fix delay when saving tasks
|
||||
* Fix populating clock picker with initial value instead of 00:00
|
||||
* Fix displaying selected calendar month
|
||||
* Fix grouping by start date in descending order
|
||||
* Update translations
|
||||
* Arabic - abdelbasset jabrane
|
||||
* Danish - @catsnote
|
||||
* Esperanto - Don Zouras
|
||||
* German - @Kachelkaiser
|
||||
* Hebrew - @elid34
|
||||
* Italian - @Fs00
|
||||
* Slovak - @jose1711
|
||||
* Turkish - @emintufan
|
||||
|
||||
### 14.6.1 (2025-03-30)
|
||||
|
||||
* Restore default sort mode for existing installs
|
||||
* Fix grouping by due date descending
|
||||
* Remove shadow from launcher icons
|
||||
|
||||
### 14.6 (2025-03-25)
|
||||
|
||||
* Add dynamic theme color - requires pro subscription
|
||||
* Update translation
|
||||
* Brazilian Portuguese - dedakir923
|
||||
* Bulgarian - 109247019824
|
||||
* Chinese (Simplified) - Sketch6580
|
||||
* Estonian - Priit Jõerüüt
|
||||
* Italian - @ppasserini
|
||||
* Japanese - YuzuMikan
|
||||
* Swedish - @Ziron
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 14.5.4 (2025-03-24)
|
||||
|
||||
* Updated remaining date and time pickers to Material 3
|
||||
* App will remember if you change calendar or clock to text input
|
||||
* Text input now supported on start and due date pickers
|
||||
* Remove calendar and clock mode settings
|
||||
* Open date picker to currently selected month
|
||||
* Replaced upgrade pop-up with a banner [#1429](https://github.com/tasks/tasks/issues/1429)
|
||||
* @hady-exc: Fix date picker time zone issues [#3248](https://github.com/tasks/tasks/pull/3248)
|
||||
* Fix date time picker font scaling issues [#3437](https://github.com/tasks/tasks/issues/3437)
|
||||
* Fix save task on keyboard done [#3288](https://github.com/tasks/tasks/issues/3288)
|
||||
* Fix applying date time when dismissing date time pickers
|
||||
* Fix 3 button navigation bar padding in landscape mode
|
||||
* Fix out of memory errors in backup import/export
|
||||
* Update translations
|
||||
* Brazilian Portuguese - dedakir923
|
||||
* Bulgarian - 109247019824
|
||||
* Chinese (Simplified) - Sketch6580
|
||||
* Dutch - @fvbommel
|
||||
* Estonian - Priit Jõerüüt
|
||||
* French - @FlorianLeChat
|
||||
* German - @franconian
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Romanian - @ygorigor
|
||||
* Tamil - @TamilNeram
|
||||
* Turkish - @emintufan
|
||||
|
||||
### 14.5.3 (2025-03-20)
|
||||
|
||||
* Updated date and time pickers to Material 3
|
||||
* Remove 'Start of week' preference
|
||||
* This feature can't be supported with Material 3 calendars
|
||||
|
||||
### 14.5.2 (2025-03-15)
|
||||
|
||||
* Fix items hidden under menu search bar [#3406](https://github.com/tasks/tasks/issues/3406)
|
||||
* Attempt to fix layout on some foldables
|
||||
* Fix checking for tasks.org account [#3397](https://github.com/tasks/tasks/issues/3397)
|
||||
* Slightly reduce donation nagging frequency [#3397](https://github.com/tasks/tasks/issues/3397)
|
||||
* Update translations
|
||||
* Danish - Øjvind Fritjof Arnfred
|
||||
* Hungarian - Kaci
|
||||
* Malayalam - Clouds Liberty
|
||||
* Russian - @GREAT-DNG
|
||||
* Swedish - @bittin
|
||||
* Tamil - @TamilNeram
|
||||
|
||||
### 14.5.1 (2025-03-11)
|
||||
|
||||
* Fix performance issue when opening search
|
||||
* Fix Microsoft To Do authentication crash
|
||||
* Fix crash on task list screen
|
||||
* Update translation
|
||||
* Brazilian Portuguese - dedakir923
|
||||
* Bulgarian - 109247019824
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Dutch - @fvbommel
|
||||
* Esperanto - Don Zouras
|
||||
* Estonian - Priit Jõerüüt
|
||||
* French - @FlorianLeChat
|
||||
* German - Colorful Rhino
|
||||
* Italian - @ppasserini
|
||||
* Kannada - Abilash S
|
||||
* Persian - @mamad-zahiri
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 14.5 (2025-03-04)
|
||||
|
||||
* Material 3 - work in progress
|
||||
* Side navigation drawer
|
||||
* Improve support for foldables
|
||||
* Improve edge-to-edge support
|
||||
* Remove options for top app bar and disabling collapsing app bar
|
||||
* Some features are being removed in order to make development easier for the
|
||||
upcoming desktop app. The features may return again in a future release.
|
||||
* Save backup files and attachments to Nextcloud [#1289](https://github.com/tasks/tasks/issues/1289)
|
||||
* Dismiss notification dialog when pressing cancel [#2116](https://github.com/tasks/tasks/issues/2116)
|
||||
* Performance improvements
|
||||
* Fix Microsoft To Do sync failure
|
||||
* Fix missing list chips for subtasks in custom filters
|
||||
* Fix for database timeouts
|
||||
* Fix infinite subtask recursion
|
||||
* Update translations
|
||||
* Belarusian - @fobo66
|
||||
* Estonian - Priit Jõerüüt
|
||||
* German - Colorful Rhino
|
||||
* Japanese - M_Haruki
|
||||
* Nahuatl - Benjamin Bruce
|
||||
* Slovak - @jose1711
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 14.4.8 (2025-02-04)
|
||||
|
||||
* Performance improvements
|
||||
* Update translations
|
||||
* German - Colorful Rhino, @Kachelkaiser
|
||||
* Nepali - Sagun Khatri
|
||||
|
||||
### 14.4.7 (2025-02-01)
|
||||
|
||||
* Database improvements
|
||||
* Update translations
|
||||
* Estonian - Priit Jõerüüt
|
||||
* German - @Kachelkaiser
|
||||
|
||||
### 14.4.6 (2025-01-29)
|
||||
|
||||
* Database performance improvements
|
||||
* Additional debug logging
|
||||
* Update translations
|
||||
* Danish - ERYpTION
|
||||
* Estonian - Priit Jõerüüt
|
||||
* German - @franconian, Colorful Rhino, @Kachelkaiser
|
||||
* Italian - @ppasserini
|
||||
* Korean - Sunjae Choi
|
||||
* Nepali - Sagun Khatri
|
||||
* Slovak - @jose1711
|
||||
* Swedish - Nick Wick
|
||||
|
||||
### 14.4.5 (2025-01-22)
|
||||
|
||||
* Performance improvements
|
||||
* DAVx5 sync performance improvements
|
||||
* Update translations
|
||||
* Bosnian - @hasak
|
||||
* Esperanto - Don Zouras
|
||||
* Estonian - Priit Jõerüüt, @dermezl
|
||||
* Italian - @ppasserini
|
||||
* Nepali - @sagunkhatri
|
||||
|
||||
|
||||
### 14.4.4 (2025-01-19)
|
||||
|
||||
* Fix list pickers [#3269](https://github.com/tasks/tasks/issues/3269)
|
||||
|
||||
### 14.4.3 (2025-01-18)
|
||||
|
||||
* Preserve reminder recurrence when copying tasks
|
||||
* Refresh task list after changing settings
|
||||
* Fix missing chips for local lists
|
||||
* Fix changes being lost when completing task from edit screen
|
||||
* Update translations
|
||||
* German - @franconian
|
||||
* Turkish - @emintufan
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 14.4.2 (2025-01-16)
|
||||
|
||||
* Fix crash on missing account
|
||||
* Update translations
|
||||
* Bulgarian - 109247019824
|
||||
* Chinese (Simplified) - Sketch6580
|
||||
* Croatian - @milotype
|
||||
* Dutch - @fvbommel
|
||||
* Esperanto - Don Zouras
|
||||
* French - @FlorianLeChat, @CennoxX
|
||||
* German - @franconian
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Russian - @hady-exc
|
||||
* Slovak - @jose1711
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 14.4.1 (2025-01-11)
|
||||
|
||||
* Microsoft To Do support [#2011](https://github.com/tasks/tasks/issues/2011)
|
||||
* This feature is in early access, please report any bugs!
|
||||
* Enable under 'Advanced' settings
|
||||
* Add configuration option for new lines in titles
|
||||
* @TonSilver - Copy comments to clipboard with long press [#3212](https://github.com/tasks/tasks/pull/3212)
|
||||
* @jheld - Attempt to fix F-Droid build with colorpicker fork [#2028](https://github.com/tasks/tasks/issues/2028)
|
||||
* Subscription changes
|
||||
* Multiple Google Task accounts are now free to use
|
||||
* Tasker plugins are now free to use
|
||||
* Fix crash on empty shortcut labels
|
||||
* Fix missing settings button on Android 10 and below
|
||||
* Update translations
|
||||
* Bulgarian - 109247019824
|
||||
* Chinese (Simplified) - 大王叫我来巡山, Sketch6580
|
||||
* Czech - @AtmosphericIgnition
|
||||
* Dutch - @fvbommel
|
||||
* Esperanto - Don Zouras
|
||||
* French - @FlorianLeChat, @lfavole
|
||||
* German - @franconian, Colorful Rhino
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Slovak - @jose1711
|
||||
* Swedish - @Ziron, @bittin
|
||||
* Turkish - @emintufan
|
||||
|
||||
### 14.3.1 (2025-01-02)
|
||||
|
||||
* Fix edit screen disappearing on rotation
|
||||
* Fix notification bundling issue
|
||||
* Fix scrolling in custom filter settings
|
||||
* Remove map theme and desaturation options
|
||||
* Update translations
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Dutch - @fvbommel
|
||||
* French - @FlorianLeChat
|
||||
* German - @p-rogalski
|
||||
* Italian - @ppasserini
|
||||
* Korean - Sunjae Choi
|
||||
* Swedish - @bittin
|
||||
|
||||
### 14.3 (2024-12-24)
|
||||
|
||||
* "Add widget to home screen" shortcut in list settings
|
||||
* "Add shortcut to home screen" shortcut in list settings
|
||||
* Shortcuts use list icon and color
|
||||
* Fix long running sync indicators [#3045](https://github.com/tasks/tasks/issues/3045)
|
||||
* @hady-exc: Migrate list setting screens to Compose [#3163](https://github.com/tasks/tasks/pull/3163)
|
||||
* Update translations
|
||||
* Bosnian - @hasak
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Croatian - @milotype
|
||||
* Esperanto - Don Zouras
|
||||
* Finnish - @pHamala, @Ricky-Tigg
|
||||
* German - @p-rogalski, @franconian, @Atalanttore
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Korean - Sunjae Choi
|
||||
* Spanish - gallegonovato
|
||||
* Swedish - Nick Wick
|
||||
|
||||
### 14.2.1 (2024-12-03)
|
||||
|
||||
* Fix save button when 'Back button saves task' is enabled [#3149](https://github.com/tasks/tasks/issues/3149)
|
||||
* Fix customizing edit screen order screen
|
||||
|
||||
### 14.2 (2024-12-02)
|
||||
|
||||
* Updated edit screen task title
|
||||
* Show full title
|
||||
* Removed collapse on scroll
|
||||
* Removed floating action button
|
||||
* Add separate alarms and reminders warning
|
||||
* Capitalize tag picker text field
|
||||
* Update translations
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Catalan - raulmagdalena
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Dutch - @fvbommel
|
||||
* French - @FlorianLeChat
|
||||
* Italian - @ppasserini
|
||||
* Spanish - gallegonovato
|
||||
* Ukrainian - @nathalier
|
||||
|
||||
### 14.1.1 (2024-11-26)
|
||||
|
||||
* Show warning when quiet hours are in effect
|
||||
* Fix escape character in some localizations [#3046](https://github.com/tasks/tasks/issues/3046)
|
||||
* Fix comment delete button color [#3102](https://github.com/tasks/tasks/issues/3102)
|
||||
* Update translations
|
||||
* Bosnian - @hasak
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Catalan - raulmagdalena
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Croatian - @milotype
|
||||
* Dutch - @fvbommel
|
||||
* Esperanto - Don Zouras
|
||||
* French - @FlorianLeChat
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Polish - @rom4nik
|
||||
* Spanish - gallegonovato
|
||||
* Swedish - Nick Wick
|
||||
|
||||
### 14.1 (2024-11-20)
|
||||
|
||||
* Add 'Help & Feedback > Send application logs'
|
||||
* Delete snoozed reminders when completing tasks
|
||||
* Fix duplicated tasks when using 'Share' [#2404](https://github.com/tasks/tasks/issues/2404)
|
||||
* Don't show sync indicator on startup when sync is not used
|
||||
* Update translations
|
||||
* Bosnian - @hasak
|
||||
* Brazilian Portuguese - kowih83264
|
||||
* Croatian - @milotype
|
||||
* German - min7-i
|
||||
|
||||
### 14.0.1 (2024-11-10)
|
||||
|
||||
* Fix widget crash
|
||||
* Fix EteSync sync failure [#3092](https://github.com/tasks/tasks/issues/3092)
|
||||
* Minor Wear OS improvements
|
||||
* 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
|
||||
* French - @FlorianLeChat
|
||||
* German - @p-rogalski, @franconian
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Spanish - gallegonovato
|
||||
* Swedish - @bittin
|
||||
* Turkish - @oersen
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 13.11.2 (2024-09-29)
|
||||
|
||||
* 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
|
||||
* 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)
|
||||
* Update translations
|
||||
* Brazilian Portuguese - Jose Delvani
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Catalan - @Seveorr, @jtorrensamer
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Chinese (Traditional) - hugoalh
|
||||
* French - @FlorianLeChat
|
||||
* Spanish - gallegonovato
|
||||
* Turkish - @oersen
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 13.10 (2024-07-05)
|
||||
|
||||
* 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~~
|
||||
* 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)
|
||||
|
||||
* Fix widget crash [#2873](https://github.com/tasks/tasks/issues/2873)
|
||||
* Fix recurrence unable to finish [#2874](https://github.com/tasks/tasks/issues/2874)
|
||||
* Fix edit screen being cleared when reopening app [#2857](https://github.com/tasks/tasks/issues/2857)
|
||||
* Fix performance regressions
|
||||
* Simplified internal alarm scheduling logic
|
||||
* Update translations
|
||||
* Arabic - @islam2hamy
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
|
||||
### 13.9 (2024-05-01)
|
||||
|
||||
* @elmuffo: Add swipe-to-snooze [#2839](https://github.com/tasks/tasks/pull/2839)
|
||||
* @IlyaBizyaev: Add option to use quick tile without unlocking device [#2847](https://github.com/tasks/tasks/pull/2847)
|
||||
* @liz-desartiges: Add support for Z Flip 5 cover screen [#2843](https://github.com/tasks/tasks/pull/2843)
|
||||
* @purushyb: Fix drawer not updating after editing items [#2855](https://github.com/tasks/tasks/pull/2855)
|
||||
* @hady-exc: Migrate tag picker screen to Compose [#2849](https://github.com/tasks/tasks/pull/2849)
|
||||
* @yurtpage: Add Russian app store description [#2848](https://github.com/tasks/tasks/pull/2848)
|
||||
* Fix duplicate notifications [#2835](https://github.com/tasks/tasks/issues/2835)
|
||||
* Fix adding '(Completed)' to calendar entries [#2832](https://github.com/tasks/tasks/issues/2832)
|
||||
* Fix hiding empty items from drawer [#2831](https://github.com/tasks/tasks/issues/2831)
|
||||
* Exclude old snoozed tasks from snoozed task filter
|
||||
* Update translations
|
||||
* Brazilian Portuguese - @mayhmemo, @gorgonun
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Croatian - @milotype
|
||||
* Esperanto - Don Zouras
|
||||
* French - Lionel HANNEQUIN
|
||||
* German - sorifukobexomajepasiricupuva33, min7-i
|
||||
* Portuguese - @fparri, @laralem
|
||||
* Spanish - gallegonovato
|
||||
* Swedish - @JonatanWick
|
||||
* Turkish - @emintufan, @oersen
|
||||
|
||||
### 13.8.1 (2024-03-24)
|
||||
|
||||
* Fix copy causing duplicate Google Tasks
|
||||
* Fix navigation drawer crash
|
||||
* Fix backup import dropping tasks
|
||||
|
||||
### 13.8 (2024-03-22)
|
||||
|
||||
* Dynamic widget theme (name-your-price subscription required)
|
||||
* Replace 'until' with 'ends on' for repeating tasks [#2797](https://github.com/tasks/tasks/pull/2797) - @akwala
|
||||
* Fix loading selected list on startup [#2777](https://github.com/tasks/tasks/issues/2777)
|
||||
* Fix repeating tasks ending one day early
|
||||
* Fix repeating task crash
|
||||
* Fix backup import crash
|
||||
* Fix Astrid manual ordering crash in widget
|
||||
* Update translations
|
||||
* Brazilian Portuguese - @mayhmemo
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Catalan - @ferranpujolcamins
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Croatian - @milotype
|
||||
* Czech - Odweta
|
||||
* German - @macpac59
|
||||
* Italian - @ppasserini
|
||||
* Spanish - gallegonovato
|
||||
* Swedish - @bittin
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
* Vietnamese - @ngocanhtve
|
||||
|
||||
### 13.7 (2024-02-07)
|
||||
|
||||
* Fix returning to previous filter after search [#2700](https://github.com/tasks/tasks/pull/2700)
|
||||
* Fix wearable notifications on Android 14+
|
||||
* Fix issue causing repeating tasks to not repeat
|
||||
* Fix dragging a task into a subtask in another list
|
||||
* Rewrote navigation drawer in Jetpack Compose
|
||||
* Internal changes to navigation
|
||||
* Enable multi-select when adding attachments
|
||||
* Show count of tasks to be deleted when clearing completed
|
||||
* Include hidden subtasks when clearing completed [#2724](https://github.com/tasks/tasks/issues/2724)
|
||||
* Don't show hidden or completed tasks in snoozed filter
|
||||
* Remove markdown from repeating task snackbar
|
||||
* Update translations
|
||||
* Azerbaijani - Shaban Mamedov
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Catalan - raulmagdalena
|
||||
* Chinese (Simplified) - 大王叫我来巡山
|
||||
* Chinese (Traditional) - @abc0922001
|
||||
* Croatian - @milotype
|
||||
* Dutch - @mm4c
|
||||
* Esperanto - Don Zouras
|
||||
* Finnish - @millerii
|
||||
* French - J. Lavoie
|
||||
* German - @CennoxX
|
||||
* Hebrew - @elig0n
|
||||
* Interlingua - @softinterlingua
|
||||
* Odia - @SubhamJena
|
||||
* Persian - @Monirzadeh
|
||||
* Spanish - gallegonovato
|
||||
* Swedish - @bittin
|
||||
* Turkish - @oersen
|
||||
* Ukrainian - Сергій
|
||||
* Vietnamese - @ngocanhtve
|
||||
|
||||
### 13.6.3 (2023-11-25)
|
||||
|
||||
* Revert "Preserve modification times on initial sync" [#2460](https://github.com/tasks/tasks/issues/2640)
|
||||
* Fix unnecessary DecSync work
|
||||
|
||||
### 13.6.2 (2023-10-30)
|
||||
|
||||
* Fix updating modification timestamp on edits
|
||||
|
||||
### 13.6.1 (2023-10-27)
|
||||
|
||||
* Push pending changes when app is backgrounded
|
||||
* Don't require internet connection for DAVx5/EteSync/DecSync sync
|
||||
* Don't perform background sync for DAVx5/EteSync/DecSync
|
||||
* Background sync is performed by the sync app
|
||||
* Preserve modification times on initial sync [#2496](https://github.com/tasks/tasks/issues/2496)
|
||||
* Replace deprecated method call [#2547](https://github.com/tasks/tasks/pull/2547) - @kmj-99
|
||||
* Improve task list scrolling performance
|
||||
* Fix hourly recurrence bug
|
||||
* Update translations
|
||||
* Chinese (Simplified) - Eric
|
||||
* Croatian - @milotype
|
||||
* Czech - @ceskyDJ
|
||||
* Finnish - @millerii
|
||||
* French - Lionel HANNEQUIN, Bruno Duyé
|
||||
* Japanese - Kazushi Hayama
|
||||
* Portuguese - @loucurapt
|
||||
* Romanian - @ygorigor
|
||||
* Swedish - @bittin
|
||||
|
||||
### 13.6 (2023-10-07)
|
||||
|
||||
* Change priority with multi-select [#2257](https://github.com/tasks/tasks/pull/2452) - @vulewuxe86
|
||||
* 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
|
||||
* Dutch - @fvbommel
|
||||
* French - @FlorianLeChat
|
||||
* German - @qwerty287, deep map, @franconian
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Japanese - Kazushi Hayama, Naga
|
||||
* Spanish - @FlorianLeChat
|
||||
* Swedish - @Anaemix, @bittin
|
||||
* Turkish - @emintufan, @oersen
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 13.5.1 (2023-08-02)
|
||||
|
||||
* 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
|
||||
|
||||
### 13.5 (2023-07-28)
|
||||
|
||||
* New custom recurrence picker
|
||||
* Update translations
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Czech - @ceskyDJ
|
||||
* Dutch - @fvbommel
|
||||
* French - @FlorianLeChat
|
||||
* Italian - @ppasserini
|
||||
* Spanish - @FlorianLeChat
|
||||
|
||||
### 13.4 - (2023-07-16)
|
||||
|
||||
* 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
|
||||
* Update translations
|
||||
* Bulgarian - @StoyanDimitrov
|
||||
* Catalan - @and4po
|
||||
* Chinese (Simplified) - Poesty Li
|
||||
* Croatian - @milotype
|
||||
* Dutch - @fvbommel
|
||||
* French - @FlorianLeChat
|
||||
* German - @qwerty287, @franconian
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Spanish - @FlorianLeChat
|
||||
* 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
|
||||
* 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
|
||||
* 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)
|
||||
|
||||
* 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
|
||||
* Dutch - @fvbommel
|
||||
* Finnish - @millerii
|
||||
* French - @FlorianLeChat
|
||||
* German - @helloworldtest123
|
||||
* Hungarian - Kaci
|
||||
* Italian - @ppasserini
|
||||
* Lithuanian - @70h
|
||||
* Russian - Nikita Epifanov
|
||||
* Spanish - @FlorianLeChat
|
||||
* Turkish - @ersen0
|
||||
* Ukrainian - @IhorHordiichuk
|
||||
|
||||
### 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
|
||||
|
||||
[Older releases](https://github.com/tasks/tasks/blob/main/V10_12_CHANGELOG.md)
|
||||
@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at github@tasks.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@ -1,34 +0,0 @@
|
||||
### Translation
|
||||
|
||||
You can translate Tasks using [Weblate](https://hosted.weblate.org/projects/tasks/android). To get started, register a new account or login with your GitHub account if you have one.
|
||||
|
||||
### Opening issues
|
||||
|
||||
Before opening an issue, please make sure that your issue:
|
||||
- is not a duplicate (i.e. it has not been reported before, closed or open)
|
||||
- has not been fixed
|
||||
- is in English (issues in a language other than English will be closed unless someone translates them)
|
||||
- does not contain multiple feature requests/bug reports. Please open a separate issue for each one.
|
||||
|
||||
### Code contribution
|
||||
|
||||
#### 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)
|
||||
3. Select `File > Open`, select the Tasks directory, and accept prompts to install missing SDK components
|
||||
|
||||
#### Set up Mapbox
|
||||
1. Register at [mapbox.com](https://www.mapbox.com)
|
||||
2. Add `tasks_mapbox_key_debug="<your_api_key>"` to your [`gradle.properties`](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties) file. You can create an access token or use your [default public token](https://docs.mapbox.com/help/glossary/access-token/#default-public-token)
|
||||
|
||||
#### Set up Google Tasks and Google Drive
|
||||
1. Register at [cloud.google.com](https://cloud.google.com)
|
||||
2. Enable [Google Tasks API](https://console.cloud.google.com/apis/library/tasks.googleapis.com) and [Google Drive API](https://console.cloud.google.com/apis/library/drive.googleapis.com)
|
||||
3. [Create android authorization credentials](https://developers.google.com/identity/protocols/OAuth2InstalledApp#creatingcred)
|
||||
|
||||
#### Set up Google Maps and Google Places
|
||||
1. Register at [cloud.google.com](https://cloud.google.com)
|
||||
2. Enable [Google Maps SDK](https://console.cloud.google.com/apis/library/maps-android-backend.googleapis.com) and [Google Places API](https://console.cloud.google.com/apis/library/places-backend.googleapis.com)
|
||||
3. [Set up an API key](https://cloud.google.com/video-intelligence/docs/common/auth#set_up_an_api_key)
|
||||
4. Add `tasks_google_key_debug="<your_api_key>"` to your [`gradle.properties`](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties) file
|
||||
5. Select `Build > Select Build Variant` and choose the `googleplay` variant
|
||||
@ -1,4 +0,0 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
gem "abbrev"
|
||||
@ -1,230 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
abbrev (0.1.2)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1196.0)
|
||||
aws-sdk-core (3.240.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.118.0)
|
||||
aws-sdk-core (~> 3, >= 3.239.1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.208.0)
|
||||
aws-sdk-core (~> 3, >= 3.234.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.3.0)
|
||||
bigdecimal (4.0.1)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
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)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.1.1)
|
||||
multipart-post (~> 2.0)
|
||||
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.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.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)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
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)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
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.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
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)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
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.8.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.5.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.31.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.12.2)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.2)
|
||||
public_suffix (6.0.2)
|
||||
rake (13.3.0)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.4.2)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.20.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
abbrev
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.6.9
|
||||
@ -1,28 +1,12 @@
|
||||
Astrid was a popular cross-platform productivity service that was [acquired](https://web.archive.org/web/20130811052500/http://blog.astrid.com/blog/2013/05/01/yahoo-acquires-astrid/) and [discontinued](https://techcrunch.com/2013/07/06/astrid-goes-dark-august-5-goodnight-sweet-squid/) in 2013. The source code from Astrid's open source Android app serves as the basis of Tasks.
|
||||
[](https://travis-ci.org/tasks/tasks)
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Get it on Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=org.tasks)
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/packages/org.tasks)
|
||||
[](https://pledgie.com/campaigns/24281)
|
||||
|
||||
Please visit [tasks.org](https://tasks.org) for end user documentation and support
|
||||
<a href='https://play.google.com/store/apps/details?id=org.tasks&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' width="210" height="80"/></a>
|
||||
<a href='https://f-droid.org/repository/browse/?fdid=org.tasks'><img src='https://f-droid.org/wiki/images/f/ff/F-Droid-button_available-on_bigger.png' /></a>
|
||||
[](https://www.amazon.com/gp/product/B00QHGTL7O/ref=mas_pm_tasks_astrid_to_do_list_clone)
|
||||
|
||||
---
|
||||
|
||||
[](https://tasks.org/docs/donate)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=alex@tasks.org)
|
||||
[](https://liberapay.com/tasks/donate)
|
||||
|
||||
[](https://github.com/tasks/tasks/actions/workflows/bundle.yml) [](https://hosted.weblate.org/engage/tasks/?utm_source=widget)
|
||||
|
||||
### Contributing
|
||||
|
||||
Contributions are always welcome! Whether translations, code changes, bug reports, feature requests, or otherwise, your help is appreciated. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
### Communication
|
||||
|
||||
You can submit questions to [GitHub Discussions](https://github.com/tasks/tasks/discussions).
|
||||
|
||||
If you have a suggestion or want to report a bug, please see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
Visit the wiki to
|
||||
* [help with translations](https://github.com/tasks/tasks/wiki/Translations)
|
||||
* [become a beta tester](https://github.com/tasks/tasks/wiki/Beta-Testing)
|
||||
* [get started with development](https://github.com/tasks/tasks/wiki/Getting-Started-with-Development)
|
||||
|
||||
@ -1,579 +0,0 @@
|
||||
[Newer releases](https://github.com/tasks/tasks/blob/main/CHANGELOG.md)
|
||||
|
||||
### 9.7.3 (2020-07-07)
|
||||
|
||||
* Fix Google Task bugs
|
||||
|
||||
### 9.7.2 (2020-06-22)
|
||||
|
||||
* Downgrade Mapbox SDK to remove non-free library (F-Droid only)
|
||||
|
||||
### 9.7.1 (2020-06-19)
|
||||
|
||||
* Fix crash on backup import
|
||||
* Fix CalDAV/EteSync subtask move bug
|
||||
|
||||
### 9.7 (2020-06-12)
|
||||
|
||||
* Added '☰ > Manage lists'
|
||||
* Drag and drop to rearrange the drawer
|
||||
* Tap to edit or delete a list
|
||||
* Display 2 additional snooze options - @rangzen
|
||||
|
||||
### 9.6 (2020-06-06)
|
||||
|
||||
* Add support for offline lists. Offline lists support manual ordering and infinite-depth subtasks
|
||||
* Rename 'My order' to 'Astrid manual sorting' for 'My Tasks', 'Today', and tags
|
||||
* Add '⚙ > Look and feel > Disable sort groups'
|
||||
* Add '⚙ > Look and feel > Open last viewed list'
|
||||
* Add '⚙ > Look and feel > Chips' toggles for subtasks, places, lists, and tags
|
||||
* Add '⚙ > Navigation drawer > Lists'
|
||||
* Add '⚙ > Task defaults > Default list'
|
||||
* Add '⚙ > Task defaults > New tasks on top'
|
||||
* Add '⚙ > Advanced > Astrid manual sorting'
|
||||
* Fix preference reset button
|
||||
|
||||
### 9.5 (2020-06-03)
|
||||
|
||||
* Drag and drop to change subtasks in all list types
|
||||
* Drag and drop to reprioritize or reschedule tasks while sorting by due date
|
||||
or priority
|
||||
* Bug fixes
|
||||
|
||||
### 9.4.1 (2020-06-01)
|
||||
|
||||
* Add 'Tasks settings > Advanced > Improve performance' toggle
|
||||
* Bug fixes
|
||||
|
||||
### 9.4 (2020-05-27)
|
||||
|
||||
* Add collapsible group headers when sorting by due date, priority, created, or modified
|
||||
|
||||
### 9.3.1 (2020-05-26)
|
||||
|
||||
* Fix offline subtasks
|
||||
|
||||
### 9.3 (2020-05-22)
|
||||
|
||||
* Add manual sorting support for CalDAV and EteSync
|
||||
|
||||
### 9.2 (2020-05-13)
|
||||
|
||||
* 'New task' quick settings tile (Android 7+)
|
||||
* Search results match place names and addresses, caldav list names, google task list names, and comments
|
||||
* Fix duplicated search results
|
||||
* Began migrating codebase to Kotlin
|
||||
|
||||
### 9.1 (2020-05-04)
|
||||
|
||||
* 'New task' launcher shortcut (Android 7.1+)
|
||||
* Add option to disable subtask chip on widget
|
||||
|
||||
### 9.0 (2020-05-03)
|
||||
|
||||
* Show What's New after update
|
||||
* Collapsible subtasks enabled by default
|
||||
* 20 new icons
|
||||
* Show subtask chip even if list chips are disabled
|
||||
* Indent subtasks in 'Share' output
|
||||
* Don't trigger location reminders for snoozed or hidden tasks
|
||||
* Minimum supported version is now Android 6.0
|
||||
|
||||
### 8.11 (2020-04-27)
|
||||
|
||||
* Edit existing custom filters
|
||||
* Drag and drop to rearrange filter criteria
|
||||
* Swipe to delete filter criteria
|
||||
* Tap on filter criteria to choose filter operator
|
||||
* Offer additional built-in filters
|
||||
* Add sort by creation time
|
||||
* Choose any day as start of week
|
||||
|
||||
### 8.10 (2020-04-20)
|
||||
|
||||
* New widget features
|
||||
* Menu button to quickly change list
|
||||
* Expand and collapse subtasks
|
||||
* Click on due date to reschedule
|
||||
* Access widget settings from main app preferences
|
||||
* Show description
|
||||
* Show hidden task indicators
|
||||
* New widget settings
|
||||
* Row spacing: default, compact, none
|
||||
* Due date: after title, below title, or hidden
|
||||
* Configure header, row, and footer opacity
|
||||
* Configure footer click behavior
|
||||
* Show full task title
|
||||
* Show full description
|
||||
* Hide dividers
|
||||
* Improve widget touch targets
|
||||
* Expand/collapse Google Task subtasks in 'My order' mode
|
||||
* Fix bug when changing sort order to/from 'My order'
|
||||
* Fix crash when switching to 'My order' list with subtasks disabled
|
||||
|
||||
### 8.9.2 (2020-04-10)
|
||||
|
||||
* Fix 'Add reminder' layout issues
|
||||
* Fix move between EteSync lists
|
||||
* Accept date time changes when dismissing dialog
|
||||
* Improve date time picker behavior in landscape mode
|
||||
|
||||
### 8.9.1 (2020-04-08)
|
||||
|
||||
* Add option to always hide check button
|
||||
* Hide check button for new tasks
|
||||
* Rearrange multi-select buttons
|
||||
* Allow more space for time buttons in date time picker
|
||||
* Fix priority button layout on smaller devices
|
||||
* Fix clicking on hidden task titles
|
||||
* Fix tag picker checkbox tint on Android 4.4
|
||||
* Fix EteSync crash on malformed iCalendar data
|
||||
|
||||
### 8.9 (2020-04-06)
|
||||
|
||||
* Add 'Select all' option to multi-select menu
|
||||
* Add 'Share' to menu and multi-select menu
|
||||
* Display 'Calendar event created' snackbar after creating a calendar event
|
||||
|
||||
### 8.8 (2020-04-01)
|
||||
|
||||
* New bottom sheet due date picker
|
||||
* Shortcuts and calendar displayed together (Android 6+)
|
||||
* Click on due date in task list to reschedule
|
||||
* Option to autoclose due date picker after selecting a date or time
|
||||
* Redesigned title in edit screen
|
||||
* 'Discard' in overflow menu when 'Back button saves task' enabled
|
||||
* Add preference for linkifying edit screen
|
||||
* Updated date and time formatting
|
||||
* Minimum supported version is now Android 4.4
|
||||
* Custom backup/attachment directory requires Android 5+
|
||||
|
||||
### 8.7.1 (2020-03-31)
|
||||
|
||||
* Fix multi-account Google Task synchronization
|
||||
|
||||
### 8.7 (2020-03-19)
|
||||
|
||||
* Places are now lists
|
||||
* Rename a place
|
||||
* Assign an icon and color to a place
|
||||
* Add new navigation drawer settings
|
||||
* Option to remove filters, tags, and places from drawer
|
||||
* Option to hide unused tags and places in drawer
|
||||
|
||||
### 8.6.1 (2020-03-19)
|
||||
|
||||
* Fix crash on startup
|
||||
|
||||
### 8.6 (2020-03-17)
|
||||
|
||||
* Expand and collapse navigation drawer groups
|
||||
|
||||
### 8.5 (2020-03-13)
|
||||
|
||||
* Synchronize locations with CalDAV and EteSync
|
||||
* Fix crash when clearing completed from recently modified filter
|
||||
|
||||
### 8.4 (2020-03-11)
|
||||
|
||||
* New chip configuration options
|
||||
* Outlined or filled
|
||||
* Text and icon, text only, or icon only
|
||||
* Add option to disable color desaturation
|
||||
* Fix EteSync shared lists
|
||||
* Google Task sync requires Android 4.4+
|
||||
|
||||
### 8.3 (2020-03-08)
|
||||
|
||||
* Synchronize CalDAV and EteSync colors
|
||||
* Rename CalDAV and EteSync lists
|
||||
* Update Turkish translations - @emintufan
|
||||
|
||||
### 8.2.1 (2020-03-07)
|
||||
|
||||
* Increase default chip text contrast
|
||||
* New purchase activity
|
||||
* Fix dividers on Android 4.x
|
||||
|
||||
### 8.2 (2020-03-04)
|
||||
|
||||
* Choose your own app and widget colors with a color wheel
|
||||
* Dark theme now free for all
|
||||
* New 'System default' theme
|
||||
* New outlined chip style
|
||||
* Dark theme is now darker
|
||||
* Light theme is now lighter
|
||||
* Desaturate theme colors in dark mode
|
||||
* Improve dialog theming consistency
|
||||
* Bug fixes
|
||||
|
||||
### 8.1 (2020-02-21)
|
||||
|
||||
* Updated app settings screen
|
||||
|
||||
### 8.0.1 (2020-02-16)
|
||||
|
||||
* Fix missing sync settings on fdroid
|
||||
|
||||
### 8.0 (2020-02-12)
|
||||
|
||||
* EteSync support
|
||||
|
||||
### 7.8 (2020-01-24)
|
||||
|
||||
* Android AutoBackup integration
|
||||
|
||||
### 7.7 (2020-01-21)
|
||||
|
||||
* Add support for offline multi-level subtasks
|
||||
* Update Simplified Chinese translations - @sr093906
|
||||
|
||||
### 7.6.1 (2020-01-17)
|
||||
|
||||
* Fix long press in Google Task and CalDAV lists
|
||||
* Fix bug when moving multi-level CalDAV subtasks
|
||||
* Preserve remote VTODO when moving CalDAV tasks
|
||||
* Add Interlingua translations - @softinterlingua
|
||||
|
||||
### 7.6 (2020-01-10)
|
||||
|
||||
* Change tags with multi-select
|
||||
* Fix custom filter crash on deleted tag
|
||||
|
||||
### 7.5 (2020-01-07)
|
||||
|
||||
* New tag picker
|
||||
* Support self-signed SSL certificates
|
||||
|
||||
### 7.4.2 (2019-12-30)
|
||||
|
||||
* Fix Tasker plugin settings
|
||||
|
||||
### 7.4.1 (2019-12-27)
|
||||
|
||||
* Add option to enable subtasks in task list
|
||||
* Performance improvements
|
||||
* Ask Play Services to update security provider
|
||||
* Display custom icons in tag picker
|
||||
* Fix case comparison when sorting navigation drawer
|
||||
|
||||
### 7.4 (2019-12-16)
|
||||
|
||||
* Add Google Task and CalDAV subtasks from the edit screen
|
||||
* 'Recently modified' shows all modifications in past 24 hours
|
||||
* Fix duplicated multi-level subtask count
|
||||
* Increase checkbox touch target
|
||||
* Naturally order lists and filters
|
||||
|
||||
### 7.3.2 (2019-12-12)
|
||||
|
||||
* Fix slow query for subtasks
|
||||
* Fix setting icon on new CalDAV list
|
||||
* Fix clear completed for subtasks
|
||||
* Fix crash when clearing 1000+ tasks
|
||||
|
||||
### 7.3.1 (2019-12-05)
|
||||
|
||||
* Fix crash on missing filter
|
||||
|
||||
### 7.3 (2019-12-03)
|
||||
|
||||
* Expand and collapse subtasks
|
||||
|
||||
### 7.2.2 (2019-12-03)
|
||||
|
||||
* Fix Google Task sorting
|
||||
* Fix crash when deleting 500+ tasks
|
||||
|
||||
### 7.2.1 (2019-11-27)
|
||||
|
||||
* Bug fixes and minor improvements
|
||||
|
||||
### 7.2 (2019-11-25)
|
||||
|
||||
* Display Google Task and CalDAV subtasks in all lists (Android 5+)
|
||||
* Remove completed tasks immediately - @creywood
|
||||
|
||||
### 7.1.2 (2019-11-22)
|
||||
|
||||
* Add CalDAV account setting for repeating tasks
|
||||
* Fix CalDAV repeating tasks
|
||||
* Fix Google Tasks HTTP 400 response
|
||||
|
||||
### 7.1.1 (2019-11-18)
|
||||
|
||||
* Improve subtask query performance
|
||||
* Fix crash when deleting 1000+ CalDAV tasks
|
||||
|
||||
### 7.1 (2019-11-14)
|
||||
|
||||
* Display subtasks on Google Task and CalDAV widgets (Android 5+)
|
||||
* Fix subtasks after backup import
|
||||
* Fix chained subtask completion
|
||||
|
||||
### 7.0 (2019-11-12)
|
||||
|
||||
* Add support for CalDAV subtasks (Android 5+) - @creywood
|
||||
* Display Google subtasks in all sort modes (Android 5+)
|
||||
|
||||
### 6.9.3 (2019-10-31)
|
||||
|
||||
* Fix disappearance of remotely completed recurring Google Tasks
|
||||
* Fix '0 tasks' notification
|
||||
* Limit to 20 active notifications due to change in Android 10
|
||||
|
||||
### 6.9.2 (2019-10-25)
|
||||
|
||||
* Fix bug forcing new Google Tasks to top
|
||||
* Fix bug preventing deleted tasks from being synchronized - @creywood
|
||||
|
||||
### 6.9.1 (2019-10-09)
|
||||
|
||||
* Fix location reminders on Android 10
|
||||
* Fix CalDAV time zone issue
|
||||
|
||||
### 6.9 (2019-09-23)
|
||||
|
||||
* Synchronize tags with CalDAV
|
||||
* Target Android 10
|
||||
* Bug fixes
|
||||
|
||||
### 6.8.1 (2019-08-05)
|
||||
|
||||
* Fix CalDAV filter migration
|
||||
* Fix native date picker crash
|
||||
|
||||
### 6.8 (2019-07-30)
|
||||
|
||||
* Name your own subscription price! Upgrade, downgrade, or cancel at any time
|
||||
* Choose icons for lists (requires [subscription](https://tasks.org/subscribe))
|
||||
* Choose color for custom filters
|
||||
* Performance improvements
|
||||
* Allow duplicate CalDAV list names
|
||||
* Fix duplicate tag name bug
|
||||
|
||||
### 6.7.3 (2019-07-16)
|
||||
|
||||
* Workaround for [list updated time bug](https://issuetracker.google.com/issues/136123247) in Google Tasks API
|
||||
* Fix crash in CalDAV sync
|
||||
|
||||
### 6.7.2 (2019-07-08)
|
||||
|
||||
* Handle 404 errors when creating new Google Tasks
|
||||
* Ignore 404 errors when deleting Google Drive files
|
||||
* Don't report connection errors
|
||||
|
||||
### 6.7.1 (2019-07-05)
|
||||
|
||||
* Add location chip to task list
|
||||
* Reduce chip sizes
|
||||
* Accept 'send to' for more attachment types
|
||||
* Synchronize multiple accounts in parallel
|
||||
* Fix Google Task migration from older versions
|
||||
* Fix corrupted checkbox issue
|
||||
* Fix some RTL issues
|
||||
|
||||
### 6.7 (2019-06-13)
|
||||
|
||||
* Use drag and drop to indent tasks
|
||||
* Add new Google Tasks to top or bottom
|
||||
* Toggle hidden and completed in manually sorted Google Task lists
|
||||
* Rearrange Google Tasks without a network connection
|
||||
* Optional workaround for [custom order bug](https://issuetracker.google.com/issues/132432317) in Google Tasks API
|
||||
* Include subtasks when moving or deleting Google Tasks
|
||||
* Ignore 404 errors when fetching Google Drive folders
|
||||
* Match tags in search results
|
||||
* Fix stuck 'Generating notifications' notification
|
||||
* Don't display sync indicator when there is no network connection
|
||||
* Don't synchronize immediately after every change
|
||||
* Added Estonian translations - Eraser
|
||||
|
||||
### 6.6.4 (2019-05-21)
|
||||
|
||||
* Handle [breaking change](https://issuetracker.google.com/issues/133254108) in Google Tasks API
|
||||
|
||||
### 6.6.3 (2019-05-08)
|
||||
|
||||
* Fix backup import crash
|
||||
* Fix crash when refreshing purchases
|
||||
* Google Tasks synchronization bug fix
|
||||
|
||||
### 6.6.2 (2019-04-22)
|
||||
|
||||
* Backup and restore preferences
|
||||
* Google Task performance improvements
|
||||
* Google Task and Drive support added to F-Droid and Amazon
|
||||
* Add third-party licenses, changelog, and version info
|
||||
* Fix backup import crash
|
||||
* Fix widget bugs
|
||||
|
||||
### 6.6.1 (2019-04-15)
|
||||
|
||||
* Fix crash on devices running Android 5.1 and below
|
||||
* Fix analytics opt-out
|
||||
|
||||
### 6.6 (2019-04-10)
|
||||
|
||||
* New location picker
|
||||
* Choose Mapbox or Google Maps tiles
|
||||
* Choose Mapbox or Google Places search
|
||||
* Google Places search restricted to subscribers due to new Google Maps pricing
|
||||
* Use Mapbox for reverse geocoding
|
||||
* Select from previously used locations
|
||||
* Dark maps
|
||||
* Enable location picker in F-Droid build
|
||||
* Resume support for Amazon App Store
|
||||
* Fix Android Q background warning
|
||||
|
||||
### 6.5.6 (2019-03-27)
|
||||
|
||||
* Fix crash when clearing completed on a manually sorted Google Task list
|
||||
* Update Ukrainian translations - nathalier
|
||||
|
||||
### 6.5.5 (2019-03-14)
|
||||
|
||||
* Bug fixes
|
||||
|
||||
### 6.5.4 (2019-03-11)
|
||||
|
||||
* Fix black screen issue
|
||||
* Fix crash when task not found
|
||||
|
||||
### 6.5.3 (2019-02-19)
|
||||
|
||||
* Fix crash when upgrading from Android 7 to 8+
|
||||
* Improve OneTask interoperability
|
||||
* Performance improvement
|
||||
|
||||
### 6.5.2 (2019-02-11)
|
||||
|
||||
* Bug fixes
|
||||
|
||||
### 6.5.1 (2019-02-10)
|
||||
|
||||
* Bug fixes
|
||||
|
||||
### 6.5 (2019-02-08)
|
||||
|
||||
* Improve notification accuracy
|
||||
* Performance improvements
|
||||
* Bug fixes
|
||||
* Add Tagalog translations - Topol
|
||||
|
||||
### 6.4.1 (2019-01-16)
|
||||
|
||||
* Limit number of active notifications
|
||||
* Limit rate of notifications
|
||||
* Fix Synology Calendar sync issue
|
||||
* Fix exception when external storage is unavailable
|
||||
|
||||
### 6.4 (2019-01-10)
|
||||
|
||||
* Copy backups to Google Drive
|
||||
* Improved search
|
||||
* Use system file picker (Android 4.4+)
|
||||
* Use system directory picker (Android 5.0+)
|
||||
* Accept 'send' and 'send_multiple' actions with images
|
||||
* File attachment bug fixes
|
||||
|
||||
### 6.3.1 (2018-11-07)
|
||||
|
||||
* New location row in task edit screen
|
||||
* Add location departure notifications
|
||||
* Set CalDAV completion percentage and status
|
||||
* Bug fixes
|
||||
|
||||
### 6.2 (2018-10-29)
|
||||
|
||||
* New white theme color
|
||||
* New icons
|
||||
* New list and tag chips
|
||||
* Linkify text when editing tasks
|
||||
* Option to linkify text on task list
|
||||
* Show description on task list
|
||||
* Move due date next to title
|
||||
* Updated hidden task visualization
|
||||
* No longer require contacts permission (Oreo+)
|
||||
* Dropped support for Android 4.0
|
||||
|
||||
### 6.1.3 (2018-10-22)
|
||||
|
||||
* Fix translation error
|
||||
|
||||
### 6.1.2 (2018-10-18)
|
||||
|
||||
* Remove missed call functionality due to Google Play Developer policy change
|
||||
* Fix manual sort issue affecting Samsung Oreo devices
|
||||
* Fix refresh issue affecting Pure Calendar Widget
|
||||
* Fix memory leak
|
||||
* Schedule jobs with WorkManager instead of android-job
|
||||
|
||||
### 6.1.1 (2018-07-20)
|
||||
|
||||
* Fix notification badge issues
|
||||
* Allow non-SSL connections
|
||||
* Allow user-defined certificate authorities
|
||||
|
||||
### 6.1 (2018-06-30)
|
||||
|
||||
* Customize launcher icon
|
||||
* Customize shortcut widget icon and label
|
||||
* Add custom text selection action (Android 6+)
|
||||
* Target Android P
|
||||
* Remove 'Tasks' from notification body
|
||||
* Fix localization issues - @marmo
|
||||
* Fix crash when calendar permissions are revoked
|
||||
* Fix crash when opening task from widget
|
||||
* Fix crash when recording audio note
|
||||
* Fix crash when dismissing dialogs
|
||||
* Fix crash in backup import
|
||||
* Fix crash on invalid URL during CalDAV setup
|
||||
* Fix crash when editing task
|
||||
|
||||
### 6.0.6 (2018-04-28)
|
||||
|
||||
* Fix crash when creating shortcuts on pre-Oreo devices
|
||||
* Fix crash when Google Task or CalDAV list is missing
|
||||
* Downgrade Play Services for compatibility with MicroG
|
||||
|
||||
### 6.0.5 (2018-04-26)
|
||||
|
||||
* Fix crash when deleting 1000+ tasks at once
|
||||
* Fix hidden dates in date picker
|
||||
* Fix crash on bad response from billing client
|
||||
* Report crash when database fails to open
|
||||
|
||||
### 6.0.4 (2018-04-25)
|
||||
|
||||
* Fix crash caused by leftover Google Analytics campaign tracker
|
||||
|
||||
### 6.0.3 (2018-04-25)
|
||||
|
||||
* Fix crash when manually sorting Google Task lists
|
||||
* Fix multi account Google Task sync issue
|
||||
|
||||
### 6.0.2 (2018-04-25)
|
||||
|
||||
* Fix crash caused by missing tag metadata
|
||||
* Fix crash caused by missing Android System WebView
|
||||
* Replace Google Analytics with Firebase Analytics
|
||||
* Add Crashlytics exception reporting
|
||||
|
||||
### 6.0.1 (2018-04-23)
|
||||
|
||||
* Fix crash caused by missing Google Task metadata
|
||||
|
||||
### 6.0 (2018-04-23)
|
||||
|
||||
* Change to [annual subscription](https://tasks.org/subscribe) pricing
|
||||
* [CalDAV synchronization](https://tasks.org/caldav)
|
||||
* Sync with [multiple Google Task accounts](https://tasks.org/docs/google_tasks_intro.html)
|
||||
* Default theme changed to blue
|
||||
* Display Google Task and CalDAV chips on task list
|
||||
* Display sync error icon in navigation drawer
|
||||
* Move tasks between Google Task and CalDAV lists using multi-select
|
||||
* Add "Don't Sync" option when choosing a Google Task or CalDAV list
|
||||
* Add option to restrict background synchronization to unmetered connections
|
||||
* Custom filters with due date criteria no longer set a due time of 23:59/11:59PM
|
||||
* Internal improvements to notification scheduling should reduce notification delays
|
||||
* Fix list animation bug
|
||||
@ -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)
|
||||
@ -0,0 +1,168 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.1'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.0-beta5'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
android {
|
||||
lintOptions {
|
||||
lintConfig file("lint.xml")
|
||||
textOutput 'stdout'
|
||||
textReport true
|
||||
}
|
||||
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.tasks"
|
||||
versionCode 469
|
||||
versionName "5.0.0"
|
||||
targetSdkVersion 26
|
||||
minSdkVersion 15
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions 'store'
|
||||
|
||||
productFlavors {
|
||||
generic {
|
||||
dimension 'store'
|
||||
}
|
||||
googleplay {
|
||||
dimension 'store'
|
||||
}
|
||||
amazon {
|
||||
dimension 'store'
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty('keyAlias') &&
|
||||
project.hasProperty('storeFile') &&
|
||||
project.hasProperty('storePassword') &&
|
||||
project.hasProperty('keyPassword')) {
|
||||
android.signingConfigs.release.keyAlias = keyAlias
|
||||
android.signingConfigs.release.storeFile = file(storeFile)
|
||||
android.signingConfigs.release.storePassword = storePassword
|
||||
android.signingConfigs.release.keyPassword = keyPassword
|
||||
} else {
|
||||
buildTypes.release.signingConfig = null
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
all*.exclude group: 'com.google.guava', module: 'guava-jdk5'
|
||||
all*.exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
||||
}
|
||||
|
||||
final DAGGER_VERSION = '2.9'
|
||||
final BUTTERKNIFE_VERSION = '8.8.1'
|
||||
final GPS_VERSION = '11.2.2'
|
||||
final SUPPORT_VERSION = '26.0.2'
|
||||
final STETHO_VERSION = '1.5.0'
|
||||
final ROOM_VERSION = '1.0.0-alpha9'
|
||||
final TESTING_SUPPORT_VERSION = '1.0.0'
|
||||
|
||||
dependencies {
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
|
||||
compile "com.google.dagger:dagger:${DAGGER_VERSION}"
|
||||
|
||||
compile "android.arch.persistence.room:rxjava2:${ROOM_VERSION}"
|
||||
annotationProcessor "android.arch.persistence.room:compiler:${ROOM_VERSION}"
|
||||
compile "io.reactivex.rxjava2:rxandroid:2.0.1"
|
||||
|
||||
annotationProcessor "com.jakewharton:butterknife-compiler:${BUTTERKNIFE_VERSION}"
|
||||
compile "com.jakewharton:butterknife:${BUTTERKNIFE_VERSION}"
|
||||
|
||||
debugCompile ("com.facebook.stetho:stetho:${STETHO_VERSION}") {
|
||||
exclude group: 'com.google.code.findbugs', module: 'jsr305'
|
||||
}
|
||||
debugCompile "com.facebook.stetho:stetho-timber:${STETHO_VERSION}@aar"
|
||||
//noinspection GradleDependency
|
||||
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
|
||||
debugCompile 'com.android.support:multidex:1.0.2'
|
||||
|
||||
compile 'com.github.rey5137:material:1.2.4'
|
||||
compile 'com.nononsenseapps:filepicker:4.1.0'
|
||||
compile "com.android.support:design:${SUPPORT_VERSION}"
|
||||
compile "com.android.support:support-annotations:${SUPPORT_VERSION}"
|
||||
compile "com.android.support:support-v13:${SUPPORT_VERSION}"
|
||||
compile "com.android.support:cardview-v7:${SUPPORT_VERSION}"
|
||||
compile 'com.jakewharton.timber:timber:4.5.1'
|
||||
compile 'com.jakewharton.threetenabp:threetenabp:1.0.5'
|
||||
//noinspection GradleDependency
|
||||
compile 'com.google.guava:guava:20.0'
|
||||
compile 'com.jakewharton:process-phoenix:2.0.0'
|
||||
compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0'
|
||||
compile 'com.twofortyfouram:android-plugin-api-for-locale:1.0.2'
|
||||
compile 'com.bignerdranch.android:recyclerview-multiselect:0.2'
|
||||
compile ('com.rubiconproject.oss:jchronic:0.2.6') {
|
||||
transitive = false
|
||||
}
|
||||
compile ('org.scala-saddle:google-rfc-2445:20110304') {
|
||||
transitive = false
|
||||
}
|
||||
compile ('com.wdullaer:materialdatetimepicker:3.2.3') {
|
||||
exclude group: 'com.android.support', module: 'support-v4'
|
||||
}
|
||||
compile "me.leolin:ShortcutBadger:1.1.18@aar"
|
||||
|
||||
googleplayCompile "com.google.android.gms:play-services-location:${GPS_VERSION}"
|
||||
googleplayCompile "com.google.android.gms:play-services-analytics:${GPS_VERSION}"
|
||||
googleplayCompile "com.google.android.gms:play-services-auth:${GPS_VERSION}"
|
||||
googleplayCompile "com.google.android.gms:play-services-places:${GPS_VERSION}"
|
||||
googleplayCompile 'com.google.apis:google-api-services-tasks:v1-rev47-1.22.0'
|
||||
googleplayCompile 'com.google.api-client:google-api-client-android:1.22.0'
|
||||
|
||||
amazonCompile "com.google.android.gms:play-services-analytics:${GPS_VERSION}"
|
||||
|
||||
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
|
||||
androidTestAnnotationProcessor "com.jakewharton:butterknife-compiler:${BUTTERKNIFE_VERSION}"
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
androidTestCompile 'com.natpryce:make-it-easy:4.0.1'
|
||||
androidTestCompile "com.android.support.test:runner:${TESTING_SUPPORT_VERSION}"
|
||||
androidTestCompile "com.android.support.test:rules:${TESTING_SUPPORT_VERSION}"
|
||||
androidTestCompile "com.android.support:support-annotations:${SUPPORT_VERSION}"
|
||||
}
|
||||
@ -1,293 +0,0 @@
|
||||
@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.google.firebase.crashlytics")
|
||||
kotlin("android")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
composeCompiler {
|
||||
enableStrongSkippingMode = true
|
||||
}
|
||||
|
||||
android {
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
dataBinding = true
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
lint {
|
||||
lintConfig = file("lint.xml")
|
||||
textOutput = File("stdout")
|
||||
textReport = true
|
||||
}
|
||||
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
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()
|
||||
testInstrumentationRunner = "org.tasks.TestRunner"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val tasksKeyAlias: String? by project
|
||||
val tasksStoreFile: String? by project
|
||||
val tasksStorePassword: String? by project
|
||||
val tasksKeyPassword: String? by project
|
||||
|
||||
keyAlias = tasksKeyAlias
|
||||
storeFile = file(tasksStoreFile ?: "none")
|
||||
storePassword = tasksStorePassword
|
||||
keyPassword = tasksKeyPassword
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
flavorDimensions += listOf("store")
|
||||
|
||||
@Suppress("LocalVariableName")
|
||||
buildTypes {
|
||||
debug {
|
||||
configure<CrashlyticsExtension> {
|
||||
mappingFileUploadEnabled = false
|
||||
}
|
||||
val tasks_mapbox_key_debug: String? by project
|
||||
val tasks_google_key_debug: String? by project
|
||||
val tasks_caldav_url: String? by project
|
||||
resValue("string", "mapbox_key", tasks_mapbox_key_debug ?: "")
|
||||
resValue("string", "google_key", tasks_google_key_debug ?: "")
|
||||
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")
|
||||
}
|
||||
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
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard.pro")
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
create("generic") {
|
||||
dimension = "store"
|
||||
}
|
||||
create("googleplay") {
|
||||
isDefault = true
|
||||
dimension = "store"
|
||||
}
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += setOf("META-INF/*.kotlin_module", "META-INF/INDEX.LIST")
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
managedDevices {
|
||||
localDevices {
|
||||
create("pixel2api30") {
|
||||
device = "Pixel 2"
|
||||
apiLevel = 30
|
||||
systemImageSource = "aosp-atd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace = "org.tasks"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
exclude(group = "org.checkerframework")
|
||||
exclude(group = "com.google.code.findbugs")
|
||||
exclude(group = "com.google.errorprone")
|
||||
exclude(group = "com.google.j2objc")
|
||||
exclude(group = "com.google.http-client", module = "google-http-client-apache-v2")
|
||||
exclude(group = "com.google.http-client", module = "google-http-client-jackson2")
|
||||
}
|
||||
|
||||
val genericImplementation by configurations
|
||||
val googleplayImplementation by configurations
|
||||
|
||||
dependencies {
|
||||
implementation(projects.data)
|
||||
implementation(projects.kmp)
|
||||
implementation(projects.icons)
|
||||
implementation(libs.androidx.navigation)
|
||||
implementation(libs.androidx.adaptive.navigation.android)
|
||||
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")
|
||||
}
|
||||
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.navigation)
|
||||
implementation(libs.androidx.hilt.work)
|
||||
|
||||
implementation(libs.androidx.core.remoteviews)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
implementation(libs.androidx.datastore)
|
||||
implementation(libs.androidx.fragment.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
implementation(libs.androidx.lifecycle.viewmodel)
|
||||
implementation(libs.androidx.room)
|
||||
implementation(libs.androidx.sqlite)
|
||||
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)
|
||||
|
||||
debugImplementation(libs.leakcanary)
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation(libs.kotlin.reflect)
|
||||
|
||||
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.compose.material3.adaptive)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.swiperefreshlayout)
|
||||
implementation(libs.androidx.preference)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.dashclock.api)
|
||||
implementation(libs.locale) {
|
||||
isTransitive = false
|
||||
}
|
||||
implementation(libs.jchronic) {
|
||||
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.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-tooling-preview")
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.coil.video)
|
||||
implementation(libs.coil.svg)
|
||||
implementation(libs.coil.gif)
|
||||
|
||||
implementation(libs.ktor)
|
||||
implementation(libs.ktor.client.logging)
|
||||
implementation(libs.ktor.content.negotiation)
|
||||
implementation(libs.ktor.serialization)
|
||||
|
||||
implementation(libs.accompanist.permissions)
|
||||
|
||||
googleplayImplementation(platform(libs.firebase))
|
||||
googleplayImplementation(libs.firebase.crashlytics)
|
||||
googleplayImplementation(libs.firebase.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(libs.microsoft.authentication) {
|
||||
exclude("com.microsoft.device.display", "display-mask")
|
||||
}
|
||||
googleplayImplementation(projects.wearDatalayer)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "org.tasks.ak",
|
||||
"variantName": "genericRelease",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 130804,
|
||||
"versionName": "14.0.6",
|
||||
"outputFile": "app-generic-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<lint>
|
||||
<issue id="MissingTranslation" severity="ignore"/>
|
||||
<issue id="InconsistentArrays" severity="error"/>
|
||||
<issue id="MissingTranslation" severity="ignore"/>
|
||||
<issue id="VectorRaster" severity="ignore" />
|
||||
|
||||
<issue id="MissingQuantity" severity="ignore"/>
|
||||
<issue id="ImpliedQuantity" severity="ignore"/>
|
||||
|
||||
<issue id="InvalidPackage">
|
||||
<ignore regexp="net.fortuna.ical4j.util.JCacheTimeZoneCache"/>
|
||||
</issue>
|
||||
<issue id="MissingQuantity" severity="ignore"/>
|
||||
<issue id="ImpliedQuantity" severity="ignore"/>
|
||||
</lint>
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
require 'rexml/document'
|
||||
|
||||
STRINGS = {}
|
||||
STRING_ARRAYS = {}
|
||||
PLURALS = {}
|
||||
|
||||
def load(path)
|
||||
file = File.new(path)
|
||||
doc = REXML::Document.new(file)
|
||||
doc.context[:attribute_quote] = :quote
|
||||
doc
|
||||
end
|
||||
|
||||
def get_items(elem)
|
||||
items = []
|
||||
elem.each_element('item') do |item|
|
||||
items << item.text
|
||||
end
|
||||
items
|
||||
end
|
||||
|
||||
def get_plurals(elem)
|
||||
plurals = {}
|
||||
elem.each_element('item') do |item|
|
||||
plurals[item.attributes['quantity']] = item.text
|
||||
end
|
||||
plurals
|
||||
end
|
||||
|
||||
def index_elements(doc)
|
||||
doc.elements['resources'].each_element('string') { |elem| STRINGS[elem.attributes['name']] = elem.text }
|
||||
doc.elements['resources'].each_element('string-array') { |elem| STRING_ARRAYS[elem.attributes['name']] = get_items(elem) }
|
||||
doc.elements['resources'].each_element('plurals') { |elem| PLURALS[elem.attributes['name']] = get_plurals(elem) }
|
||||
end
|
||||
|
||||
def find_duplicate_plurals(doc)
|
||||
dups = []
|
||||
doc.elements['resources'].each_element('plurals') do |elem|
|
||||
string_name = elem.attributes['name']
|
||||
plurals = get_plurals(elem)
|
||||
dups << string_name if plurals.eql? PLURALS[string_name]
|
||||
end
|
||||
dups
|
||||
end
|
||||
|
||||
def find_duplicate_string_arrays(doc)
|
||||
dups = []
|
||||
doc.elements['resources'].each_element('string-array') do |elem|
|
||||
string_name = elem.attributes['name']
|
||||
items = get_items(elem)
|
||||
dups << string_name if items.eql? STRING_ARRAYS[string_name]
|
||||
end
|
||||
dups
|
||||
end
|
||||
|
||||
def find_duplicate_strings(doc)
|
||||
dups = []
|
||||
doc.elements['resources'].each_element('string') do |elem|
|
||||
string_name = elem.attributes['name']
|
||||
dups << string_name if elem.text.eql? STRINGS[string_name]
|
||||
end
|
||||
dups
|
||||
end
|
||||
|
||||
def remove_items(doc, type, names)
|
||||
names.each { |name| doc.elements.delete("resources/#{type}[@name='#{name}']") }
|
||||
end
|
||||
|
||||
def clean(path)
|
||||
doc = load(path)
|
||||
remove_items(doc, 'string', find_duplicate_strings(doc))
|
||||
remove_items(doc, 'string-array', find_duplicate_string_arrays(doc))
|
||||
remove_items(doc, 'plurals', find_duplicate_plurals(doc))
|
||||
prolog, *tail = doc.to_s.split("\n").reject { |x| x.strip.eql? "" }
|
||||
File.open(path, 'w') do |f|
|
||||
f.puts prolog
|
||||
f.puts "<!-- ************************************************************** -->"
|
||||
f.puts "<!-- ********* THIS FILE IS GENERATED BY GETLOCALIZATION ********** -->"
|
||||
f.puts "<!-- ******** http://www.getlocalization.com/tasks_android ******** -->"
|
||||
f.puts "<!-- ******************* DO NOT MODIFY MANUALLY ******************* -->"
|
||||
f.puts "<!-- ************************************************************** -->"
|
||||
f.puts "<!--suppress AndroidLintTypographyEllipsis,AndroidLintTypographyDashes-->"
|
||||
f.print tail.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def remove_untranslated_strings(*string_files)
|
||||
Dir.glob("src/main/res/values/strings*.xml").each { |path| index_elements(load(path)) }
|
||||
string_files.each { |path| clean path }
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
lang = ARGV[0]
|
||||
remove_untranslated_strings("src/main/res/values-#{lang}/strings.xml")
|
||||
end
|
||||
@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
$:.unshift File.dirname(__FILE__)
|
||||
require 'clean_translations'
|
||||
|
||||
# Script for invoking the GetLocalization tools
|
||||
# IMPORTANT: Right now, must be invoked from the project's root directory.
|
||||
# Usage: ./bin/getloc.rb [cmd] [lang]
|
||||
# cmd: 'export' or 'import'
|
||||
|
||||
# lang: Language code or 'master'
|
||||
|
||||
PROJECT_NAME='tasks_android'
|
||||
LANGUAGE_MAP = {
|
||||
"el" => "grk",
|
||||
"sk" => "sk-SK",
|
||||
"hu" => "hu-HU",
|
||||
"fa" => "pes-IR"
|
||||
}
|
||||
|
||||
# Converts astrid language codes to GetLocalization language codes (which don't use -r)
|
||||
def astrid_code_to_getloc_code(lang)
|
||||
(LANGUAGE_MAP[lang] || lang).sub("-r", "-")
|
||||
end
|
||||
|
||||
# Inverse of the above function
|
||||
def getloc_code_to_astrid_code(lang)
|
||||
(LANGUAGE_MAP.invert[lang] || lang).sub("-", "-r")
|
||||
end
|
||||
|
||||
# Uploads files for the specified language to GetLocalization
|
||||
# tmp_files (Array): temporary strings files to use
|
||||
# lang (String): language code
|
||||
# src_files_block (lambda): Block for computing the source file list from the language code
|
||||
def export(tmp_files, lang, src_files_block)
|
||||
src_files = src_files_block.call(lang)
|
||||
for i in 0...tmp_files.length
|
||||
%x(cp #{src_files[i]} #{tmp_files[i]}) if src_files[i] != tmp_files[i]
|
||||
end
|
||||
|
||||
tmp_files.each do |f|
|
||||
%x(gsed -i "s/\\\\\\'/'/g" #{f})
|
||||
end
|
||||
|
||||
if lang == "master"
|
||||
tmp_files.each do |f|
|
||||
puts "Updating master file #{f}"
|
||||
%x(curl --form file=@#{f} --user "#{@user}:#{@password}" https://api.getlocalization.com/#{PROJECT_NAME}/api/update-master/)
|
||||
end
|
||||
else
|
||||
raise "dont do this if you already exported your translations"
|
||||
lang_tmp = astrid_code_to_getloc_code(lang)
|
||||
tmp_files.each do |f|
|
||||
puts "Updating language file #{f}"
|
||||
name = File.basename(f)
|
||||
%x(curl --form file=@#{f} --user "#{@user}:#{@password}" https://api.getlocalization.com/#{PROJECT_NAME}/api/translations/file/#{name}/#{lang_tmp}/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Downloads and imports files for the specified language
|
||||
# tmp_files (Array): temporary strings files to use
|
||||
# lang (String): language code
|
||||
# dst_files_block (lambda): Block for computing the destination files list from the language code
|
||||
def import(tmp_files, lang, dst_files_block)
|
||||
if lang == "master"
|
||||
tmp_dir = File.dirname(tmp_files[0])
|
||||
tmp_all = File.join(tmp_dir, "all.zip")
|
||||
tmp_all_dir = File.join(tmp_dir, "all")
|
||||
|
||||
%x(curl --user "#{@user}:#{@password}" https://api.getlocalization.com/#{PROJECT_NAME}/api/translations/zip/ -o #{tmp_all})
|
||||
%x(mkdir #{tmp_all_dir})
|
||||
%x(tar xzf #{tmp_all} -C #{tmp_all_dir})
|
||||
|
||||
# Get all translations
|
||||
Dir.foreach(tmp_all_dir) do |l|
|
||||
if (l != "." && l != "..")
|
||||
lang_local = getloc_code_to_astrid_code(l)
|
||||
dst_files = dst_files_block.call(lang_local)
|
||||
|
||||
for i in 0...tmp_files.length
|
||||
file = File.join(tmp_all_dir, l, File.basename(tmp_files[i]))
|
||||
%x(gsed -i "s/\\([^\\\\\\]\\)'/\\1\\\\\\'/g" #{file})
|
||||
puts "Moving #{file} to #{dst_files[i]}"
|
||||
%x(mv #{file} #{dst_files[i]})
|
||||
end
|
||||
end
|
||||
end
|
||||
%x(rm -rf #{tmp_all_dir})
|
||||
%x(rm #{tmp_all})
|
||||
else
|
||||
lang_tmp = astrid_code_to_getloc_code(lang)
|
||||
dst_files = dst_files_block.call(lang)
|
||||
for i in 0...tmp_files.length
|
||||
name = File.basename(tmp_files[i])
|
||||
%x(curl --user "#{@user}:#{@password}" https://api.getlocalization.com/#{PROJECT_NAME}/api/translations/file/#{name}/#{lang_tmp}/ -o #{tmp_files[i]})
|
||||
%x(gsed -i "s/\\([^\\\\\\]\\)'/\\1\\\\\\'/g" #{tmp_files[i]})
|
||||
`gsed -i '/\s*<!--.*-->\s*$/d' #{tmp_files[i]}` # strip comments
|
||||
puts "Moving #{tmp_files[i]} to #{dst_files[i]}"
|
||||
%x(mv #{tmp_files[i]} #{dst_files[i]})
|
||||
end
|
||||
remove_untranslated_strings(*dst_files)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Android
|
||||
def self.tmp_files
|
||||
FileUtils.mkdir_p "translations"
|
||||
["translations/strings.xml"]
|
||||
end
|
||||
|
||||
def self.src_files(cmd, type)
|
||||
if cmd == :export && type == "master"
|
||||
lambda { |l| ["src/main/res/values/strings.xml"] }
|
||||
else
|
||||
lambda { |l| ["src/main/res/values-#{l}/strings.xml"] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Main function for invoking the GetLocalization tools
|
||||
# cmd (String): Command to invoke. Must be 'import' or 'export'
|
||||
# lang (String): Language code. Can also be 'master' to specify master files for export or all languages for import.
|
||||
def getloc(cmd, languages)
|
||||
cmd = cmd.to_sym
|
||||
|
||||
raise "must set GETLOC_USER and GETLOC_PASS environment variables" if ENV['GETLOC_USER'].nil? or ENV['GETLOC_PASS'].nil?
|
||||
@user = ENV['GETLOC_USER']
|
||||
@password = ENV['GETLOC_PASS']
|
||||
platform_class = Android
|
||||
languages.split(',').each do |lang|
|
||||
case cmd
|
||||
when :export
|
||||
puts "Exporting #{lang} files"
|
||||
export(platform_class.tmp_files, lang, platform_class.src_files(cmd, lang))
|
||||
when :import
|
||||
puts "Importing #{lang} files"
|
||||
import(platform_class.tmp_files, lang, platform_class.src_files(cmd, lang))
|
||||
else
|
||||
puts "Command #{cmd} not recognized. Should be one of 'export' or 'import'."
|
||||
return
|
||||
end
|
||||
|
||||
platform_class.tmp_files.each do |f|
|
||||
%x(rm -f #{f})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
getloc(*ARGV)
|
||||
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.tasks">
|
||||
|
||||
<application>
|
||||
<!-- Google Analytics -->
|
||||
|
||||
<receiver
|
||||
android:name="com.google.android.gms.analytics.AnalyticsReceiver"
|
||||
android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.analytics.ANALYTICS_DISPATCH" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name="com.google.android.gms.analytics.AnalyticsService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name="com.google.android.gms.analytics.CampaignTrackingReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.vending.INSTALL_REFERRER" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name="com.google.android.gms.analytics.CampaignTrackingService" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,23 @@
|
||||
package com.todoroo.astrid.gtasks;
|
||||
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class GtasksFilterExposer {
|
||||
@Inject
|
||||
public GtasksFilterExposer() {
|
||||
|
||||
}
|
||||
|
||||
public List<Filter> getFilters() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public Filter getFilter(long aLong) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.todoroo.astrid.gtasks;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class GtasksListService {
|
||||
|
||||
@Inject
|
||||
public GtasksListService() {
|
||||
|
||||
}
|
||||
|
||||
public List<GtasksList> getLists() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public GtasksList getList(long storeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<GtasksList> getSortedGtasksList() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.todoroo.astrid.gtasks;
|
||||
|
||||
import com.todoroo.astrid.activity.TaskListFragment;
|
||||
import com.todoroo.astrid.api.GtasksFilter;
|
||||
|
||||
import org.tasks.tasklist.GtasksListFragment;
|
||||
|
||||
public class GtasksSubtaskListFragment extends GtasksListFragment {
|
||||
public static TaskListFragment newGtasksSubtaskListFragment(GtasksFilter gtasksFilter, GtasksList list) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.tasks;
|
||||
|
||||
import android.accounts.Account;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class AccountManager {
|
||||
|
||||
@Inject
|
||||
public AccountManager() {
|
||||
}
|
||||
|
||||
public List<String> getAccounts() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public Account getAccount(String userName) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package org.tasks;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class FlavorSetup {
|
||||
@Inject
|
||||
public FlavorSetup() {
|
||||
|
||||
}
|
||||
|
||||
public void setup() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package org.tasks.activities;
|
||||
|
||||
public class GoogleTaskListSettingsActivity {
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package org.tasks.analytics;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.gms.analytics.ExceptionParser;
|
||||
import com.google.android.gms.analytics.ExceptionReporter;
|
||||
import com.google.android.gms.analytics.GoogleAnalytics;
|
||||
import com.google.android.gms.analytics.HitBuilders;
|
||||
import com.google.android.gms.analytics.StandardExceptionParser;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import org.tasks.BuildConfig;
|
||||
import org.tasks.R;
|
||||
import org.tasks.injection.ApplicationScope;
|
||||
import org.tasks.injection.ForApplication;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@ApplicationScope
|
||||
public class Tracker {
|
||||
|
||||
private final GoogleAnalytics analytics;
|
||||
private final com.google.android.gms.analytics.Tracker tracker;
|
||||
private final ExceptionParser exceptionParser;
|
||||
private final Context context;
|
||||
|
||||
@Inject
|
||||
public Tracker(@ForApplication Context context) {
|
||||
this.context = context;
|
||||
analytics = GoogleAnalytics.getInstance(context);
|
||||
tracker = analytics.newTracker(R.xml.google_analytics);
|
||||
tracker.setAppVersion(Integer.toString(BuildConfig.VERSION_CODE));
|
||||
final StandardExceptionParser standardExceptionParser = new StandardExceptionParser(context, null);
|
||||
exceptionParser = (thread, throwable) -> {
|
||||
StringBuilder stack = new StringBuilder()
|
||||
.append(standardExceptionParser.getDescription(thread, throwable))
|
||||
.append("\n")
|
||||
.append(throwable.getClass().getName())
|
||||
.append("\n");
|
||||
for (StackTraceElement element : throwable.getStackTrace()) {
|
||||
stack.append(element.toString())
|
||||
.append("\n");
|
||||
}
|
||||
return stack.toString();
|
||||
};
|
||||
ExceptionReporter reporter = new ExceptionReporter(
|
||||
tracker,
|
||||
Thread.getDefaultUncaughtExceptionHandler(),
|
||||
context);
|
||||
reporter.setExceptionParser(exceptionParser);
|
||||
Thread.setDefaultUncaughtExceptionHandler(reporter);
|
||||
}
|
||||
|
||||
public void setTrackingEnabled(boolean enabled) {
|
||||
analytics.setAppOptOut(!enabled);
|
||||
}
|
||||
|
||||
public void reportException(Throwable t) {
|
||||
reportException(Thread.currentThread(), t);
|
||||
}
|
||||
|
||||
public void reportException(Thread thread, Throwable t) {
|
||||
Timber.e(t, t.getMessage());
|
||||
tracker.send(new HitBuilders.ExceptionBuilder()
|
||||
.setDescription(exceptionParser.getDescription(thread.getName(), t))
|
||||
.setFatal(false)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void reportEvent(Tracking.Events event) {
|
||||
reportEvent(event, null);
|
||||
}
|
||||
|
||||
public void reportEvent(Tracking.Events event, String label) {
|
||||
reportEvent(event, event.action, label);
|
||||
}
|
||||
|
||||
public void reportEvent(Tracking.Events event, int action, String label) {
|
||||
reportEvent(event, context.getString(action), label);
|
||||
}
|
||||
|
||||
public void reportEvent(Tracking.Events event, String action, String label) {
|
||||
reportEvent(event.category, action, label);
|
||||
}
|
||||
|
||||
private void reportEvent(int category, String action, String label) {
|
||||
HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder()
|
||||
.setCategory(context.getString(category))
|
||||
.setAction(action);
|
||||
if (!Strings.isNullOrEmpty(label)) {
|
||||
eventBuilder.setLabel(label);
|
||||
}
|
||||
tracker.send(eventBuilder.build());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.tasks.billing;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class PurchaseHelper {
|
||||
|
||||
@Inject
|
||||
public PurchaseHelper() {
|
||||
}
|
||||
|
||||
public boolean purchase(final Activity activity,
|
||||
final String sku, final String pref,
|
||||
final int requestCode, final PurchaseHelperCallback callback) {
|
||||
callback.purchaseCompleted(false, sku);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void handleActivityResult(PurchaseHelperCallback callback, int requestCode, int resultCode, Intent data) {
|
||||
|
||||
}
|
||||
|
||||
public void disposeIabHelper() {
|
||||
|
||||
}
|
||||
|
||||
public void consumePurchases() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.tasks.gtasks;
|
||||
|
||||
import com.todoroo.astrid.activity.TaskListFragment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SyncAdapterHelper {
|
||||
@Inject
|
||||
public SyncAdapterHelper() {
|
||||
|
||||
}
|
||||
|
||||
public boolean shouldShowBackgroundSyncWarning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void checkPlayServices(TaskListFragment taskListFragment) {
|
||||
|
||||
}
|
||||
|
||||
public boolean initiateManualSync() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void requestSynchronization() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
package org.tasks.injection;
|
||||
|
||||
import com.todoroo.astrid.activity.BeastModePreferences;
|
||||
import com.todoroo.astrid.activity.ShareLinkActivity;
|
||||
import com.todoroo.astrid.activity.TaskListActivity;
|
||||
import com.todoroo.astrid.core.CustomFilterActivity;
|
||||
import com.todoroo.astrid.core.DefaultsPreferences;
|
||||
import com.todoroo.astrid.core.OldTaskPreferences;
|
||||
import com.todoroo.astrid.files.AACRecordingActivity;
|
||||
import com.todoroo.astrid.gcal.CalendarReminderActivity;
|
||||
import com.todoroo.astrid.reminders.ReminderPreferences;
|
||||
|
||||
import org.tasks.activities.AddAttachmentActivity;
|
||||
import org.tasks.activities.CalendarSelectionActivity;
|
||||
import org.tasks.activities.CameraActivity;
|
||||
import org.tasks.activities.ColorPickerActivity;
|
||||
import org.tasks.activities.DateAndTimePickerActivity;
|
||||
import org.tasks.activities.DatePickerActivity;
|
||||
import org.tasks.activities.FilterSelectionActivity;
|
||||
import org.tasks.activities.FilterSettingsActivity;
|
||||
import org.tasks.activities.GoogleTaskListSettingsActivity;
|
||||
import org.tasks.activities.TagSettingsActivity;
|
||||
import org.tasks.activities.TimePickerActivity;
|
||||
import org.tasks.dashclock.DashClockSettings;
|
||||
import org.tasks.files.FileExplore;
|
||||
import org.tasks.files.MyFilePickerActivity;
|
||||
import org.tasks.locale.ui.activity.TaskerSettingsActivity;
|
||||
import org.tasks.preferences.AppearancePreferences;
|
||||
import org.tasks.preferences.BasicPreferences;
|
||||
import org.tasks.preferences.DateTimePreferences;
|
||||
import org.tasks.preferences.HelpAndFeedbackActivity;
|
||||
import org.tasks.preferences.MiscellaneousPreferences;
|
||||
import org.tasks.reminders.MissedCallActivity;
|
||||
import org.tasks.reminders.NotificationActivity;
|
||||
import org.tasks.reminders.SnoozeActivity;
|
||||
import org.tasks.themes.Theme;
|
||||
import org.tasks.voice.VoiceCommandActivity;
|
||||
import org.tasks.widget.ShortcutConfigActivity;
|
||||
import org.tasks.widget.WidgetConfigActivity;
|
||||
|
||||
import dagger.Subcomponent;
|
||||
|
||||
@ActivityScope
|
||||
@Subcomponent(modules = ActivityModule.class)
|
||||
public interface ActivityComponent {
|
||||
Theme getTheme();
|
||||
|
||||
FragmentComponent plus(FragmentModule module);
|
||||
|
||||
DialogFragmentComponent plus(DialogFragmentModule dialogFragmentModule);
|
||||
|
||||
NativeDialogFragmentComponent plus(NativeDialogFragmentModule nativeDialogFragmentModule);
|
||||
|
||||
void inject(TaskerSettingsActivity taskerSettingsActivity);
|
||||
|
||||
void inject(DashClockSettings dashClockSettings);
|
||||
|
||||
void inject(AACRecordingActivity aacRecordingActivity);
|
||||
|
||||
void inject(CustomFilterActivity customFilterActivity);
|
||||
|
||||
void inject(CalendarReminderActivity calendarReminderActivity);
|
||||
|
||||
void inject(FilterSettingsActivity filterSettingsActivity);
|
||||
|
||||
void inject(TagSettingsActivity tagSettingsActivity);
|
||||
|
||||
void inject(ShareLinkActivity shareLinkActivity);
|
||||
|
||||
void inject(TaskListActivity taskListActivity);
|
||||
|
||||
void inject(BeastModePreferences beastModePreferences);
|
||||
|
||||
void inject(NotificationActivity notificationActivity);
|
||||
|
||||
void inject(SnoozeActivity snoozeActivity);
|
||||
|
||||
void inject(MissedCallActivity missedCallActivity);
|
||||
|
||||
void inject(FileExplore fileExplore);
|
||||
|
||||
void inject(CalendarSelectionActivity calendarSelectionActivity);
|
||||
|
||||
void inject(FilterSelectionActivity filterSelectionActivity);
|
||||
|
||||
void inject(DateAndTimePickerActivity dateAndTimePickerActivity);
|
||||
|
||||
void inject(AddAttachmentActivity addAttachmentActivity);
|
||||
|
||||
void inject(DatePickerActivity datePickerActivity);
|
||||
|
||||
void inject(CameraActivity cameraActivity);
|
||||
|
||||
void inject(TimePickerActivity timePickerActivity);
|
||||
|
||||
void inject(VoiceCommandActivity voiceCommandActivity);
|
||||
|
||||
void inject(ReminderPreferences reminderPreferences);
|
||||
|
||||
void inject(WidgetConfigActivity widgetConfigActivity);
|
||||
|
||||
void inject(OldTaskPreferences oldTaskPreferences);
|
||||
|
||||
void inject(DefaultsPreferences defaultsPreferences);
|
||||
|
||||
void inject(ShortcutConfigActivity shortcutConfigActivity);
|
||||
|
||||
void inject(MiscellaneousPreferences miscellaneousPreferences);
|
||||
|
||||
void inject(HelpAndFeedbackActivity helpAndFeedbackActivity);
|
||||
|
||||
void inject(DateTimePreferences dateTimePreferences);
|
||||
|
||||
void inject(AppearancePreferences appearancePreferences);
|
||||
|
||||
void inject(MyFilePickerActivity myFilePickerActivity);
|
||||
|
||||
void inject(ColorPickerActivity colorPickerActivity);
|
||||
|
||||
void inject(BasicPreferences basicPreferences);
|
||||
|
||||
void inject(GoogleTaskListSettingsActivity googleTaskListSettingsActivity);
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.tasks.injection;
|
||||
|
||||
import org.tasks.Tasks;
|
||||
import org.tasks.dashclock.DashClockExtension;
|
||||
import org.tasks.widget.ScrollableWidgetUpdateService;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@ApplicationScope
|
||||
@Component(modules = ApplicationModule.class)
|
||||
public interface ApplicationComponent {
|
||||
void inject(DashClockExtension dashClockExtension);
|
||||
|
||||
void inject(Tasks tasks);
|
||||
|
||||
void inject(ScrollableWidgetUpdateService scrollableWidgetUpdateService);
|
||||
|
||||
ActivityComponent plus(ActivityModule module);
|
||||
|
||||
BroadcastComponent plus(BroadcastModule module);
|
||||
|
||||
IntentServiceComponent plus(IntentServiceModule module);
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package org.tasks.injection;
|
||||
|
||||
import org.tasks.activities.CalendarSelectionDialog;
|
||||
import org.tasks.dialogs.AddAttachmentDialog;
|
||||
import org.tasks.dialogs.ColorPickerDialog;
|
||||
import org.tasks.dialogs.RecordAudioDialog;
|
||||
import org.tasks.dialogs.SortDialog;
|
||||
import org.tasks.reminders.MissedCallDialog;
|
||||
import org.tasks.reminders.NotificationDialog;
|
||||
import org.tasks.reminders.SnoozeDialog;
|
||||
|
||||
import dagger.Subcomponent;
|
||||
|
||||
@Subcomponent(modules = DialogFragmentModule.class)
|
||||
public interface DialogFragmentComponent {
|
||||
void inject(NotificationDialog notificationDialog);
|
||||
|
||||
void inject(MissedCallDialog missedCallDialog);
|
||||
|
||||
void inject(CalendarSelectionDialog calendarSelectionDialog);
|
||||
|
||||
void inject(AddAttachmentDialog addAttachmentDialog);
|
||||
|
||||
void inject(SnoozeDialog snoozeDialog);
|
||||
|
||||
void inject(SortDialog sortDialog);
|
||||
|
||||
void inject(ColorPickerDialog colorPickerDialog);
|
||||
|
||||
void inject(RecordAudioDialog recordAudioDialog);
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package org.tasks.injection;
|
||||
|
||||
import org.tasks.dialogs.DonationDialog;
|
||||
import org.tasks.dialogs.ExportTasksDialog;
|
||||
import org.tasks.dialogs.ImportTasksDialog;
|
||||
import org.tasks.dialogs.NativeDatePickerDialog;
|
||||
import org.tasks.dialogs.NativeTimePickerDialog;
|
||||
import org.tasks.dialogs.SeekBarDialog;
|
||||
import org.tasks.locale.LocalePickerDialog;
|
||||
|
||||
import dagger.Subcomponent;
|
||||
|
||||
@Subcomponent(modules = NativeDialogFragmentModule.class)
|
||||
public interface NativeDialogFragmentComponent {
|
||||
void inject(LocalePickerDialog localePickerDialog);
|
||||
|
||||
void inject(NativeDatePickerDialog nativeDatePickerDialog);
|
||||
|
||||
void inject(NativeTimePickerDialog nativeTimePickerDialog);
|
||||
|
||||
void inject(SeekBarDialog seekBarDialog);
|
||||
|
||||
void inject(ExportTasksDialog exportTasksDialog);
|
||||
|
||||
void inject(ImportTasksDialog importTasksDialog);
|
||||
|
||||
void inject(DonationDialog donationDialog);
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
public class GeofenceApi {
|
||||
|
||||
@Inject
|
||||
public GeofenceApi() {
|
||||
|
||||
}
|
||||
|
||||
public void register(List<Geofence> activeGeofences) {
|
||||
|
||||
}
|
||||
|
||||
public void cancel(Geofence geofence) {
|
||||
|
||||
}
|
||||
|
||||
public void cancel(List<Geofence> geofences) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package org.tasks.location;
|
||||
|
||||
public class GeofenceTransitionsIntentService {
|
||||
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.tasks.location;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
public class PlacePicker {
|
||||
public static Intent getIntent(Activity activity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Geofence getPlace(Context context, Intent data, Preferences preferences) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package org.tasks.receivers;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.tasks.injection.BroadcastComponent;
|
||||
import org.tasks.injection.InjectingBroadcastReceiver;
|
||||
|
||||
public class PushReceiver extends InjectingBroadcastReceiver {
|
||||
|
||||
public static void broadcast(Context context, Task task, ContentValues values) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(BroadcastComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package org.tasks.tasklist;
|
||||
|
||||
import com.todoroo.astrid.activity.TaskListFragment;
|
||||
import com.todoroo.astrid.api.GtasksFilter;
|
||||
import com.todoroo.astrid.gtasks.GtasksList;
|
||||
|
||||
public class GtasksListFragment extends TaskListFragment {
|
||||
public static TaskListFragment newGtasksListFragment(GtasksFilter gtasksFilter, GtasksList list) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package org.tasks.ui;
|
||||
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.gtasks.GtasksList;
|
||||
|
||||
import org.tasks.R;
|
||||
import org.tasks.injection.FragmentComponent;
|
||||
|
||||
public class GoogleTaskListFragment extends TaskEditControlFragment {
|
||||
public static final int TAG = R.string.TEA_ctrl_google_task_list;
|
||||
|
||||
@Override
|
||||
protected int getLayout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getIcon() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int controlId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(boolean isNewTask, Task task) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Task task) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(FragmentComponent component) {
|
||||
|
||||
}
|
||||
|
||||
public void setList(GtasksList list) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sku_themes">themes</string>
|
||||
</resources>
|
||||
@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.test;
|
||||
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.andlib.data.Callback;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests translations for consistency with the default values. You must
|
||||
* extend this class and create it with your own values for strings
|
||||
* and arrays.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TranslationTests {
|
||||
|
||||
/**
|
||||
* Loop through each locale and call runnable
|
||||
*/
|
||||
private void forEachLocale(Callback<Resources> callback) {
|
||||
Locale[] locales = Locale.getAvailableLocales();
|
||||
for(Locale locale : locales) {
|
||||
callback.apply(getResourcesForLocale(locale));
|
||||
}
|
||||
}
|
||||
|
||||
private Resources getResourcesForLocale(Locale locale) {
|
||||
Resources resources = getTargetContext().getResources();
|
||||
Configuration configuration = new Configuration(resources.getConfiguration());
|
||||
configuration.locale = locale;
|
||||
return new Resources(resources.getAssets(), resources.getDisplayMetrics(), configuration);
|
||||
}
|
||||
|
||||
private static final class FormatStringData {
|
||||
private static final char[] scratch = new char[10];
|
||||
|
||||
/** format characters */
|
||||
public final char[] characters;
|
||||
|
||||
/** the original string */
|
||||
public final String string;
|
||||
|
||||
public FormatStringData(String string) {
|
||||
this.string = string;
|
||||
|
||||
int pos = -1;
|
||||
int count = 0;
|
||||
while(true) {
|
||||
pos = string.indexOf('%', ++pos);
|
||||
if(pos++ == -1)
|
||||
break;
|
||||
if(pos >= string.length())
|
||||
scratch[count++] = '\0';
|
||||
else
|
||||
scratch[count++] = string.charAt(pos);
|
||||
}
|
||||
characters = new char[count];
|
||||
for(int i = 0; i < count; i++) {
|
||||
characters[i] = scratch[i];
|
||||
}
|
||||
}
|
||||
|
||||
/** test that the characters match */
|
||||
public boolean matches(FormatStringData other) {
|
||||
if(characters.length != other.characters.length)
|
||||
return false;
|
||||
outer: for(int i = 0; i < characters.length; i++) {
|
||||
if(Character.isDigit(characters[i])) {
|
||||
for(int j = 0; j < other.characters.length; j++)
|
||||
if(characters[i] == other.characters[j])
|
||||
break outer;
|
||||
return false;
|
||||
} else if(characters[i] != other.characters[i])
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder value = new StringBuilder("[");
|
||||
for(int i = 0; i < characters.length; i++) {
|
||||
value.append(characters[i]);
|
||||
if(i < characters.length - 1)
|
||||
value.append(',');
|
||||
}
|
||||
value.append("]: '").append(string).append('\'');
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal test of format string parser
|
||||
*/
|
||||
@Test
|
||||
public void testFormatStringParser() {
|
||||
String s = "abc";
|
||||
FormatStringData data = new FormatStringData(s);
|
||||
assertEquals(s, data.string);
|
||||
assertEquals(0, data.characters.length);
|
||||
|
||||
s = "abc %s def";
|
||||
data = new FormatStringData(s);
|
||||
assertEquals(1, data.characters.length);
|
||||
assertEquals('s', data.characters[0]);
|
||||
|
||||
s = "abc %%s def %d";
|
||||
data = new FormatStringData(s);
|
||||
assertEquals(2, data.characters.length);
|
||||
assertEquals('%', data.characters[0]);
|
||||
assertEquals('d', data.characters[1]);
|
||||
assertTrue(data.toString(), data.toString().contains("[%"));
|
||||
assertTrue(data.toString(), data.toString().contains("d]"));
|
||||
assertTrue(data.toString(), data.toString().contains(s));
|
||||
assertTrue(data.matches(new FormatStringData("espanol %% und %d si")));
|
||||
assertFalse(data.matches(new FormatStringData("ingles %d ja %% pon")));
|
||||
|
||||
s = "% abc %";
|
||||
data = new FormatStringData(s);
|
||||
assertEquals(2, data.characters.length);
|
||||
assertEquals(' ', data.characters[0]);
|
||||
assertEquals('\0', data.characters[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the format specifiers in translations match exactly the
|
||||
* translations in the default text
|
||||
*/
|
||||
@Test
|
||||
public void testFormatStringsMatch() throws Exception {
|
||||
final Resources resources = getTargetContext().getResources();
|
||||
final int[] strings = getResourceIds(R.string.class);
|
||||
final FormatStringData[] formatStrings = new FormatStringData[strings.length];
|
||||
|
||||
final StringBuilder failures = new StringBuilder();
|
||||
|
||||
for(int i = 0; i < strings.length; i++) {
|
||||
try {
|
||||
String string = resources.getString(strings[i]);
|
||||
formatStrings[i] = new FormatStringData(string);
|
||||
} catch (Exception e) {
|
||||
String name = resources.getResourceName(strings[i]);
|
||||
failures.append(String.format("error opening %s: %s\n",
|
||||
name, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
forEachLocale(r -> {
|
||||
Locale locale = r.getConfiguration().locale;
|
||||
for(int i = 0; i < strings.length; i++) {
|
||||
try {
|
||||
switch(strings[i]) {
|
||||
case R.string.abc_shareactionprovider_share_with_application:
|
||||
continue;
|
||||
}
|
||||
String string = r.getString(strings[i]);
|
||||
FormatStringData newFS = new FormatStringData(string);
|
||||
if(!newFS.matches(formatStrings[i])) {
|
||||
String name = r.getResourceName(strings[i]);
|
||||
failures.append(String.format("%s (%s): %s != %s\n",
|
||||
name, locale.toString(), newFS, formatStrings[i]));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String name = r.getResourceName(strings[i]);
|
||||
failures.append(String.format("%s: error opening %s: %s\n",
|
||||
locale.toString(), name, e.getMessage()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(failures.toString(), errorCount(failures) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if string contains contains substrings
|
||||
*/
|
||||
private void contains(Resources r, int resource, StringBuilder failures, String expected) {
|
||||
String translation = r.getString(resource);
|
||||
if(!translation.contains(expected)) {
|
||||
Locale locale = r.getConfiguration().locale;
|
||||
String name = r.getResourceName(resource);
|
||||
failures.append(String.format("%s: %s did not contain: %s\n",
|
||||
locale.toString(), name, expected));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test dollar sign resources
|
||||
*/
|
||||
@Test
|
||||
public void testSpecialStringsMatch() throws Exception {
|
||||
final StringBuilder failures = new StringBuilder();
|
||||
|
||||
forEachLocale(r -> {
|
||||
contains(r, R.string.CFC_tag_text, failures, "?");
|
||||
contains(r, R.string.CFC_title_contains_text, failures, "?");
|
||||
contains(r, R.string.CFC_dueBefore_text, failures, "?");
|
||||
contains(r, R.string.CFC_tag_contains_text, failures, "?");
|
||||
contains(r, R.string.CFC_gtasks_list_text, failures, "?");
|
||||
});
|
||||
|
||||
assertEquals(failures.toString(), 0,
|
||||
failures.toString().replaceAll("[^\n]", "").length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Count newlines
|
||||
*/
|
||||
private int errorCount(StringBuilder failures) {
|
||||
int count = 0;
|
||||
int pos = -1;
|
||||
while(true) {
|
||||
pos = failures.indexOf("\n", pos + 1);
|
||||
if(pos == -1)
|
||||
return count;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an array of all string resource id's
|
||||
*/
|
||||
private int[] getResourceIds(Class<?> resources) {
|
||||
Field[] fields = resources.getDeclaredFields();
|
||||
List<Integer> ids = new ArrayList<>(fields.length);
|
||||
for (Field field : fields) {
|
||||
try {
|
||||
ids.add(field.getInt(null));
|
||||
} catch (Exception e) {
|
||||
// not a field we care about
|
||||
}
|
||||
}
|
||||
int[] idsAsIntArray = new int[ids.size()];
|
||||
for(int i = 0; i < ids.size(); i++)
|
||||
idsAsIntArray[i] = ids.get(i);
|
||||
return idsAsIntArray;
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.test
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.tasks.R.string
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests translations for consistency with the default values. You must extend this class and create
|
||||
* it with your own values for strings and arrays.
|
||||
*
|
||||
* @author Tim Su <tim></tim>@todoroo.com>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TranslationTests {
|
||||
/** Loop through each locale and call runnable */
|
||||
private fun forEachLocale(callback: (Resources) -> Unit) {
|
||||
val locales = Locale.getAvailableLocales()
|
||||
for (locale in locales) {
|
||||
callback(getResourcesForLocale(locale))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getResourcesForLocale(locale: Locale): Resources {
|
||||
val resources = InstrumentationRegistry.getTargetContext().resources
|
||||
val configuration = Configuration(resources.configuration)
|
||||
configuration.locale = locale
|
||||
return Resources(resources.assets, resources.displayMetrics, configuration)
|
||||
}
|
||||
|
||||
/** check if string contains contains substrings */
|
||||
private fun contains(r: Resources, resource: Int, failures: StringBuilder, expected: String) {
|
||||
val translation = r.getString(resource)
|
||||
if (!translation.contains(expected)) {
|
||||
val locale = r.configuration.locale
|
||||
val name = r.getResourceName(resource)
|
||||
failures.append(String.format("%s: %s did not contain: %s\n", locale.toString(), name, expected))
|
||||
}
|
||||
}
|
||||
|
||||
/** Test dollar sign resources */
|
||||
@Test
|
||||
fun testSpecialStringsMatch() {
|
||||
val failures = StringBuilder()
|
||||
forEachLocale { r: Resources ->
|
||||
contains(r, string.CFC_tag_text, failures, "?")
|
||||
contains(r, string.CFC_title_contains_text, failures, "?")
|
||||
contains(r, string.CFC_startBefore_text, failures, "?")
|
||||
contains(r, string.CFC_dueBefore_text, failures, "?")
|
||||
contains(r, string.CFC_tag_contains_text, failures, "?")
|
||||
contains(r, string.CFC_gtasks_list_text, failures, "?")
|
||||
}
|
||||
assertEquals(failures.toString(), 0, failures.toString().replace("[^\n]".toRegex(), "").length)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.Snippet;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.addCalendarMonthsToUnixtime;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.getDateString;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.getStartOfDay;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.getTimeString;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.getWeekday;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.getWeekdayShort;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.oneMonthFromNow;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.tasks.Freeze.freezeAt;
|
||||
import static org.tasks.date.DateTimeUtils.newDate;
|
||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DateUtilitiesTest {
|
||||
|
||||
private static Locale defaultLocale;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
defaultLocale = Locale.getDefault();
|
||||
Locale.setDefault(Locale.US);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
DateUtilities.is24HourOverride = null;
|
||||
Locale.setDefault(defaultLocale);
|
||||
}
|
||||
|
||||
private void setLocale(Locale locale) {
|
||||
Locale.setDefault(locale);
|
||||
Configuration config = new Configuration();
|
||||
config.locale = locale;
|
||||
DisplayMetrics metrics = getTargetContext().getResources().getDisplayMetrics();
|
||||
getTargetContext().getResources().updateConfiguration(config, metrics);
|
||||
}
|
||||
|
||||
public void forEachLocale(Runnable r) {
|
||||
Locale[] locales = Locale.getAvailableLocales();
|
||||
for(Locale locale : locales) {
|
||||
setLocale(locale);
|
||||
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimeString() {
|
||||
forEachLocale(() -> {
|
||||
DateTime d = newDateTime();
|
||||
|
||||
DateUtilities.is24HourOverride = false;
|
||||
for (int i = 0; i < 24; i++) {
|
||||
d = d.withHourOfDay(i);
|
||||
getTimeString(getTargetContext(), d);
|
||||
}
|
||||
|
||||
DateUtilities.is24HourOverride = true;
|
||||
for (int i = 0; i < 24; i++) {
|
||||
d = d.withHourOfDay(i);
|
||||
getTimeString(getTargetContext(), d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateString() {
|
||||
forEachLocale(() -> {
|
||||
DateTime d = newDateTime();
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
d = d.withMonthOfYear(i);
|
||||
getDateString(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet24HourTime() {
|
||||
DateUtilities.is24HourOverride = true;
|
||||
assertEquals("09:05", getTimeString(null, new DateTime(2014, 1, 4, 9, 5, 36)));
|
||||
assertEquals("13:00", getTimeString(null, new DateTime(2014, 1, 4, 13, 0, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTime() {
|
||||
DateUtilities.is24HourOverride = false;
|
||||
assertEquals("9:05 AM", getTimeString(null, new DateTime(2014, 1, 4, 9, 5, 36)));
|
||||
assertEquals("1:05 PM", getTimeString(null, new DateTime(2014, 1, 4, 13, 5, 36)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWithNoMinutes() {
|
||||
DateUtilities.is24HourOverride = false;
|
||||
assertEquals("1 PM", getTimeString(null, new DateTime(2014, 1, 4, 13, 0, 59))); // derp?
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDateStringWithYear() {
|
||||
assertEquals("Jan 4 '14", getDateString(new DateTime(2014, 1, 4, 0, 0, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDateStringHidingYear() {
|
||||
freezeAt(newDate(2014, 1, 1)).thawAfter(new Snippet() {{
|
||||
assertEquals("Jan 1", getDateString(newDateTime()));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDateStringWithDifferentYear() {
|
||||
freezeAt(newDate(2013, 12, 31)).thawAfter(new Snippet() {{
|
||||
assertEquals("Jan 1 '14", getDateString(new DateTime(2014, 1, 1, 0, 0, 0)));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneMonthFromStartOfDecember() {
|
||||
DateTime now = new DateTime(2013, 12, 1, 12, 19, 45, 192);
|
||||
final long expected = new DateTime(2014, 1, 1, 12, 19, 45, 192).getMillis();
|
||||
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
assertEquals(expected, oneMonthFromNow());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneMonthFromEndOfDecember() {
|
||||
DateTime now = new DateTime(2013, 12, 31, 16, 31, 20, 597);
|
||||
final long expected = new DateTime(2014, 1, 31, 16, 31, 20, 597).getMillis();
|
||||
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
assertEquals(expected, oneMonthFromNow());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSixMonthsFromEndOfDecember() {
|
||||
final DateTime now = new DateTime(2013, 12, 31, 17, 17, 32, 900);
|
||||
final long expected = new DateTime(2014, 7, 1, 17, 17, 32, 900).getMillis();
|
||||
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
assertEquals(expected, addCalendarMonthsToUnixtime(now.getMillis(), 6));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneMonthFromEndOfJanuary() {
|
||||
DateTime now = new DateTime(2014, 1, 31, 12, 54, 33, 175);
|
||||
final long expected = new DateTime(2014, 3, 3, 12, 54, 33, 175).getMillis();
|
||||
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
assertEquals(expected, oneMonthFromNow());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneMonthFromEndOfFebruary() {
|
||||
DateTime now = new DateTime(2014, 2, 28, 9, 19, 7, 990);
|
||||
final long expected = new DateTime(2014, 3, 28, 9, 19, 7, 990).getMillis();
|
||||
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
assertEquals(expected, oneMonthFromNow());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldGetStartOfDay() {
|
||||
DateTime now = new DateTime(2014, 1, 3, 10, 41, 41, 520);
|
||||
assertEquals(
|
||||
now.startOfDay().getMillis(),
|
||||
getStartOfDay(now.getMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWeekdayLongString() {
|
||||
assertEquals("Sunday", getWeekday(newDate(2013, 12, 29)));
|
||||
assertEquals("Monday", getWeekday(newDate(2013, 12, 30)));
|
||||
assertEquals("Tuesday", getWeekday(newDate(2013, 12, 31)));
|
||||
assertEquals("Wednesday", getWeekday(newDate(2014, 1, 1)));
|
||||
assertEquals("Thursday", getWeekday(newDate(2014, 1, 2)));
|
||||
assertEquals("Friday", getWeekday(newDate(2014, 1, 3)));
|
||||
assertEquals("Saturday", getWeekday(newDate(2014, 1, 4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWeekdayShortString() {
|
||||
assertEquals("Sun", getWeekdayShort(newDate(2013, 12, 29)));
|
||||
assertEquals("Mon", getWeekdayShort(newDate(2013, 12, 30)));
|
||||
assertEquals("Tue", getWeekdayShort(newDate(2013, 12, 31)));
|
||||
assertEquals("Wed", getWeekdayShort(newDate(2014, 1, 1)));
|
||||
assertEquals("Thu", getWeekdayShort(newDate(2014, 1, 2)));
|
||||
assertEquals("Fri", getWeekdayShort(newDate(2014, 1, 3)));
|
||||
assertEquals("Sat", getWeekdayShort(newDate(2014, 1, 4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddMonthsToTimestamp() {
|
||||
assertEquals(newDate(2014, 1, 1).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 12, 1).getMillis(), 1));
|
||||
assertEquals(newDate(2014, 12, 31).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 12, 31).getMillis(), 12));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddMonthsWithLessDays() {
|
||||
assertEquals(newDate(2014, 3, 3).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 12, 31).getMillis(), 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddMonthsWithMoreDays() {
|
||||
assertEquals(newDate(2014, 1, 30).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 11, 30).getMillis(), 2));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.getRelativeDay;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.tasks.Freeze.freezeAt;
|
||||
import static org.tasks.Freeze.thaw;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RelativeDayTest {
|
||||
|
||||
private static Locale defaultLocale;
|
||||
private static final DateTime now = new DateTime(2013, 12, 31, 11, 9, 42, 357);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
defaultLocale = Locale.getDefault();
|
||||
Locale.setDefault(Locale.US);
|
||||
freezeAt(now);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Locale.setDefault(defaultLocale);
|
||||
thaw();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativeDayIsToday() {
|
||||
checkRelativeDay(new DateTime(), "Today", "Today");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativeDayIsTomorrow() {
|
||||
checkRelativeDay(new DateTime().plusDays(1), "Tomorrow", "Tmrw");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativeDayIsYesterday() {
|
||||
checkRelativeDay(new DateTime().minusDays(1), "Yesterday", "Yest");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativeDayTwo() {
|
||||
checkRelativeDay(new DateTime().minusDays(2), "Sunday", "Sun");
|
||||
checkRelativeDay(new DateTime().plusDays(2), "Thursday", "Thu");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativeDaySix() {
|
||||
checkRelativeDay(new DateTime().minusDays(6), "Wednesday", "Wed");
|
||||
checkRelativeDay(new DateTime().plusDays(6), "Monday", "Mon");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativeDayOneWeek() {
|
||||
checkRelativeDay(new DateTime().minusDays(7), "Dec 24", "Dec 24");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativeDayOneWeekNextYear() {
|
||||
checkRelativeDay(new DateTime().plusDays(7), "Jan 7 '14", "Jan 7 '14");
|
||||
}
|
||||
|
||||
private void checkRelativeDay(DateTime now, String full, String abbreviated) {
|
||||
assertEquals(full, getRelativeDay(getTargetContext(), now.getMillis(), false));
|
||||
assertEquals(abbreviated, getRelativeDay(getTargetContext(), now.getMillis(), true));
|
||||
}
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
package com.todoroo.andlib.utility
|
||||
|
||||
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
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RelativeDayTest {
|
||||
private lateinit var defaultLocale: Locale
|
||||
private val now = DateTime(2013, 12, 31, 11, 9, 42, 357)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
defaultLocale = Locale.getDefault()
|
||||
Locale.setDefault(Locale.US)
|
||||
Freeze.freezeAt(now)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Locale.setDefault(defaultLocale)
|
||||
Freeze.thaw()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeDayIsToday() {
|
||||
checkRelativeDay(DateTime(), "Today", "Today")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeDayIsTomorrow() {
|
||||
checkRelativeDay(DateTime().plusDays(1), "Tomorrow", "Tmrw")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeDayIsYesterday() {
|
||||
checkRelativeDay(DateTime().minusDays(1), "Yesterday", "Yest")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeDayTwo() {
|
||||
checkRelativeDay(DateTime().minusDays(2), "Sunday", "Sun")
|
||||
checkRelativeDay(DateTime().plusDays(2), "Thursday", "Thu")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeDaySix() {
|
||||
checkRelativeDay(DateTime().minusDays(6), "Wednesday", "Wed")
|
||||
checkRelativeDay(DateTime().plusDays(6), "Monday", "Mon")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeDayOneWeek() {
|
||||
checkRelativeDay(DateTime().minusDays(7), "December 24", "Dec 24")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeDayOneWeekNextYear() {
|
||||
checkRelativeDay(DateTime().plusDays(7), "January 7, 2014", "Jan 7, 2014")
|
||||
}
|
||||
|
||||
private fun checkRelativeDay(now: DateTime, full: String, abbreviated: String) = runBlocking {
|
||||
assertEquals(
|
||||
full,
|
||||
getRelativeDay(now.millis, DateStyle.LONG))
|
||||
assertEquals(
|
||||
abbreviated,
|
||||
getRelativeDay(now.millis))
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package com.todoroo.astrid.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.tasks.extensions.isFromHistory
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MainActivityTest {
|
||||
@Test
|
||||
fun newTaskIsNotFromHistory() {
|
||||
assertFalse(Intent().setFlags(FLAG_ACTIVITY_NEW_TASK).isFromHistory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun oldTaskIsNotFromHistory() {
|
||||
assertFalse(Intent().setFlags(FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY).isFromHistory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun newTaskIsFromHistory() {
|
||||
assertTrue(
|
||||
Intent()
|
||||
.setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY)
|
||||
.isFromHistory)
|
||||
}
|
||||
}
|
||||
@ -1,250 +0,0 @@
|
||||
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 kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.tasks.LocalBroadcastManager
|
||||
import org.tasks.R
|
||||
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.CaldavAccount
|
||||
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_CALDAV
|
||||
import org.tasks.data.entity.CaldavCalendar
|
||||
import org.tasks.data.entity.Task
|
||||
import org.tasks.filters.CaldavFilter
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.makers.CaldavTaskMaker.CALENDAR
|
||||
import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT
|
||||
import org.tasks.makers.CaldavTaskMaker.TASK
|
||||
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
|
||||
import org.tasks.makers.TaskMaker.CREATION_TIME
|
||||
import org.tasks.makers.TaskMaker.PARENT
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.time.DateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class CaldavManualSortTaskAdapterTest : InjectingTestCase() {
|
||||
@Inject lateinit var googleTaskDao: GoogleTaskDao
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@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(
|
||||
calendar = CaldavCalendar(name = "calendar", uuid = "1234"),
|
||||
account = CaldavAccount(accountType = TYPE_CALDAV)
|
||||
)
|
||||
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_manual_sort, true)
|
||||
tasks.clear()
|
||||
adapter = CaldavManualSortTaskAdapter(googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
|
||||
adapter.setDataSource(dataSource)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveToSamePositionIsNoop() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(1)))
|
||||
|
||||
move(0, 0)
|
||||
|
||||
checkOrder(null, 0)
|
||||
checkOrder(null, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskToTopOfList() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(1)))
|
||||
|
||||
move(1, 0)
|
||||
|
||||
checkOrder(created.minusSeconds(1), 1)
|
||||
checkOrder(null, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskToBottomOfList() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(1)))
|
||||
|
||||
move(0, 1)
|
||||
|
||||
checkOrder(null, 1)
|
||||
checkOrder(created.plusSeconds(2), 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveDownToMiddleOfList() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(1)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(2)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(3)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(4)))
|
||||
|
||||
move(0, 2)
|
||||
|
||||
checkOrder(null, 1)
|
||||
checkOrder(null, 2)
|
||||
checkOrder(created.plusSeconds(3), 0)
|
||||
checkOrder(created.plusSeconds(4), 3)
|
||||
checkOrder(created.plusSeconds(5), 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveUpToMiddleOfList() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(1)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(2)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(3)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(4)))
|
||||
|
||||
move(3, 1)
|
||||
|
||||
checkOrder(null, 0)
|
||||
checkOrder(created.plusSeconds(1), 3)
|
||||
checkOrder(created.plusSeconds(2), 1)
|
||||
checkOrder(created.plusSeconds(3), 2)
|
||||
checkOrder(null, 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveDownNoShiftRequired() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(1)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(3)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(4)))
|
||||
|
||||
move(0, 1)
|
||||
|
||||
checkOrder(null, 1)
|
||||
checkOrder(created.plusSeconds(2), 0)
|
||||
checkOrder(null, 2)
|
||||
checkOrder(null, 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveUpNoShiftRequired() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(2)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(3)))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(4)))
|
||||
|
||||
move(2, 1)
|
||||
|
||||
checkOrder(null, 0)
|
||||
checkOrder(created.plusSeconds(1), 2)
|
||||
checkOrder(null, 1)
|
||||
checkOrder(null, 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveToNewSubtask() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(2)))
|
||||
|
||||
move(1, 1, 1)
|
||||
|
||||
checkOrder(null, 0)
|
||||
checkOrder(null, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveToTopOfExistingSubtasks() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
val parent = addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(5)), with(PARENT, parent))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(2)))
|
||||
|
||||
move(2, 1, 1)
|
||||
|
||||
checkOrder(null, 0)
|
||||
checkOrder(created.plusSeconds(4), 2)
|
||||
checkOrder(null, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun indentingChangesParent() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
addTask(with(CREATION_TIME, created))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(2)))
|
||||
|
||||
move(1, 1, 1)
|
||||
|
||||
assertEquals(tasks[0].id, tasks[1].parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deindentLastMultiLevelSubtask() {
|
||||
val created = DateTime(2020, 5, 17, 9, 53, 17)
|
||||
val grandparent = addTask(with(CREATION_TIME, created))
|
||||
val parent = addTask(with(CREATION_TIME, created.plusSeconds(5)), with(PARENT, grandparent))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(1)), with(PARENT, parent))
|
||||
addTask(with(CREATION_TIME, created.plusSeconds(2)), with(PARENT, parent))
|
||||
|
||||
move(3, 3, 1)
|
||||
|
||||
assertEquals(grandparent, tasks[3].parent)
|
||||
checkOrder(created.plusSeconds(6), 3)
|
||||
}
|
||||
|
||||
private fun move(from: Int, to: Int, indent: Int = 0) = runBlocking {
|
||||
tasks.addAll(taskDao.fetchTasks(getQuery(preferences, filter)))
|
||||
val adjustedTo = if (from < to) to + 1 else to // match DragAndDropRecyclerAdapter behavior
|
||||
adapter.moved(from, adjustedTo, indent)
|
||||
}
|
||||
|
||||
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
|
||||
if (order == null) {
|
||||
assertNull(sortOrder)
|
||||
} else {
|
||||
assertEquals(order, sortOrder)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTask(vararg properties: PropertyValue<in Task?, *>): Long = runBlocking {
|
||||
val task = newTask(*properties)
|
||||
taskDao.createNew(task)
|
||||
val remoteParent = if (task.parent > 0) caldavDao.getRemoteIdForTask(task.parent) else null
|
||||
caldavDao.insert(
|
||||
newCaldavTask(
|
||||
with(TASK, task.id),
|
||||
with(CALENDAR, "1234"),
|
||||
with(REMOTE_PARENT, remoteParent)))
|
||||
task.id
|
||||
}
|
||||
}
|
||||
@ -1,213 +0,0 @@
|
||||
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 kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.*
|
||||
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.makers.TaskContainerMaker.PARENT
|
||||
import org.tasks.makers.TaskContainerMaker.newTaskContainer
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class CaldavTaskAdapterTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@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>()
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
|
||||
tasks.clear()
|
||||
adapter = TaskAdapter(false, googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
|
||||
adapter.setDataSource(object : TaskAdapterDataSource {
|
||||
override fun getItem(position: Int) = tasks[position]
|
||||
|
||||
override fun getTaskCount() = tasks.size
|
||||
})
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canMoveTask() {
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
assertTrue(adapter.canMove(tasks[0], 0, tasks[1], 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cantMoveTaskToChildPosition() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
|
||||
assertFalse(adapter.canMove(tasks[0], 0, tasks[1], 1))
|
||||
assertFalse(adapter.canMove(tasks[0], 0, tasks[2], 2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canMoveChildAboveParent() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
|
||||
assertTrue(adapter.canMove(tasks[1], 1, tasks[0], 0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canMoveChildBetweenSiblings() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
|
||||
assertTrue(adapter.canMove(tasks[1], 1, tasks[2], 2))
|
||||
assertTrue(adapter.canMove(tasks[2], 2, tasks[1], 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun maxIndentNoChildren() {
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
assertEquals(1, adapter.maxIndent(0, tasks[1]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun maxIndentMultiLevelSubtask() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask()
|
||||
|
||||
assertEquals(1, adapter.maxIndent(0, tasks[1]))
|
||||
assertEquals(2, adapter.maxIndent(1, tasks[2]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun minIndentInMiddleOfSubtasks() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
|
||||
assertEquals(1, adapter.minIndent(2, tasks[1]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun minIndentAtEndOfSubtasks() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask()
|
||||
|
||||
assertEquals(0, adapter.minIndent(3, tasks[2]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun minIndentAtEndOfMultiLevelSubtask() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[1]))
|
||||
addTask()
|
||||
|
||||
assertEquals(0, adapter.minIndent(2, tasks[1]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun minIndentInMiddleOfMultiLevelSubtasks() {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[1]))
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask()
|
||||
|
||||
assertEquals(1, adapter.minIndent(3, tasks[2]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun movingTaskToNewParentSetsId() = runBlocking {
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
adapter.moved(1, 1, 1)
|
||||
|
||||
assertEquals(tasks[0].id, taskDao.fetch(tasks[1].id)!!.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun movingTaskToNewParentSetsRemoteId() = runBlocking {
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
adapter.moved(1, 1, 1)
|
||||
|
||||
val parentId = caldavDao.getTask(tasks[0].id)!!.remoteId!!
|
||||
|
||||
assertTrue(parentId.isNotBlank())
|
||||
assertEquals(parentId, caldavDao.getTask(tasks[1].id)!!.remoteParent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unindentingTaskRemovesParent() = runBlocking {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
|
||||
adapter.moved(1, 1, 0)
|
||||
|
||||
assertTrue(caldavDao.getTask(tasks[1].id)!!.remoteParent.isNullOrBlank())
|
||||
assertEquals(0, taskDao.fetch(tasks[1].id)!!.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskUpToParent() = runBlocking {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[1]))
|
||||
|
||||
adapter.moved(2, 2, 1)
|
||||
|
||||
assertEquals(tasks[0].id, taskDao.fetch(tasks[2].id)!!.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskUpToGrandparent() = runBlocking {
|
||||
addTask()
|
||||
addTask(with(PARENT, tasks[0]))
|
||||
addTask(with(PARENT, tasks[1]))
|
||||
addTask(with(PARENT, tasks[2]))
|
||||
|
||||
adapter.moved(3, 3, 1)
|
||||
|
||||
assertEquals(tasks[0].id, taskDao.fetch(tasks[3].id)!!.parent)
|
||||
}
|
||||
|
||||
private fun addTask(vararg properties: PropertyValue<in TaskContainer?, *>) = runBlocking {
|
||||
val t = newTaskContainer(*properties)
|
||||
val task = t.task
|
||||
taskDao.createNew(task)
|
||||
val caldavTask = CaldavTask(task = t.id, calendar = "calendar")
|
||||
if (task.parent > 0) {
|
||||
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.parent)
|
||||
}
|
||||
tasks.add(
|
||||
t.copy(
|
||||
caldavTask = caldavTask.copy(
|
||||
id = caldavDao.insert(caldavTask)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,450 +0,0 @@
|
||||
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 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.TaskContainer
|
||||
import org.tasks.data.TaskListQuery.getQuery
|
||||
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_GOOGLE_TASKS
|
||||
import org.tasks.data.entity.CaldavCalendar
|
||||
import org.tasks.data.entity.Task
|
||||
import org.tasks.filters.CaldavFilter
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.makers.CaldavTaskMaker.CALENDAR
|
||||
import org.tasks.makers.CaldavTaskMaker.TASK
|
||||
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
|
||||
import org.tasks.makers.TaskMaker.PARENT
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
import org.tasks.preferences.Preferences
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class GoogleTaskManualSortAdapterTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var caldavDao: CaldavDao
|
||||
@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 = CaldavFilter(
|
||||
calendar = CaldavCalendar(uuid = "1234"),
|
||||
account = CaldavAccount(accountType = TYPE_GOOGLE_TASKS)
|
||||
)
|
||||
private val dataSource = object : TaskAdapterDataSource {
|
||||
override fun getItem(position: Int) = tasks[position]
|
||||
|
||||
override fun getTaskCount() = tasks.size
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskToTopOfList() {
|
||||
addTask()
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
move(2, 0)
|
||||
|
||||
checkOrder(0, 2)
|
||||
checkOrder(1, 0)
|
||||
checkOrder(2, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskToBottomOfList() {
|
||||
addTask()
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
move(0, 2)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(1, 2)
|
||||
checkOrder(2, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskToBottomOfListAsNewSubtask() {
|
||||
addTask()
|
||||
addTask()
|
||||
val parent = addTask()
|
||||
|
||||
move(0, 2, 1)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(1, 2)
|
||||
checkOrder(0, 0, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskToBottomOfListAndSubtask() {
|
||||
addTask()
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(0, 2, 1)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 0, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskToMiddleOfList() {
|
||||
addTask()
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
move(0, 1)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(1, 0)
|
||||
checkOrder(2, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskDownAsNewSubtask() {
|
||||
addTask()
|
||||
val parent = addTask()
|
||||
addTask()
|
||||
|
||||
move(0, 1, 1)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(0, 0, parent)
|
||||
checkOrder(1, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskDownToFrontOfSubtasks() {
|
||||
addTask()
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(0, 1, 1)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(0, 0, parent)
|
||||
checkOrder(1, 2, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskDownToMiddleOfSubtasks() {
|
||||
addTask()
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(0, 2, 1)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 0, parent)
|
||||
checkOrder(2, 3, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskDownToEndOfSubtasks() {
|
||||
addTask()
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
|
||||
move(0, 2, 1)
|
||||
|
||||
checkOrder(0, 1)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 0, parent)
|
||||
checkOrder(1, 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskUpAsNewSubtask() {
|
||||
val parent = addTask()
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
move(2, 1, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskUpToFrontOfSubtasks() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
|
||||
move(2, 1, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 1, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskUpToMiddleOfSubtasks() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
|
||||
move(3, 2, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 1, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(2, 2, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveTaskUpToEndOfSubtasks() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
move(3, 2, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 1, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(1, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun indentTask() {
|
||||
val parent = addTask()
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
move(1, 1, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 1, parent)
|
||||
checkOrder(1, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskFromTopToBottom() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
|
||||
move(1, 3, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(2, 1, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskFromTopToBottomAtEndOfList() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(1, 3, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(2, 1, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskFromBottomToTop() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(3, 1, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 3, parent)
|
||||
checkOrder(1, 1, parent)
|
||||
checkOrder(2, 2, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskFromTopToMiddle() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(1, 2, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 1, parent)
|
||||
checkOrder(2, 3, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskFromBottomToMiddle() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(3, 2, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 1, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(2, 2, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskFromMiddleToTop() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(2, 1, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 1, parent)
|
||||
checkOrder(2, 3, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskFromMiddleToBottom() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(2, 3, 1)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 1, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(2, 2, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskUpToTopLevel() {
|
||||
addTask()
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
move(3, 1, 0)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(1, 3)
|
||||
checkOrder(2, 1)
|
||||
checkOrder(0, 2, parent)
|
||||
checkOrder(1, 4, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskDownToTopLevel() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
addTask()
|
||||
|
||||
move(2, 4, 0)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 1, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(1, 4)
|
||||
checkOrder(2, 2)
|
||||
checkOrder(3, 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveSubtaskToEndOfListAndDeindent() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
|
||||
move(2, 3, 0)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(0, 1, parent)
|
||||
checkOrder(1, 3, parent)
|
||||
checkOrder(1, 2)
|
||||
checkOrder(2, 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deindentTask() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask()
|
||||
|
||||
move(1, 1, 0)
|
||||
|
||||
checkOrder(0, 0)
|
||||
checkOrder(1, 1)
|
||||
checkOrder(2, 2)
|
||||
}
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
preferences.clear()
|
||||
preferences.setBoolean(R.string.p_manual_sort, true)
|
||||
tasks.clear()
|
||||
adapter = GoogleTaskManualSortAdapter(googleTaskDao, caldavDao, taskDao, localBroadcastManager, taskMover)
|
||||
adapter.setDataSource(dataSource)
|
||||
}
|
||||
|
||||
private fun move(from: Int, to: Int, indent: Int = 0) = runBlocking {
|
||||
tasks.addAll(taskDao.fetchTasks(getQuery(preferences, filter)))
|
||||
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)!!
|
||||
assertEquals(order, googleTask.order)
|
||||
assertEquals(parent, googleTask.parent)
|
||||
}
|
||||
|
||||
private fun addTask(vararg properties: PropertyValue<in Task?, *>): Long = runBlocking {
|
||||
val task = newTask(*properties)
|
||||
taskDao.createNew(task)
|
||||
googleTaskDao.insertAndShift(
|
||||
task,
|
||||
newCaldavTask(
|
||||
with(TASK, task.id),
|
||||
with(CALENDAR, "1234"),
|
||||
),
|
||||
false
|
||||
)
|
||||
task.id
|
||||
}
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
package com.todoroo.astrid.adapter
|
||||
|
||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||
import com.natpryce.makeiteasy.PropertyValue
|
||||
import com.todoroo.astrid.dao.TaskDao
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.tasks.data.TaskContainer
|
||||
import org.tasks.data.TaskListQuery.getQuery
|
||||
import org.tasks.data.entity.Task
|
||||
import org.tasks.filters.MyTasksFilter
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.makers.TaskMaker.PARENT
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
import org.tasks.preferences.Preferences
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class OfflineSubtaskTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var preferences: Preferences
|
||||
|
||||
private val filter = runBlocking { MyTasksFilter.create() }
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
preferences.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleLevelSubtask() {
|
||||
val parent = addTask()
|
||||
val child = addTask(with(PARENT, parent))
|
||||
|
||||
val tasks = query()
|
||||
|
||||
assertEquals(child, tasks[1].id)
|
||||
assertEquals(parent, tasks[1].parent)
|
||||
assertEquals(1, tasks[1].indent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiLevelSubtasks() {
|
||||
val grandparent = addTask()
|
||||
val parent = addTask(with(PARENT, grandparent))
|
||||
val child = addTask(with(PARENT, parent))
|
||||
|
||||
val tasks = query()
|
||||
|
||||
assertEquals(child, tasks[2].id)
|
||||
assertEquals(parent, tasks[2].parent)
|
||||
assertEquals(2, tasks[2].indent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parentWithOneChildHasChildrenCountOne() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
val tasks = query()
|
||||
|
||||
val parentTask = tasks.find { it.id == parent }!!
|
||||
assertEquals(1, parentTask.children)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parentWithMultipleChildrenHasCorrectCount() {
|
||||
val parent = addTask()
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
val tasks = query()
|
||||
|
||||
val parentTask = tasks.find { it.id == parent }!!
|
||||
assertEquals(3, parentTask.children)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun grandparentCountsAllDescendants() {
|
||||
val grandparent = addTask()
|
||||
val parent = addTask(with(PARENT, grandparent))
|
||||
addTask(with(PARENT, parent))
|
||||
|
||||
val tasks = query()
|
||||
|
||||
val grandparentTask = tasks.find { it.id == grandparent }!!
|
||||
assertEquals(2, grandparentTask.children)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun leafTaskHasNoChildren() {
|
||||
val parent = addTask()
|
||||
val child = addTask(with(PARENT, parent))
|
||||
|
||||
val tasks = query()
|
||||
|
||||
val childTask = tasks.find { it.id == child }!!
|
||||
assertEquals(0, childTask.children)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deepHierarchyCountsAllDescendants() {
|
||||
val root = addTask()
|
||||
val level1 = addTask(with(PARENT, root))
|
||||
val level2 = addTask(with(PARENT, level1))
|
||||
val level3 = addTask(with(PARENT, level2))
|
||||
addTask(with(PARENT, level3))
|
||||
|
||||
val tasks = query()
|
||||
|
||||
val rootTask = tasks.find { it.id == root }!!
|
||||
assertEquals(4, rootTask.children)
|
||||
}
|
||||
|
||||
private fun addTask(vararg properties: PropertyValue<in Task?, *>): Long = runBlocking {
|
||||
val task = newTask(*properties)
|
||||
taskDao.createNew(task)
|
||||
task.id
|
||||
}
|
||||
|
||||
private fun query(): List<TaskContainer> = runBlocking {
|
||||
taskDao.fetchTasks(getQuery(preferences, filter))
|
||||
}
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
package com.todoroo.astrid.adapter
|
||||
|
||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||
import com.natpryce.makeiteasy.PropertyValue
|
||||
import com.todoroo.astrid.dao.TaskDao
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
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.makers.TaskMaker.DUE_DATE
|
||||
import org.tasks.makers.TaskMaker.PARENT
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
import org.tasks.preferences.Preferences
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class RecursiveLoopTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var preferences: Preferences
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
preferences.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleSelfLoop() = runBlocking {
|
||||
addTask(with(DUE_DATE, newDateTime()), with(PARENT, 1L))
|
||||
|
||||
val tasks = getTasks()
|
||||
|
||||
assertEquals(1, tasks.size)
|
||||
assertEquals(1L, tasks[0].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleSingleLevelLoop() = runBlocking {
|
||||
val parent = addTask(with(DUE_DATE, newDateTime()))
|
||||
val child = addTask(with(PARENT, parent))
|
||||
|
||||
taskDao.setParent(child, listOf(parent))
|
||||
|
||||
val tasks = getTasks()
|
||||
assertEquals(2, tasks.size)
|
||||
assertEquals(parent, tasks[0].id)
|
||||
assertEquals(child, tasks[1].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleMultiLevelLoop() = runBlocking {
|
||||
val parent = addTask(with(DUE_DATE, newDateTime()))
|
||||
val child = addTask(with(PARENT, parent))
|
||||
val grandchild = addTask(with(PARENT, child))
|
||||
|
||||
taskDao.setParent(grandchild, listOf(parent))
|
||||
|
||||
val tasks = getTasks()
|
||||
assertEquals(3, tasks.size)
|
||||
assertEquals(parent, tasks[0].id)
|
||||
assertEquals(child, tasks[1].id)
|
||||
assertEquals(grandchild, tasks[2].id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun descendantsRecursiveLoopBothMatchFilter() = runBlocking {
|
||||
val parent = addTask(with(DUE_DATE, newDateTime()))
|
||||
val child = addTask(with(DUE_DATE, newDateTime()), with(PARENT, parent))
|
||||
|
||||
taskDao.setParent(child, listOf(parent))
|
||||
|
||||
val tasks = getTasks()
|
||||
assertEquals(2, tasks.size)
|
||||
}
|
||||
|
||||
private suspend fun getTasks() = taskDao.fetchTasks(
|
||||
getQuery(preferences, TodayFilter.create())
|
||||
)
|
||||
|
||||
private suspend fun addTask(vararg properties: PropertyValue<in Task?, *>): Long {
|
||||
val task = newTask(*properties)
|
||||
taskDao.createNew(task)
|
||||
return task.id
|
||||
}
|
||||
}
|
||||
@ -1,287 +0,0 @@
|
||||
package com.todoroo.astrid.alarms
|
||||
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
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.injection.InjectingTestCase
|
||||
import org.tasks.time.DateTime
|
||||
import org.tasks.time.DateTimeUtils2
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class AlarmJobServiceTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@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)
|
||||
)
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.dao;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.injection.TestComponent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertNotSame;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MetadataDaoTests extends DatabaseTestCase {
|
||||
|
||||
@Inject MetadataDao metadataDao;
|
||||
@Inject TaskDao taskDao;
|
||||
|
||||
private Metadata metadata;
|
||||
|
||||
public static Property<?>[] KEYS = new Property<?>[] { Metadata.ID, Metadata.KEY };
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
metadata = new Metadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test basic creation, fetch, and save
|
||||
*/
|
||||
@Test
|
||||
public void testCrud() throws Exception {
|
||||
assertTrue(metadataDao.toList(Query.select(Metadata.ID)).isEmpty());
|
||||
|
||||
// create "happy"
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setTask(1L);
|
||||
metadata.setKey("happy");
|
||||
assertTrue(metadataDao.persist(metadata));
|
||||
assertEquals(1, metadataDao.toList(Query.select(Metadata.ID)).size());
|
||||
long happyId = metadata.getId();
|
||||
assertNotSame(Metadata.NO_ID, happyId);
|
||||
metadata = metadataDao.fetch(happyId, KEYS);
|
||||
assertEquals("happy", metadata.getKey());
|
||||
|
||||
// create "sad"
|
||||
metadata = new Metadata();
|
||||
metadata.setTask(1L);
|
||||
metadata.setKey("sad");
|
||||
assertTrue(metadataDao.persist(metadata));
|
||||
assertEquals(2, metadataDao.toList(Query.select(Metadata.ID)).size());
|
||||
|
||||
// rename sad to melancholy
|
||||
long sadId = metadata.getId();
|
||||
assertNotSame(Metadata.NO_ID, sadId);
|
||||
metadata.setKey("melancholy");
|
||||
assertTrue(metadataDao.persist(metadata));
|
||||
assertEquals(2, metadataDao.toList(Query.select(Metadata.ID)).size());
|
||||
|
||||
// check state
|
||||
metadata = metadataDao.fetch(happyId, KEYS);
|
||||
assertEquals("happy", metadata.getKey());
|
||||
metadata = metadataDao.fetch(sadId, KEYS);
|
||||
assertEquals("melancholy", metadata.getKey());
|
||||
|
||||
// delete sad
|
||||
assertTrue(metadataDao.delete(sadId));
|
||||
List<Metadata> metadataList = metadataDao.toList(Query.select(KEYS));
|
||||
assertEquals(1, metadataList.size());
|
||||
assertEquals("happy", metadataList.get(0).getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test metadata bound to task
|
||||
*/
|
||||
@Test
|
||||
public void testMetadataConditions() {
|
||||
// create "happy"
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setKey("with1");
|
||||
metadata.setTask(1L);
|
||||
assertTrue(metadataDao.persist(metadata));
|
||||
|
||||
metadata = new Metadata();
|
||||
metadata.setKey("with2");
|
||||
metadata.setTask(2L);
|
||||
assertTrue(metadataDao.persist(metadata));
|
||||
|
||||
metadata = new Metadata();
|
||||
metadata.setKey("with1");
|
||||
metadata.setTask(1L);
|
||||
assertTrue(metadataDao.persist(metadata));
|
||||
|
||||
List<Metadata> metadataList = metadataDao.toList(Query.select(KEYS).where(MetadataCriteria.byTask(1)));
|
||||
assertEquals(2, metadataList.size());
|
||||
assertEquals("with1", metadataList.get(0).getKey());
|
||||
assertEquals("with1", metadataList.get(1).getKey());
|
||||
|
||||
assertTrue(metadataDao.toList(Query.select(KEYS).where(MetadataCriteria.byTask(3))).isEmpty());
|
||||
|
||||
assertEquals(2, metadataDao.deleteWhere(MetadataCriteria.byTask(1)));
|
||||
assertEquals(1, metadataDao.toList(Query.select(KEYS)).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontSaveMetadataWithoutTaskId() {
|
||||
try {
|
||||
metadataDao.persist(metadata);
|
||||
fail("expected exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().startsWith("metadata needs to be attached to a task"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveMetadata() {
|
||||
metadata.setTask(1L);
|
||||
metadataDao.persist(metadata);
|
||||
|
||||
assertNotNull(metadataDao.fetch(metadata.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontDeleteValidMetadata() {
|
||||
final Task task = new Task();
|
||||
taskDao.save(task);
|
||||
metadata.setTask(task.getId());
|
||||
metadataDao.persist(metadata);
|
||||
|
||||
metadataDao.removeDanglingMetadata();
|
||||
|
||||
assertNotNull(metadataDao.fetch(metadata.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDangling() {
|
||||
metadata.setTask(1L);
|
||||
metadataDao.persist(metadata);
|
||||
|
||||
metadataDao.removeDanglingMetadata();
|
||||
|
||||
assertNull(metadataDao.fetch(1));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.dao;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.injection.TestComponent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNotSame;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaskDaoTests extends DatabaseTestCase {
|
||||
|
||||
public static Property<?>[] IDS = new Property<?>[] { Task.ID };
|
||||
|
||||
public static Property<?>[] TITLES = new Property<?>[] { Task.ID,
|
||||
Task.TITLE };
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
|
||||
/**
|
||||
* Test basic task creation, fetch, and save
|
||||
*/
|
||||
@Test
|
||||
public void testTaskCreation() {
|
||||
assertEquals(0, taskDao.toList(Query.select(IDS)).size());
|
||||
|
||||
// create task "happy"
|
||||
Task task = new Task();
|
||||
task.setTitle("happy");
|
||||
taskDao.save(task);
|
||||
assertEquals(1, taskDao.toList(Query.select(IDS)).size());
|
||||
long happyId = task.getId();
|
||||
assertNotSame(Task.NO_ID, happyId);
|
||||
task = taskDao.fetch(happyId, TITLES);
|
||||
assertEquals("happy", task.getTitle());
|
||||
|
||||
// create task "sad"
|
||||
task = new Task();
|
||||
task.setTitle("sad");
|
||||
taskDao.save(task);
|
||||
assertEquals(2, taskDao.toList(Query.select(IDS)).size());
|
||||
|
||||
// rename sad to melancholy
|
||||
long sadId = task.getId();
|
||||
assertNotSame(Task.NO_ID, sadId);
|
||||
task.setTitle("melancholy");
|
||||
taskDao.save(task);
|
||||
assertEquals(2, taskDao.toList(Query.select(IDS)).size());
|
||||
|
||||
// check state
|
||||
task = taskDao.fetch(happyId, TITLES);
|
||||
assertEquals("happy", task.getTitle());
|
||||
task = taskDao.fetch(sadId,TITLES);
|
||||
assertEquals("melancholy", task.getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test various task fetch conditions
|
||||
*/
|
||||
@Test
|
||||
public void testTaskConditions() {
|
||||
// create normal task
|
||||
Task task = new Task();
|
||||
task.setTitle("normal");
|
||||
taskDao.save(task);
|
||||
|
||||
// create blank task
|
||||
task = new Task();
|
||||
task.setTitle("");
|
||||
taskDao.save(task);
|
||||
|
||||
// create hidden task
|
||||
task = new Task();
|
||||
task.setTitle("hidden");
|
||||
task.setHideUntil(DateUtilities.now() + 10000);
|
||||
taskDao.save(task);
|
||||
|
||||
// create task with deadlines
|
||||
task = new Task();
|
||||
task.setTitle("deadlineInFuture");
|
||||
task.setDueDate(DateUtilities.now() + 10000);
|
||||
taskDao.save(task);
|
||||
|
||||
task = new Task();
|
||||
task.setTitle("deadlineInPast");
|
||||
task.setDueDate(DateUtilities.now() - 10000);
|
||||
taskDao.save(task);
|
||||
|
||||
// create completed task
|
||||
task = new Task();
|
||||
task.setTitle("completed");
|
||||
task.setCompletionDate(DateUtilities.now() - 10000);
|
||||
taskDao.save(task);
|
||||
|
||||
// check has no name
|
||||
List<Task> tasks = taskDao.toList(Query.select(TITLES).where(TaskCriteria.hasNoTitle()));
|
||||
assertEquals(1, tasks.size());
|
||||
assertEquals("", tasks.get(0).getTitle());
|
||||
|
||||
// check is active
|
||||
assertEquals(5, taskDao.toList(Query.select(TITLES).where(TaskCriteria.isActive())).size());
|
||||
|
||||
// check is visible
|
||||
assertEquals(5, taskDao.toList(Query.select(TITLES).where(TaskCriteria.isVisible())).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test task deletion
|
||||
*/
|
||||
@Test
|
||||
public void testTDeletion() {
|
||||
assertEquals(0, taskDao.toList(Query.select(IDS)).size());
|
||||
|
||||
// create task "happy"
|
||||
Task task = new Task();
|
||||
task.setTitle("happy");
|
||||
taskDao.save(task);
|
||||
assertEquals(1, taskDao.toList(Query.select(IDS)).size());
|
||||
|
||||
// delete
|
||||
long happyId = task.getId();
|
||||
assertTrue(taskDao.delete(happyId));
|
||||
assertEquals(0, taskDao.toList(Query.select(IDS)).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test save without prior create doesn't work
|
||||
*/
|
||||
@Test
|
||||
public void testSaveWithoutCreate() {
|
||||
// try to save task "happy"
|
||||
Task task = new Task();
|
||||
task.setTitle("happy");
|
||||
task.setID(1L);
|
||||
|
||||
taskDao.save(task);
|
||||
|
||||
assertEquals(0, taskDao.toList(Query.select(IDS)).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test passing invalid task indices to various things
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidIndex() {
|
||||
assertEquals(0, taskDao.toList(Query.select(IDS)).size());
|
||||
|
||||
assertNull(taskDao.fetch(1, IDS));
|
||||
|
||||
assertFalse(taskDao.delete(1));
|
||||
|
||||
// make sure db still works
|
||||
assertEquals(0, taskDao.toList(Query.select(IDS)).size());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
// TODO check eventing
|
||||
}
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.dao
|
||||
|
||||
import org.tasks.data.entity.Task
|
||||
import com.todoroo.astrid.service.TaskDeleter
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.time.DateTimeUtils2.currentTimeMillis
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class TaskDaoTests : InjectingTestCase() {
|
||||
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var taskDeleter: TaskDeleter
|
||||
|
||||
/** Test basic task creation, fetch, and save */
|
||||
@Test
|
||||
fun testTaskCreation() = runBlocking {
|
||||
assertEquals(0, taskDao.getAll().size)
|
||||
|
||||
// create task "happy"
|
||||
var task = Task()
|
||||
task.title = "happy"
|
||||
taskDao.createNew(task)
|
||||
assertEquals(1, taskDao.getAll().size)
|
||||
val happyId = task.id
|
||||
assertNotSame(Task.NO_ID, happyId)
|
||||
task = taskDao.fetch(happyId)!!
|
||||
assertEquals("happy", task.title)
|
||||
|
||||
// create task "sad"
|
||||
task = Task()
|
||||
task.title = "sad"
|
||||
taskDao.createNew(task)
|
||||
assertEquals(2, taskDao.getAll().size)
|
||||
|
||||
// rename sad to melancholy
|
||||
val sadId = task.id
|
||||
assertNotSame(Task.NO_ID, sadId)
|
||||
task.title = "melancholy"
|
||||
taskDao.save(task)
|
||||
assertEquals(2, taskDao.getAll().size)
|
||||
|
||||
// check state
|
||||
task = taskDao.fetch(happyId)!!
|
||||
assertEquals("happy", task.title)
|
||||
task = taskDao.fetch(sadId)!!
|
||||
assertEquals("melancholy", task.title)
|
||||
}
|
||||
|
||||
/** Test various task fetch conditions */
|
||||
@Test
|
||||
fun testTaskConditions() = runBlocking {
|
||||
// create normal task
|
||||
var task = Task()
|
||||
task.title = "normal"
|
||||
taskDao.createNew(task)
|
||||
|
||||
// create blank task
|
||||
task = Task()
|
||||
task.title = ""
|
||||
taskDao.createNew(task)
|
||||
|
||||
// create hidden task
|
||||
task = Task()
|
||||
task.title = "hidden"
|
||||
task.hideUntil = currentTimeMillis() + 10000
|
||||
taskDao.createNew(task)
|
||||
|
||||
// create task with deadlines
|
||||
task = Task()
|
||||
task.title = "deadlineInFuture"
|
||||
task.dueDate = currentTimeMillis() + 10000
|
||||
taskDao.createNew(task)
|
||||
task = Task()
|
||||
task.title = "deadlineInPast"
|
||||
task.dueDate = currentTimeMillis() - 10000
|
||||
taskDao.createNew(task)
|
||||
|
||||
// create completed task
|
||||
task = Task()
|
||||
task.title = "completed"
|
||||
task.completionDate = currentTimeMillis() - 10000
|
||||
taskDao.createNew(task)
|
||||
|
||||
// check is active
|
||||
assertEquals(5, taskDao.getActiveTasks().size)
|
||||
|
||||
// check is visible
|
||||
assertEquals(5, taskDao.getActiveTasks().size)
|
||||
}
|
||||
|
||||
/** Test task deletion */
|
||||
@Test
|
||||
fun testTDeletion() = runBlocking {
|
||||
assertEquals(0, taskDao.getAll().size)
|
||||
|
||||
// create task "happy"
|
||||
val task = Task()
|
||||
task.title = "happy"
|
||||
taskDao.createNew(task)
|
||||
assertEquals(1, taskDao.getAll().size)
|
||||
|
||||
// delete
|
||||
taskDeleter.delete(task)
|
||||
assertEquals(0, taskDao.getAll().size)
|
||||
}
|
||||
|
||||
/** Test save without prior create doesn't work */
|
||||
@Test
|
||||
fun testSaveWithoutCreate() = runBlocking {
|
||||
// try to save task "happy"
|
||||
val task = Task()
|
||||
task.title = "happy"
|
||||
task.id = 1L
|
||||
taskDao.save(task)
|
||||
assertEquals(0, taskDao.getAll().size)
|
||||
}
|
||||
|
||||
/** Test passing invalid task indices to various things */
|
||||
@Test
|
||||
fun testInvalidIndex() = runBlocking {
|
||||
assertEquals(0, taskDao.getAll().size)
|
||||
assertNull(taskDao.fetch(1))
|
||||
taskDeleter.delete(listOf(1L))
|
||||
|
||||
// make sure db still works
|
||||
assertEquals(0, taskDao.getAll().size)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,281 @@
|
||||
package com.todoroo.astrid.data;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.Snippet;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static com.todoroo.astrid.data.Task.COMPLETION_DATE;
|
||||
import static com.todoroo.astrid.data.Task.DELETION_DATE;
|
||||
import static com.todoroo.astrid.data.Task.DUE_DATE;
|
||||
import static com.todoroo.astrid.data.Task.HIDE_UNTIL;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_DAY_AFTER;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_IN_TWO_WEEKS;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_NEXT_MONTH;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_NEXT_WEEK;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_NONE;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY_TIME;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_TODAY;
|
||||
import static com.todoroo.astrid.data.Task.URGENCY_TOMORROW;
|
||||
import static com.todoroo.astrid.data.Task.createDueDate;
|
||||
import static com.todoroo.astrid.data.Task.hasDueTime;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.tasks.Freeze.freezeAt;
|
||||
import static org.tasks.Freeze.thaw;
|
||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaskTest {
|
||||
|
||||
private static final DateTime now = new DateTime(2013, 12, 31, 16, 10, 53, 452);
|
||||
private static final DateTime specificDueDate = new DateTime(2014, 3, 17, 9, 54, 27, 959);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
freezeAt(now);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
thaw();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDueDateNoUrgency() {
|
||||
assertEquals(0, createDueDate(URGENCY_NONE, 1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDueDateToday() {
|
||||
long expected = new DateTime(2013, 12, 31, 12, 0, 0, 0).getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_TODAY, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDueDateTomorrow() {
|
||||
long expected = new DateTime(2014, 1, 1, 12, 0, 0, 0).getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_TOMORROW, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDueDateDayAfter() {
|
||||
long expected = new DateTime(2014, 1, 2, 12, 0, 0, 0).getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_DAY_AFTER, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDueDateNextWeek() {
|
||||
long expected = new DateTime(2014, 1, 7, 12, 0, 0, 0).getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_NEXT_WEEK, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDueDateInTwoWeeks() {
|
||||
long expected = new DateTime(2014, 1, 14, 12, 0, 0, 0).getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_IN_TWO_WEEKS, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDueDateNextMonth() {
|
||||
long expected = new DateTime(2014, 1, 31, 12, 0, 0, 0).getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_NEXT_MONTH, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveTimeForSpecificDay() {
|
||||
long expected = specificDueDate
|
||||
.withHourOfDay(12)
|
||||
.withMinuteOfHour(0)
|
||||
.withSecondOfMinute(0)
|
||||
.withMillisOfSecond(0)
|
||||
.getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_SPECIFIC_DAY, specificDueDate.getMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSecondsForSpecificTime() {
|
||||
long expected = specificDueDate
|
||||
.withSecondOfMinute(1)
|
||||
.withMillisOfSecond(0)
|
||||
.getMillis();
|
||||
assertEquals(expected, createDueDate(URGENCY_SPECIFIC_DAY_TIME, specificDueDate.getMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskHasDueTime() {
|
||||
Task task = new Task();
|
||||
task.setValue(DUE_DATE, 1388516076000L);
|
||||
assertTrue(task.hasDueTime());
|
||||
assertTrue(task.hasDueDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskHasDueDate() {
|
||||
Task task = new Task();
|
||||
task.setValue(DUE_DATE, 1388469600000L);
|
||||
assertFalse(task.hasDueTime());
|
||||
assertTrue(task.hasDueDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesHaveDueTime() {
|
||||
assertTrue(hasDueTime(1388516076000L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDueTime() {
|
||||
assertFalse(hasDueTime(newDateTime().startOfDay().getMillis()));
|
||||
assertFalse(hasDueTime(newDateTime().withMillisOfDay(60000).getMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasDueTime() {
|
||||
assertTrue(hasDueTime(newDateTime().withMillisOfDay(1).getMillis()));
|
||||
assertTrue(hasDueTime(newDateTime().withMillisOfDay(1000).getMillis()));
|
||||
assertTrue(hasDueTime(newDateTime().withMillisOfDay(59999).getMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotHaveDueTime() {
|
||||
assertFalse(hasDueTime(1388469600000L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewTaskIsNotCompleted() {
|
||||
assertFalse(new Task().isCompleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewTaskNotDeleted() {
|
||||
assertFalse(new Task().isDeleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewTaskNotHidden() {
|
||||
assertFalse(new Task().isHidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewTaskDoesNotHaveDueDateOrTime() {
|
||||
assertFalse(new Task().hasDueDate());
|
||||
assertFalse(new Task().hasDueTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskIsCompleted() {
|
||||
Task task = new Task();
|
||||
task.setValue(COMPLETION_DATE, 1L);
|
||||
assertTrue(task.isCompleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskIsNotHiddenAtHideUntilTime() {
|
||||
final long now = currentTimeMillis();
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
task.setValue(HIDE_UNTIL, now);
|
||||
assertFalse(task.isHidden());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskIsHiddenBeforeHideUntilTime() {
|
||||
final long now = currentTimeMillis();
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
task.setValue(HIDE_UNTIL, now + 1);
|
||||
assertTrue(task.isHidden());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskIsDeleted() {
|
||||
Task task = new Task();
|
||||
task.setValue(DELETION_DATE, 1L);
|
||||
assertTrue(task.isDeleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskWithNoDueDateIsOverdue() {
|
||||
assertTrue(new Task().isOverdue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskNotOverdueAtDueTime() {
|
||||
final long now = currentTimeMillis();
|
||||
freezeAt(now).thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
task.setValue(DUE_DATE, now);
|
||||
assertFalse(task.isOverdue());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskIsOverduePastDueTime() {
|
||||
final long dueDate = currentTimeMillis();
|
||||
freezeAt(dueDate + 1).thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
task.setValue(DUE_DATE, dueDate);
|
||||
assertTrue(task.isOverdue());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskNotOverdueBeforeNoonOnDueDate() {
|
||||
final DateTime dueDate = new DateTime().startOfDay();
|
||||
freezeAt(dueDate.plusHours(12).minusMillis(1)).thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
task.setValue(DUE_DATE, dueDate.getMillis());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.isOverdue());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskOverdueAtNoonOnDueDate() {
|
||||
final DateTime dueDate = new DateTime().startOfDay();
|
||||
freezeAt(dueDate.plusHours(12)).thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
task.setValue(DUE_DATE, dueDate.getMillis());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.isOverdue());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskWithNoDueTimeIsOverdue() {
|
||||
final DateTime dueDate = new DateTime().startOfDay();
|
||||
freezeAt(dueDate.plusDays(1)).thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
task.setValue(DUE_DATE, dueDate.getMillis());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertTrue(task.isOverdue());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanity() {
|
||||
assertTrue(Task.IMPORTANCE_DO_OR_DIE < Task.IMPORTANCE_MUST_DO);
|
||||
assertTrue(Task.IMPORTANCE_MUST_DO < Task.IMPORTANCE_SHOULD_DO);
|
||||
assertTrue(Task.IMPORTANCE_SHOULD_DO < Task.IMPORTANCE_NONE);
|
||||
|
||||
ArrayList<Integer> reminderFlags = new ArrayList<>();
|
||||
reminderFlags.add(Task.NOTIFY_AFTER_DEADLINE);
|
||||
reminderFlags.add(Task.NOTIFY_AT_DEADLINE);
|
||||
reminderFlags.add(Task.NOTIFY_MODE_NONSTOP);
|
||||
|
||||
// assert no duplicates
|
||||
assertEquals(new TreeSet<>(reminderFlags).size(), reminderFlags.size());
|
||||
}
|
||||
}
|
||||
@ -1,142 +0,0 @@
|
||||
package com.todoroo.astrid.gcal
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentUris
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.provider.CalendarContract
|
||||
import android.provider.CalendarContract.Calendars
|
||||
import android.provider.CalendarContract.Events
|
||||
import androidx.core.net.toUri
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.tasks.TestUtilities.withTZ
|
||||
import org.tasks.data.entity.Task
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.time.DateTime
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class GCalHelperTest : InjectingTestCase() {
|
||||
|
||||
@get:Rule
|
||||
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
|
||||
Manifest.permission.READ_CALENDAR,
|
||||
Manifest.permission.WRITE_CALENDAR
|
||||
)
|
||||
|
||||
@Inject lateinit var gcalHelper: GCalHelper
|
||||
|
||||
private var testCalendarId: Long = -1
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
testCalendarId = createTestCalendar()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
if (testCalendarId > 0) {
|
||||
try {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
context.contentResolver.delete(
|
||||
ContentUris.withAppendedId(Calendars.CONTENT_URI, testCalendarId),
|
||||
null,
|
||||
null
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun allDayEventInNewYork() = assertAllDayEvent("America/New_York") // UTC-5
|
||||
@Test fun allDayEventInBerlin() = assertAllDayEvent("Europe/Berlin") // UTC+1
|
||||
@Test fun allDayEventInAuckland() = assertAllDayEvent("Pacific/Auckland") // UTC+13
|
||||
@Test fun allDayEventInTokyo() = assertAllDayEvent("Asia/Tokyo") // UTC+9
|
||||
@Test fun allDayEventInHonolulu() = assertAllDayEvent("Pacific/Honolulu") // UTC-10
|
||||
@Test fun allDayEventInChatham() = assertAllDayEvent("Pacific/Chatham") // UTC+13:45
|
||||
|
||||
private fun assertAllDayEvent(timezone: String) = withTZ(timezone) {
|
||||
val task = Task(dueDate = DateTime(2024, 12, 20).millis)
|
||||
|
||||
val eventUri = gcalHelper.createTaskEvent(task, testCalendarId.toString())
|
||||
?: throw RuntimeException("Event not created")
|
||||
|
||||
val event = queryEvent(eventUri.toString()) ?: throw RuntimeException("Event not found")
|
||||
|
||||
assertEquals(
|
||||
"DTSTART should be Dec 20 00:00 UTC",
|
||||
DateTime(2024, 12, 20, timeZone = DateTime.UTC).millis,
|
||||
event.dtStart
|
||||
)
|
||||
assertEquals(
|
||||
"DTEND should be Dec 21 00:00 UTC",
|
||||
DateTime(2024, 12, 21, timeZone = DateTime.UTC).millis,
|
||||
event.dtEnd
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestCalendar(): Long {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val values = ContentValues().apply {
|
||||
put(Calendars.ACCOUNT_NAME, "test@test.com")
|
||||
put(Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
|
||||
put(Calendars.NAME, "Test Calendar")
|
||||
put(Calendars.CALENDAR_DISPLAY_NAME, "Test Calendar")
|
||||
put(Calendars.CALENDAR_COLOR, 0xFF0000)
|
||||
put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER)
|
||||
put(Calendars.OWNER_ACCOUNT, "test@test.com")
|
||||
put(Calendars.VISIBLE, 1)
|
||||
put(Calendars.SYNC_EVENTS, 1)
|
||||
}
|
||||
val uri = Calendars.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(Calendars.ACCOUNT_NAME, "test@test.com")
|
||||
.appendQueryParameter(Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
|
||||
.build()
|
||||
val calendarUri = context.contentResolver.insert(uri, values)
|
||||
return ContentUris.parseId(calendarUri!!)
|
||||
}
|
||||
|
||||
private fun queryEvent(eventUri: String): CalendarEvent? {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val cursor = context.contentResolver.query(
|
||||
eventUri.toUri(),
|
||||
arrayOf(
|
||||
Events.DTSTART,
|
||||
Events.DTEND,
|
||||
Events.ALL_DAY,
|
||||
Events.EVENT_TIMEZONE
|
||||
),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
return cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
CalendarEvent(
|
||||
dtStart = it.getLong(0),
|
||||
dtEnd = it.getLong(1),
|
||||
allDay = it.getInt(2) == 1,
|
||||
timezone = it.getString(3)
|
||||
)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
private data class CalendarEvent(
|
||||
val dtStart: Long,
|
||||
val dtEnd: Long,
|
||||
val allDay: Boolean,
|
||||
val timezone: String?
|
||||
)
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
package com.todoroo.astrid.gtasks
|
||||
|
||||
import com.google.api.services.tasks.model.TaskList
|
||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||
import com.todoroo.astrid.service.TaskDeleter
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
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.entity.CaldavAccount
|
||||
import org.tasks.data.entity.CaldavCalendar
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.makers.RemoteGtaskListMaker
|
||||
import org.tasks.makers.RemoteGtaskListMaker.newRemoteList
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class GtasksListServiceTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDeleter: TaskDeleter
|
||||
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
|
||||
@Inject lateinit var caldavDao: CaldavDao
|
||||
|
||||
private lateinit var gtasksListService: GtasksListService
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
gtasksListService = GtasksListService(caldavDao, taskDeleter, localBroadcastManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateNewList() = runBlocking {
|
||||
setLists(
|
||||
newRemoteList(
|
||||
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.NAME, "Default")))
|
||||
assertEquals(
|
||||
CaldavCalendar(id = 1, account = "account", uuid = "1", name = "Default"),
|
||||
caldavDao.getCalendarById(1L)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetListByRemoteId() = runBlocking {
|
||||
val list = CaldavCalendar(uuid = "1")
|
||||
caldavDao.insert(list)
|
||||
assertEquals(list, caldavDao.getCalendarByUuid("1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetListReturnsNullWhenNotFound() = runBlocking {
|
||||
assertNull(caldavDao.getCalendarByUuid("1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteMissingList() = runBlocking {
|
||||
caldavDao.insert(CaldavCalendar(account = "account", uuid = "1"))
|
||||
val taskList = newRemoteList(with(RemoteGtaskListMaker.REMOTE_ID, "2"))
|
||||
setLists(taskList)
|
||||
assertEquals(
|
||||
listOf(CaldavCalendar(id = 2, account = "account", uuid = "2", name = "Default")),
|
||||
caldavDao.getCalendarsByAccount("account")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateListName() = runBlocking {
|
||||
val calendar = CaldavCalendar(uuid = "1", name = "oldName", account = "account")
|
||||
caldavDao.insert(calendar)
|
||||
setLists(
|
||||
newRemoteList(
|
||||
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.NAME, "newName")))
|
||||
assertEquals("newName", caldavDao.getCalendarById(calendar.id)!!.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNewListLastSyncIsZero() = runBlocking {
|
||||
setLists(TaskList().setId("1"))
|
||||
assertEquals(0L, caldavDao.getCalendarByUuid("1")!!.lastSync)
|
||||
}
|
||||
|
||||
private suspend fun setLists(vararg list: TaskList) {
|
||||
val account = CaldavAccount(
|
||||
username = "account",
|
||||
uuid = "account",
|
||||
)
|
||||
caldavDao.insert(account)
|
||||
gtasksListService.updateLists(account, listOf(*list))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package com.todoroo.astrid.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.Snippet;
|
||||
import org.tasks.injection.InjectingTestCase;
|
||||
import org.tasks.injection.TestComponent;
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.tasks.Freeze.freezeClock;
|
||||
import static org.tasks.RemoteModelHelpers.asQueryProperties;
|
||||
import static org.tasks.RemoteModelHelpers.compareRemoteModel;
|
||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaskTest extends InjectingTestCase {
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
@Inject Preferences preferences;
|
||||
|
||||
@Test
|
||||
public void testNewTaskHasNoCreationDate() {
|
||||
assertFalse(new Task().containsValue(Task.CREATION_DATE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSavedTaskHasCreationDate() {
|
||||
freezeClock().thawAfter(new Snippet() {{
|
||||
Task task = new Task();
|
||||
taskDao.save(task);
|
||||
assertEquals(currentTimeMillis(), (long) task.getCreationDate());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadTaskFromDb() {
|
||||
Task task = new Task();
|
||||
taskDao.save(task);
|
||||
Property[] properties = asQueryProperties(Task.TABLE, task.getDatabaseValues());
|
||||
final Task fromDb = taskDao.fetch(task.getId(), properties);
|
||||
compareRemoteModel(task, fromDb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaults() {
|
||||
preferences.setDefaults();
|
||||
ContentValues defaults = new Task().getDefaultValues();
|
||||
assertTrue(defaults.containsKey(Task.TITLE.name));
|
||||
assertTrue(defaults.containsKey(Task.DUE_DATE.name));
|
||||
assertTrue(defaults.containsKey(Task.HIDE_UNTIL.name));
|
||||
assertTrue(defaults.containsKey(Task.COMPLETION_DATE.name));
|
||||
assertTrue(defaults.containsKey(Task.IMPORTANCE.name));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
package com.todoroo.astrid.model
|
||||
|
||||
import com.todoroo.astrid.dao.TaskDao
|
||||
import org.tasks.data.entity.Task
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.tasks.SuspendFreeze.Companion.freezeClock
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.time.DateTimeUtils2.currentTimeMillis
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class TaskTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
|
||||
@Test
|
||||
fun testSavedTaskHasCreationDate() = runBlocking {
|
||||
freezeClock {
|
||||
val task = Task()
|
||||
taskDao.createNew(task)
|
||||
assertEquals(currentTimeMillis(), task.creationDate)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReadTaskFromDb() = runBlocking {
|
||||
val task = Task()
|
||||
taskDao.createNew(task)
|
||||
val fromDb = taskDao.fetch(task.id)
|
||||
assertEquals(task, fromDb)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,376 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.provider;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.StoreObject;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.injection.TestComponent;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotSame;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class Astrid3ProviderTests extends DatabaseTestCase {
|
||||
|
||||
String[] PROJECTION = new String[] {
|
||||
Task.ID.name,
|
||||
Task.TITLE.name,
|
||||
};
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
|
||||
// set up database
|
||||
Astrid3ContentProvider.setDatabaseOverride(database);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
/** Test CRUD over tasks with the ALL ITEMS cursor */
|
||||
@Test
|
||||
public void testAllItemsCrud() {
|
||||
ContentResolver resolver = getTargetContext().getContentResolver();
|
||||
|
||||
// fetch all tasks, get nothing
|
||||
Uri uri = Task.CONTENT_URI;
|
||||
Cursor cursor = resolver.query(uri, PROJECTION, "1", null, null);
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
// insert a task
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Task.TITLE.name, "mf doom?");
|
||||
resolver.insert(uri, values);
|
||||
|
||||
// fetch all tasks, get something
|
||||
cursor = resolver.query(uri, PROJECTION, "1", null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToFirst();
|
||||
assertEquals("mf doom?", cursor.getString(1));
|
||||
cursor.close();
|
||||
|
||||
// update all tasks
|
||||
values.put(Task.TITLE.name, "mf grimm?");
|
||||
resolver.update(uri, values, "1", null);
|
||||
|
||||
// fetch all tasks, get something
|
||||
cursor = resolver.query(uri, PROJECTION, "1", null, null);
|
||||
cursor.moveToFirst();
|
||||
assertEquals("mf grimm?", cursor.getString(1));
|
||||
cursor.close();
|
||||
|
||||
// delete a task
|
||||
assertEquals(1, resolver.delete(uri, "1", null));
|
||||
|
||||
// fetch all tasks, get nothing
|
||||
cursor = resolver.query(uri, PROJECTION, null, null, null);
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
/** Test selecting data */
|
||||
@Test
|
||||
public void testSelection() {
|
||||
ContentResolver resolver = getTargetContext().getContentResolver();
|
||||
Uri uri = Task.CONTENT_URI;
|
||||
|
||||
// insert some tasks
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Task.TITLE.name, "tujiko noriko");
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_MUST_DO);
|
||||
resolver.insert(uri, values);
|
||||
|
||||
values.clear();
|
||||
values.put(Task.TITLE.name, "miho asahi");
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_NONE);
|
||||
resolver.insert(uri, values);
|
||||
|
||||
// fetch all tasks with various selection parameters
|
||||
Cursor cursor = resolver.query(uri, PROJECTION, "1", null, null);
|
||||
assertEquals(2, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
cursor = resolver.query(uri, PROJECTION, null, null, null);
|
||||
assertEquals(2, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
cursor = resolver.query(uri, PROJECTION, Task.IMPORTANCE + "=" +
|
||||
Task.IMPORTANCE_MUST_DO, null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToFirst();
|
||||
assertEquals("tujiko noriko", cursor.getString(1));
|
||||
cursor.close();
|
||||
|
||||
cursor = resolver.query(uri, PROJECTION, Task.IMPORTANCE + ">" +
|
||||
Task.IMPORTANCE_MUST_DO, null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToFirst();
|
||||
assertEquals("miho asahi", cursor.getString(1));
|
||||
cursor.close();
|
||||
|
||||
cursor = resolver.query(uri, PROJECTION, Task.IMPORTANCE + "=" +
|
||||
Task.IMPORTANCE_DO_OR_DIE, null, null);
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
/** Test updating */
|
||||
@Test
|
||||
public void testUpdating() {
|
||||
ContentResolver resolver = getTargetContext().getContentResolver();
|
||||
|
||||
// insert some tasks
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Task.TITLE.name, "carlos silva");
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_SHOULD_DO);
|
||||
|
||||
Uri carlosUri = resolver.insert(Task.CONTENT_URI, values);
|
||||
|
||||
values.clear();
|
||||
values.put(Task.TITLE.name, "felix hernandez");
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_MUST_DO);
|
||||
resolver.insert(Task.CONTENT_URI, values);
|
||||
|
||||
String[] projection = new String[] {
|
||||
Task.ID.name,
|
||||
Task.TITLE.name,
|
||||
Task.IMPORTANCE.name,
|
||||
};
|
||||
|
||||
// test updating with single item URI
|
||||
Cursor cursor = resolver.query(Task.CONTENT_URI, projection,
|
||||
Task.TITLE.eq("carlos who?").toString(), null, null);
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
values.clear();
|
||||
values.put(Task.TITLE.name, "carlos who?");
|
||||
assertEquals(1, resolver.update(carlosUri, values, null, null));
|
||||
|
||||
cursor = resolver.query(Task.CONTENT_URI, projection, Task.TITLE.eq("carlos who?").toString(), null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
// test updating with all items uri
|
||||
cursor = resolver.query(Task.CONTENT_URI, PROJECTION,
|
||||
Task.IMPORTANCE.eq(Task.IMPORTANCE_NONE).toString(), null, null);
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
values.clear();
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_NONE);
|
||||
assertEquals(1, resolver.update(Task.CONTENT_URI, values,
|
||||
Task.IMPORTANCE.eq(Task.IMPORTANCE_SHOULD_DO).toString(), null));
|
||||
|
||||
cursor = resolver.query(Task.CONTENT_URI, PROJECTION,
|
||||
Task.IMPORTANCE.eq(Task.IMPORTANCE_NONE).toString(), null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
// test updating with group by uri
|
||||
try {
|
||||
Uri groupByUri = Uri.withAppendedPath(Task.CONTENT_URI,
|
||||
AstridApiConstants.GROUP_BY_URI + Task.TITLE.name);
|
||||
resolver.update(groupByUri, values, null, null);
|
||||
fail("Able to update using groupby uri");
|
||||
} catch (Exception e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/** Test deleting */
|
||||
@Test
|
||||
public void testDeleting() {
|
||||
ContentResolver resolver = getTargetContext().getContentResolver();
|
||||
Uri allItemsUri = Task.CONTENT_URI;
|
||||
|
||||
// insert some tasks
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Task.TITLE.name, "modest mouse");
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_DO_OR_DIE);
|
||||
Uri modestMouse = resolver.insert(allItemsUri, values);
|
||||
|
||||
values.clear();
|
||||
values.put(Task.TITLE.name, "death cab");
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_MUST_DO);
|
||||
resolver.insert(allItemsUri, values);
|
||||
|
||||
values.clear();
|
||||
values.put(Task.TITLE.name, "murder city devils");
|
||||
values.put(Task.IMPORTANCE.name, Task.IMPORTANCE_SHOULD_DO);
|
||||
resolver.insert(allItemsUri, values);
|
||||
|
||||
// test deleting with single URI
|
||||
Cursor cursor = resolver.query(allItemsUri, PROJECTION, Task.TITLE.name +
|
||||
" = 'modest mouse'", null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
assertEquals(1, resolver.delete(modestMouse, null, null));
|
||||
|
||||
cursor = resolver.query(allItemsUri, PROJECTION, Task.TITLE.name +
|
||||
" = 'modest mouse'", null, null);
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
// test updating with all items uri
|
||||
cursor = resolver.query(allItemsUri, PROJECTION, Task.TITLE.name +
|
||||
" = 'murder city devils'", null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
assertEquals(1, resolver.delete(allItemsUri, Task.IMPORTANCE.name +
|
||||
">" + Task.IMPORTANCE_MUST_DO, null));
|
||||
|
||||
cursor = resolver.query(allItemsUri, PROJECTION, Task.TITLE.name +
|
||||
" = 'murder city devils'", null, null);
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
// test with group by uri
|
||||
try {
|
||||
Uri groupByUri = Uri.withAppendedPath(Task.CONTENT_URI,
|
||||
AstridApiConstants.GROUP_BY_URI + Task.TITLE.name);
|
||||
resolver.delete(groupByUri, null, null);
|
||||
fail("Able to delete using groupby uri");
|
||||
} catch (Exception e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/** Test CRUD over SINGLE ITEM uri */
|
||||
@Test
|
||||
public void testSingleItemCrud() {
|
||||
ContentResolver resolver = getTargetContext().getContentResolver();
|
||||
|
||||
Uri uri = StoreObject.CONTENT_URI;
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(StoreObject.TYPE.name, "rapper");
|
||||
values.put(StoreObject.ITEM.name, "mf doom?");
|
||||
Uri firstUri = resolver.insert(uri, values);
|
||||
|
||||
values.put(StoreObject.ITEM.name, "gm grimm!");
|
||||
Uri secondUri = resolver.insert(uri, values);
|
||||
assertNotSame(firstUri, secondUri);
|
||||
|
||||
String[] storeProjection = new String[] {
|
||||
StoreObject.ITEM.name,
|
||||
};
|
||||
|
||||
|
||||
Cursor cursor = resolver.query(uri, storeProjection, null, null, null);
|
||||
assertEquals(2, cursor.getCount());
|
||||
cursor.close();
|
||||
|
||||
cursor = resolver.query(firstUri, storeProjection, null, null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToFirst();
|
||||
assertEquals("mf doom?", cursor.getString(0));
|
||||
cursor.close();
|
||||
|
||||
values.put(StoreObject.ITEM.name, "danger mouse.");
|
||||
resolver.update(firstUri, values, null, null);
|
||||
cursor = resolver.query(firstUri, storeProjection, null, null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToFirst();
|
||||
assertEquals("danger mouse.", cursor.getString(0));
|
||||
cursor.close();
|
||||
|
||||
assertEquals(1, resolver.delete(firstUri, null, null));
|
||||
|
||||
cursor = resolver.query(uri, storeProjection, null, null, null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
/** Test GROUP BY uri */
|
||||
@Test
|
||||
public void testGroupByCrud() {
|
||||
ContentResolver resolver = getTargetContext().getContentResolver();
|
||||
Uri uri = Task.CONTENT_URI;
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Task.TITLE.name, "catwoman");
|
||||
resolver.insert(uri, values);
|
||||
|
||||
values.put(Task.TITLE.name, "the joker");
|
||||
resolver.insert(uri, values);
|
||||
resolver.insert(uri, values);
|
||||
resolver.insert(uri, values);
|
||||
|
||||
values.put(Task.TITLE.name, "deep freeze");
|
||||
resolver.insert(uri, values);
|
||||
resolver.insert(uri, values);
|
||||
|
||||
Uri groupByUri = Uri.withAppendedPath(Task.CONTENT_URI,
|
||||
AstridApiConstants.GROUP_BY_URI + Task.TITLE.name);
|
||||
Cursor cursor = resolver.query(groupByUri, PROJECTION, null, null, Task.TITLE.name);
|
||||
assertEquals(3, cursor.getCount());
|
||||
cursor.moveToFirst();
|
||||
assertEquals("catwoman", cursor.getString(1));
|
||||
cursor.moveToNext();
|
||||
assertEquals("deep freeze", cursor.getString(1));
|
||||
cursor.moveToNext();
|
||||
assertEquals("the joker", cursor.getString(1));
|
||||
cursor.close();
|
||||
|
||||
// test "group-by" with metadata
|
||||
IntegerProperty age = new IntegerProperty(Metadata.TABLE, Metadata.VALUE1.name);
|
||||
StringProperty size = Metadata.VALUE2;
|
||||
|
||||
uri = Metadata.CONTENT_URI;
|
||||
|
||||
values.clear();
|
||||
values.put(Metadata.TASK.name, 1);
|
||||
values.put(Metadata.KEY.name, "sizes");
|
||||
values.put(age.name, 50);
|
||||
values.put(size.name, "large");
|
||||
resolver.insert(uri, values);
|
||||
|
||||
values.put(age.name, 40);
|
||||
values.put(size.name, "large");
|
||||
resolver.insert(uri, values);
|
||||
|
||||
values.put(age.name, 20);
|
||||
values.put(size.name, "small");
|
||||
resolver.insert(uri, values);
|
||||
|
||||
String[] metadataProjection = new String[] { "AVG(" + age + ")" };
|
||||
|
||||
Uri groupBySizeUri = Uri.withAppendedPath(Metadata.CONTENT_URI,
|
||||
AstridApiConstants.GROUP_BY_URI + size.name);
|
||||
cursor = resolver.query(groupBySizeUri, metadataProjection, null, null, size.name);
|
||||
assertEquals(2, cursor.getCount());
|
||||
|
||||
cursor.moveToFirst();
|
||||
assertEquals(45, cursor.getInt(0));
|
||||
cursor.moveToNext();
|
||||
assertEquals(20, cursor.getInt(0));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.LocalBroadcastManager;
|
||||
import org.tasks.Notifier;
|
||||
import org.tasks.injection.TestComponent;
|
||||
import org.tasks.notifications.NotificationManager;
|
||||
import org.tasks.themes.ThemeCache;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.Subcomponent;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationTests extends DatabaseTestCase {
|
||||
|
||||
@Module
|
||||
public static class NotificationTestsModule {
|
||||
private final NotificationManager notificationManager = mock(NotificationManager.class);
|
||||
private final LocalBroadcastManager localBroadcastManager = mock(LocalBroadcastManager.class);
|
||||
private final Context context;
|
||||
|
||||
public NotificationTestsModule(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public NotificationManager getNotificationManager() {
|
||||
return notificationManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public LocalBroadcastManager getBroadcaster() {
|
||||
return localBroadcastManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public ThemeCache getThemeCache() {
|
||||
return new ThemeCache(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Subcomponent(modules = NotificationTestsModule.class)
|
||||
public interface NotificationTestsComponent {
|
||||
void inject(NotificationTests notificationTests);
|
||||
}
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
@Inject NotificationManager notificationManager;
|
||||
@Inject LocalBroadcastManager localBroadcastManager;
|
||||
@Inject Notifier notifier;
|
||||
|
||||
@Override
|
||||
public void tearDown() {
|
||||
super.tearDown();
|
||||
|
||||
verifyNoMoreInteractions(notificationManager);
|
||||
verifyNoMoreInteractions(localBroadcastManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlarmToNotification() {
|
||||
final Task task = new Task() {{
|
||||
setTitle("rubberduck");
|
||||
setDueDate(DateUtilities.now() - DateUtilities.ONE_DAY);
|
||||
}};
|
||||
|
||||
taskDao.persist(task);
|
||||
|
||||
notifier.triggerTaskNotification(task.getId(), ReminderService.TYPE_DUE);
|
||||
|
||||
verify(notificationManager).notify(eq((int) task.getId()), any(NotificationCompat.Builder.class), true, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletedTaskDoesntTriggerNotification() {
|
||||
final Task task = new Task() {{
|
||||
setTitle("gooeyduck");
|
||||
setDeletionDate(DateUtilities.now());
|
||||
}};
|
||||
taskDao.persist(task);
|
||||
|
||||
notifier.triggerTaskNotification(task.getId(),ReminderService.TYPE_DUE);
|
||||
|
||||
verify(notificationManager).cancel((int) task.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompletedTaskDoesntTriggerNotification() {
|
||||
final Task task = new Task() {{
|
||||
setTitle("rubberduck");
|
||||
setCompletionDate(DateUtilities.now());
|
||||
}};
|
||||
taskDao.persist(task);
|
||||
|
||||
notifier.triggerTaskNotification(task.getId(), ReminderService.TYPE_DUE);
|
||||
|
||||
verify(notificationManager).cancel((int) task.getId());
|
||||
}
|
||||
|
||||
// public void testQuietHours() {
|
||||
// final Task task = new Task();
|
||||
// task.setTitle("rubberduck");
|
||||
// taskDao.persist(task);
|
||||
// Intent intent = new Intent();
|
||||
// intent.putExtra(TaskNotificationReceiver.ID_KEY, task.getId());
|
||||
//
|
||||
// int hour = newDate().getHours();
|
||||
// Preferences.setStringFromInteger(R.string.p_rmd_quietStart, hour - 1);
|
||||
// Preferences.setStringFromInteger(R.string.p_rmd_quietEnd, hour + 1);
|
||||
//
|
||||
// // due date notification has vibrate
|
||||
// TaskNotificationReceiver.setNotificationManager(new TestNotificationManager() {
|
||||
// public void notify(int id, Notification notification) {
|
||||
// assertNull(notification.sound);
|
||||
// assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
|
||||
// assertNotNull(notification.vibrate);
|
||||
// assertTrue(notification.vibrate.length > 0);
|
||||
// }
|
||||
// });
|
||||
// intent.putExtra(TaskNotificationReceiver.EXTRAS_TYPE, ReminderService.TYPE_DUE);
|
||||
// notificationReceiver.onReceive(getContext(), intent);
|
||||
//
|
||||
// // random notification does not
|
||||
// TaskNotificationReceiver.setNotificationManager(new TestNotificationManager() {
|
||||
// public void notify(int id, Notification notification) {
|
||||
// assertNull(notification.sound);
|
||||
// assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
|
||||
// assertTrue(notification.vibrate == null ||
|
||||
// notification.vibrate.length == 0);
|
||||
// }
|
||||
// });
|
||||
// intent.removeExtra(TaskNotificationReceiver.EXTRAS_TYPE);
|
||||
// intent.putExtra(TaskNotificationReceiver.EXTRAS_TYPE, ReminderService.TYPE_RANDOM);
|
||||
// notificationReceiver.onReceive(getContext(), intent);
|
||||
//
|
||||
// // wrapping works
|
||||
// Preferences.setStringFromInteger(R.string.p_rmd_quietStart, hour + 2);
|
||||
// Preferences.setStringFromInteger(R.string.p_rmd_quietEnd, hour + 1);
|
||||
//
|
||||
// TaskNotificationReceiver.setNotificationManager(new TestNotificationManager() {
|
||||
// public void notify(int id, Notification notification) {
|
||||
// assertNull(notification.sound);
|
||||
// assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
|
||||
// }
|
||||
// });
|
||||
// intent.removeExtra(TaskNotificationReceiver.EXTRAS_TYPE);
|
||||
// intent.putExtra(TaskNotificationReceiver.EXTRAS_TYPE, ReminderService.TYPE_DUE);
|
||||
// notificationReceiver.onReceive(getContext(), intent);
|
||||
//
|
||||
// // nonstop notification still sounds
|
||||
// task.setReminderFlags(Task.NOTIFY_MODE_NONSTOP);
|
||||
// taskDao.persist(task);
|
||||
// TaskNotificationReceiver.setNotificationManager(new TestNotificationManager() {
|
||||
// public void notify(int id, Notification notification) {
|
||||
// assertTrue(notification.sound != null ||
|
||||
// (notification.defaults & Notification.DEFAULT_SOUND) > 0);
|
||||
// }
|
||||
// });
|
||||
// notificationReceiver.onReceive(getContext(), intent);
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component
|
||||
.plus(new NotificationTestsModule(getTargetContext()))
|
||||
.inject(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,368 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.tasks.R;
|
||||
import org.tasks.Snippet;
|
||||
import org.tasks.injection.InjectingTestCase;
|
||||
import org.tasks.injection.TestComponent;
|
||||
import org.tasks.jobs.JobQueue;
|
||||
import org.tasks.jobs.Reminder;
|
||||
import org.tasks.preferences.Preferences;
|
||||
import org.tasks.reminders.Random;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static com.natpryce.makeiteasy.MakeItEasy.with;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.ONE_HOUR;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.ONE_WEEK;
|
||||
import static com.todoroo.astrid.data.Task.NOTIFY_AFTER_DEADLINE;
|
||||
import static com.todoroo.astrid.data.Task.NOTIFY_AT_DEADLINE;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.tasks.Freeze.freezeClock;
|
||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
||||
import static org.tasks.makers.TaskMaker.COMPLETION_TIME;
|
||||
import static org.tasks.makers.TaskMaker.CREATION_TIME;
|
||||
import static org.tasks.makers.TaskMaker.DELETION_TIME;
|
||||
import static org.tasks.makers.TaskMaker.DUE_DATE;
|
||||
import static org.tasks.makers.TaskMaker.DUE_TIME;
|
||||
import static org.tasks.makers.TaskMaker.ID;
|
||||
import static org.tasks.makers.TaskMaker.RANDOM_REMINDER_PERIOD;
|
||||
import static org.tasks.makers.TaskMaker.REMINDERS;
|
||||
import static org.tasks.makers.TaskMaker.REMINDER_LAST;
|
||||
import static org.tasks.makers.TaskMaker.SNOOZE_TIME;
|
||||
import static org.tasks.makers.TaskMaker.newTask;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ReminderServiceTest extends InjectingTestCase {
|
||||
|
||||
@Inject Preferences preferences;
|
||||
|
||||
private ReminderService service;
|
||||
private Random random;
|
||||
private JobQueue jobs;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
jobs = mock(JobQueue.class);
|
||||
random = mock(Random.class);
|
||||
when(random.nextFloat()).thenReturn(1.0f);
|
||||
preferences.reset();
|
||||
service = new ReminderService(preferences, jobs, random);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
verifyNoMoreInteractions(jobs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontScheduleDueDateReminderWhenFlagNotSet() {
|
||||
service.scheduleAlarm(null, newTask(with(ID, 1L), with(DUE_TIME, newDateTime())));
|
||||
|
||||
verify(jobs).cancelReminder(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontScheduleDueDateReminderWhenTimeNotSet() {
|
||||
service.scheduleAlarm(null, newTask(with(ID, 1L), with(REMINDERS, NOTIFY_AT_DEADLINE)));
|
||||
|
||||
verify(jobs).cancelReminder(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void schedulePastDueDate() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, newDateTime().minusDays(1)),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleFutureDueDate() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, newDateTime().plusDays(1)),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleReminderAtDefaultDueTime() {
|
||||
DateTime now = newDateTime();
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_DATE, now),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1, now.startOfDay().withHourOfDay(18).getMillis(), ReminderService.TYPE_DUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontScheduleReminderForCompletedTask() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, newDateTime().plusDays(1)),
|
||||
with(COMPLETION_TIME, newDateTime()),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
verify(jobs).cancelReminder(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontScheduleReminderForDeletedTask() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, newDateTime().plusDays(1)),
|
||||
with(DELETION_TIME, newDateTime()),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
verify(jobs).cancelReminder(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontScheduleDueDateReminderWhenAlreadyReminded() {
|
||||
DateTime now = newDateTime();
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, now),
|
||||
with(REMINDER_LAST, now.plusSeconds(1)),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
verify(jobs).cancelReminder(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoreStaleSnoozeTime() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, newDateTime()),
|
||||
with(SNOOZE_TIME, newDateTime().minusMinutes(5)),
|
||||
with(REMINDER_LAST, newDateTime().minusMinutes(4)),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontIgnoreMissedSnoozeTime() {
|
||||
DateTime dueDate = newDateTime();
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, dueDate),
|
||||
with(SNOOZE_TIME, dueDate.minusMinutes(4)),
|
||||
with(REMINDER_LAST, dueDate.minusMinutes(5)),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE));
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1, task.getReminderSnooze(), ReminderService.TYPE_SNOOZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleInitialRandomReminder() {
|
||||
freezeClock().thawAfter(new Snippet() {{
|
||||
DateTime now = newDateTime();
|
||||
when(random.nextFloat()).thenReturn(0.3865f);
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(REMINDER_LAST, (DateTime) null),
|
||||
with(CREATION_TIME, now.minusDays(1)),
|
||||
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleNextRandomReminder() {
|
||||
freezeClock().thawAfter(new Snippet() {{
|
||||
DateTime now = newDateTime();
|
||||
when(random.nextFloat()).thenReturn(0.3865f);
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(REMINDER_LAST, now.minusDays(1)),
|
||||
with(CREATION_TIME, now.minusDays(30)),
|
||||
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleOverdueRandomReminder() {
|
||||
freezeClock().thawAfter(new Snippet() {{
|
||||
DateTime now = newDateTime();
|
||||
when(random.nextFloat()).thenReturn(0.3865f);
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(REMINDER_LAST, now.minusDays(14)),
|
||||
with(CREATION_TIME, now.minusDays(30)),
|
||||
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, now.getMillis() + 10148400, ReminderService.TYPE_RANDOM));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleOverdueNoLastReminder() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
|
||||
with(REMINDER_LAST, (DateTime) null),
|
||||
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleOverduePastLastReminder() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
|
||||
with(REMINDER_LAST, new DateTime(2017, 9, 24, 12, 0)),
|
||||
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 24, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleOverdueBeforeLastReminder() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, new DateTime(2017, 9, 22, 12, 30)),
|
||||
with(REMINDER_LAST, new DateTime(2017, 9, 24, 15, 0)),
|
||||
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 25, 12, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleOverdueWithNoDueTime() {
|
||||
preferences.setInt(R.string.p_rmd_time, (int) TimeUnit.HOURS.toMillis(15));
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_DATE, new DateTime(2017, 9, 22)),
|
||||
with(REMINDER_LAST, new DateTime(2017, 9, 23, 12, 17, 59, 999)),
|
||||
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 0, 0, 0).getMillis(), ReminderService.TYPE_OVERDUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleSubsequentOverdueReminder() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
|
||||
with(REMINDER_LAST, new DateTime(2017, 9, 23, 15, 30, 59, 999)),
|
||||
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 24, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scheduleOverdueAfterLastReminder() {
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
|
||||
with(REMINDER_LAST, new DateTime(2017, 9, 23, 12, 17, 59, 999)),
|
||||
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void snoozeOverridesAll() {
|
||||
DateTime now = newDateTime();
|
||||
Task task = newTask(
|
||||
with(ID, 1L),
|
||||
with(DUE_TIME, now),
|
||||
with(SNOOZE_TIME, now.plusMonths(12)),
|
||||
with(REMINDERS, NOTIFY_AT_DEADLINE | NOTIFY_AFTER_DEADLINE),
|
||||
with(RANDOM_REMINDER_PERIOD, ONE_HOUR));
|
||||
|
||||
service.scheduleAlarm(null, task);
|
||||
|
||||
InOrder order = inOrder(jobs);
|
||||
order.verify(jobs).cancelReminder(1);
|
||||
order.verify(jobs).add(new Reminder(1, now.plusMonths(12).getMillis(), ReminderService.TYPE_SNOOZE));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.repeats;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.ical.values.Frequency;
|
||||
import com.google.ical.values.RRule;
|
||||
import com.google.ical.values.Weekday;
|
||||
import com.google.ical.values.WeekdayNum;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AdvancedRepeatTest {
|
||||
|
||||
private static final int PREV_PREV = -2;
|
||||
private static final int PREV = -1;
|
||||
private static final int THIS = 1;
|
||||
private static final int NEXT = 2;
|
||||
|
||||
private Task task;
|
||||
private long nextDueDate;
|
||||
private RRule rrule;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
task = new Task();
|
||||
task.setCompletionDate(DateUtilities.now());
|
||||
rrule = new RRule();
|
||||
}
|
||||
|
||||
// --- date with time tests
|
||||
|
||||
@Test
|
||||
public void testDueDateSpecificTime() throws ParseException {
|
||||
buildRRule(1, Frequency.DAILY);
|
||||
|
||||
// test specific day & time
|
||||
long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new DateTime(2010, 8, 1, 10, 4, 0).getMillis());
|
||||
task.setDueDate(dayWithTime);
|
||||
|
||||
long nextDayWithTime = dayWithTime + DateUtilities.ONE_DAY;
|
||||
nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), false);
|
||||
assertDateTimeEquals(nextDayWithTime, nextDueDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompletionDateSpecificTime() throws ParseException {
|
||||
buildRRule(1, Frequency.DAILY);
|
||||
|
||||
// test specific day & time
|
||||
long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new DateTime(2010, 8, 1, 10, 4, 0).getMillis());
|
||||
task.setDueDate(dayWithTime);
|
||||
|
||||
DateTime todayWithTime = newDateTime()
|
||||
.withHourOfDay(10)
|
||||
.withMinuteOfHour(4)
|
||||
.withSecondOfMinute(1);
|
||||
long nextDayWithTimeLong = todayWithTime.getMillis();
|
||||
nextDayWithTimeLong += DateUtilities.ONE_DAY;
|
||||
nextDayWithTimeLong = nextDayWithTimeLong / 1000L * 1000;
|
||||
|
||||
nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), true);
|
||||
assertDateTimeEquals(nextDayWithTimeLong, nextDueDate);
|
||||
}
|
||||
|
||||
// --- due date tests
|
||||
|
||||
/** test multiple days per week - DUE DATE */
|
||||
@Test
|
||||
public void testDueDateInPastSingleWeekMultiDay() throws Exception {
|
||||
buildRRule(1, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.SUNDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.MONDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.FRIDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
|
||||
}
|
||||
|
||||
/** test single day repeats - DUE DATE */
|
||||
@Test
|
||||
public void testDueDateSingleDay() throws Exception {
|
||||
buildRRule(1, Frequency.WEEKLY, Weekday.MO);
|
||||
|
||||
setTaskDueDate(PREV_PREV, Calendar.MONDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(PREV_PREV, Calendar.FRIDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(PREV, Calendar.MONDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(PREV, Calendar.FRIDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.SUNDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.MONDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
|
||||
}
|
||||
|
||||
/** test multiple days per week - DUE DATE */
|
||||
@Test
|
||||
public void testDueDateSingleWeekMultiDay() throws Exception {
|
||||
|
||||
buildRRule(1, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.SUNDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.MONDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.FRIDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
|
||||
}
|
||||
|
||||
/** test multiple days per week, multiple intervals - DUE DATE */
|
||||
@Test
|
||||
public void testDueDateMultiWeekMultiDay() throws Exception {
|
||||
buildRRule(2, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.SUNDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.MONDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
|
||||
|
||||
setTaskDueDate(THIS, Calendar.FRIDAY);
|
||||
computeNextDueDate(false);
|
||||
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
|
||||
}
|
||||
|
||||
// --- completion tests
|
||||
|
||||
/** test multiple days per week - COMPLETE DATE */
|
||||
@Test
|
||||
public void testCompleteDateSingleWeek() throws Exception {
|
||||
for(Weekday wday : Weekday.values()) {
|
||||
buildRRule(1, Frequency.WEEKLY, wday);
|
||||
computeNextDueDate(true);
|
||||
long expected = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday.javaDayNum);
|
||||
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
|
||||
assertEquals(expected, nextDueDate);
|
||||
}
|
||||
|
||||
for(Weekday wday1 : Weekday.values()) {
|
||||
for(Weekday wday2 : Weekday.values()) {
|
||||
if(wday1 == wday2)
|
||||
continue;
|
||||
|
||||
buildRRule(1, Frequency.WEEKLY, wday1, wday2);
|
||||
long nextOne = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday1.javaDayNum);
|
||||
long nextTwo = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday2.javaDayNum);
|
||||
computeNextDueDate(true);
|
||||
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
|
||||
assertEquals(Math.min(nextOne, nextTwo), nextDueDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** test multiple days per week, multiple intervals - COMPLETE DATE */
|
||||
@Test
|
||||
public void testCompleteDateMultiWeek() throws Exception {
|
||||
for(Weekday wday : Weekday.values()) {
|
||||
buildRRule(2, Frequency.WEEKLY, wday);
|
||||
computeNextDueDate(true);
|
||||
long expected = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday.javaDayNum);
|
||||
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
|
||||
assertEquals(expected, nextDueDate);
|
||||
}
|
||||
|
||||
for(Weekday wday1 : Weekday.values()) {
|
||||
for(Weekday wday2 : Weekday.values()) {
|
||||
if(wday1 == wday2)
|
||||
continue;
|
||||
|
||||
buildRRule(2, Frequency.WEEKLY, wday1, wday2);
|
||||
long nextOne = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday1.javaDayNum);
|
||||
long nextTwo = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday2.javaDayNum);
|
||||
computeNextDueDate(true);
|
||||
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
|
||||
assertEquals(Math.min(nextOne, nextTwo), nextDueDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- helpers
|
||||
|
||||
private void computeNextDueDate(boolean fromComplete) throws ParseException{
|
||||
nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), fromComplete);
|
||||
}
|
||||
|
||||
private void buildRRule(int interval, Frequency freq, Weekday... weekdays) {
|
||||
rrule.setInterval(interval);
|
||||
rrule.setFreq(freq);
|
||||
setRRuleDays(rrule, weekdays);
|
||||
}
|
||||
|
||||
private void assertDueDate(long actual, int expectedWhich, int expectedDayOfWeek) {
|
||||
long expected = getDate(task.getDueDate(), expectedWhich, expectedDayOfWeek);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
public static void assertDateTimeEquals(long date, long other) {
|
||||
assertEquals("Expected: " + newDateTime(date) + ", Actual: " + newDateTime(other),
|
||||
date, other);
|
||||
}
|
||||
|
||||
private void setRRuleDays(RRule rrule, Weekday... weekdays) {
|
||||
ArrayList<WeekdayNum> days = new ArrayList<>();
|
||||
for(Weekday wd : weekdays)
|
||||
days.add(new WeekdayNum(0, wd));
|
||||
rrule.setByDay(days);
|
||||
}
|
||||
|
||||
private void setTaskDueDate(int which, int day) {
|
||||
long time = getDate(DateUtilities.now(), which, day);
|
||||
|
||||
task.setDueDate(time);
|
||||
}
|
||||
|
||||
private long getDate(long start, int which, int dayOfWeek) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(start);
|
||||
int direction = which > 0 ? 1 : -1;
|
||||
|
||||
while(c.get(Calendar.DAY_OF_WEEK) != dayOfWeek) {
|
||||
c.add(Calendar.DAY_OF_MONTH, direction);
|
||||
}
|
||||
c.add(Calendar.DAY_OF_MONTH, (Math.abs(which) - 1) * direction * 7);
|
||||
return Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, c.getTimeInMillis());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,259 @@
|
||||
package com.todoroo.astrid.repeats;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.ical.values.Frequency;
|
||||
import com.google.ical.values.RRule;
|
||||
import com.google.ical.values.Weekday;
|
||||
import com.google.ical.values.WeekdayNum;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import static com.todoroo.astrid.repeats.RepeatTaskHelper.computeNextDueDate;
|
||||
import static java.util.Arrays.asList;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NewRepeatTests {
|
||||
|
||||
@Test
|
||||
public void testRepeatMinutelyFromDueDate() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30);
|
||||
Task task = newFromDue(Frequency.MINUTELY, 1, dueDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 26, 12, 31), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatHourlyFromDueDate() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30);
|
||||
Task task = newFromDue(Frequency.HOURLY, 1, dueDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 26, 13, 30), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatDailyFromDueDate() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30);
|
||||
Task task = newFromDue(Frequency.DAILY, 1, dueDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 27, 12, 30), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatWeeklyFromDueDate() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 34);
|
||||
Task task = newFromDue(Frequency.WEEKLY, 1, dueDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 9, 4, 1, 34), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatMonthlyFromDueDate() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 44);
|
||||
Task task = newFromDue(Frequency.MONTHLY, 1, dueDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 9, 28, 1, 44), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatYearlyFromDueDate() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 44);
|
||||
Task task = newFromDue(Frequency.YEARLY, 1, dueDateTime);
|
||||
|
||||
assertEquals(newDayTime(2017, 8, 28, 1, 44), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
/** Tests for repeating from completionDate */
|
||||
|
||||
@Test
|
||||
public void testRepeatMinutelyFromCompleteDateCompleteBefore() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.MINUTELY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 29, 0, 15), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatMinutelyFromCompleteDateCompleteAfter() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.MINUTELY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 29, 0, 15), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatHourlyFromCompleteDateCompleteBefore() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.HOURLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 29, 1, 14), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatHourlyFromCompleteDateCompleteAfter() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.HOURLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 29, 1, 14), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatDailyFromCompleteDateCompleteBefore() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.DAILY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 30, 0, 25), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatDailyFromCompleteDateCompleteAfter() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.DAILY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 30, 0, 4), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatWeeklyFromCompleteDateCompleteBefore() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.WEEKLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 9, 5, 0, 25), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatWeeklyFromCompleteDateCompleteAfter() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.WEEKLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 9, 5, 0, 4), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatMonthlyFromCompleteDateCompleteBefore() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.MONTHLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 9, 29, 0, 25), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatMonthlyFromCompleteDateCompleteAfter() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.MONTHLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2016, 9, 29, 0, 4), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatYearlyFromCompleteDateCompleteBefore() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.YEARLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2017, 8, 29, 0, 25), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatYearlyFromCompleteDateCompleteAfter() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
|
||||
Task task = newFromCompleted(Frequency.YEARLY, 1, dueDateTime, completionDateTime);
|
||||
|
||||
assertEquals(newDayTime(2017, 8, 29, 0, 4), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdvancedRepeatWeeklyFromDueDate() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
|
||||
Task task = newWeeklyFromDue(1, dueDateTime, new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 31, 0, 25), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdvancedRepeatWeeklyFromCompleteDateCompleteBefore() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 8, 28, 1, 9);
|
||||
Task task = newWeeklyFromCompleted(1, dueDateTime, completionDateTime, new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
|
||||
|
||||
assertEquals(newDayTime(2016, 8, 29, 0, 25), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdvancedRepeatWeeklyFromCompleteDateCompleteAfter() throws ParseException {
|
||||
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
|
||||
DateTime completionDateTime = newDayTime(2016, 9, 1, 1, 9);
|
||||
Task task = newWeeklyFromCompleted(1, dueDateTime, completionDateTime, new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
|
||||
|
||||
assertEquals(newDayTime(2016, 9, 5, 0, 25), calculateNextDueDate(task));
|
||||
}
|
||||
|
||||
private DateTime newDayTime(int year, int month, int day, int hour, int minute) {
|
||||
return new DateTime(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new DateTime(year, month, day, hour, minute).getMillis()));
|
||||
}
|
||||
|
||||
private DateTime calculateNextDueDate(Task task) throws ParseException {
|
||||
return new DateTime(computeNextDueDate(task, task.sanitizedRecurrence(), task.repeatAfterCompletion()));
|
||||
}
|
||||
|
||||
private Task newFromDue(Frequency frequency, int interval, DateTime dueDate) {
|
||||
return new Task() {{
|
||||
setRecurrence(getRecurrenceRule(frequency, interval, false));
|
||||
setDueDate(dueDate.getMillis());
|
||||
}};
|
||||
}
|
||||
|
||||
private Task newWeeklyFromDue(int interval, DateTime dueDate, WeekdayNum... weekdays) {
|
||||
return new Task() {{
|
||||
setRecurrence(getRecurrenceRule(Frequency.WEEKLY, interval, false, weekdays));
|
||||
setDueDate(dueDate.getMillis());
|
||||
}};
|
||||
}
|
||||
|
||||
private Task newFromCompleted(Frequency frequency, int interval, DateTime dueDate, DateTime completionDate) {
|
||||
return new Task() {{
|
||||
setRecurrence(getRecurrenceRule(frequency, interval, true));
|
||||
setDueDate(dueDate.getMillis());
|
||||
setCompletionDate(completionDate.getMillis());
|
||||
}};
|
||||
}
|
||||
|
||||
private Task newWeeklyFromCompleted(int interval, DateTime dueDate, DateTime completionDate, WeekdayNum... weekdays) {
|
||||
return new Task() {{
|
||||
setRecurrence(getRecurrenceRule(Frequency.WEEKLY, interval, true, weekdays));
|
||||
setDueDate(dueDate.getMillis());
|
||||
setCompletionDate(completionDate.getMillis());
|
||||
}};
|
||||
}
|
||||
|
||||
private String getRecurrenceRule(Frequency frequency, int interval, boolean fromCompletion, WeekdayNum... weekdays) {
|
||||
RRule rrule = new RRule();
|
||||
rrule.setFreq(frequency);
|
||||
rrule.setInterval(interval);
|
||||
if (weekdays != null) {
|
||||
rrule.setByDay(asList(weekdays));
|
||||
}
|
||||
String result = rrule.toIcal();
|
||||
if (fromCompletion) {
|
||||
result += ";FROM=COMPLETION";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
package com.todoroo.astrid.repeats;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.ical.values.Frequency;
|
||||
import com.google.ical.values.RRule;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import static com.google.ical.values.Frequency.DAILY;
|
||||
import static com.google.ical.values.Frequency.HOURLY;
|
||||
import static com.google.ical.values.Frequency.MINUTELY;
|
||||
import static com.google.ical.values.Frequency.WEEKLY;
|
||||
import static com.google.ical.values.Frequency.YEARLY;
|
||||
import static com.todoroo.andlib.utility.DateUtilities.addCalendarMonthsToUnixtime;
|
||||
import static com.todoroo.astrid.repeats.RepeatTaskHelper.computeNextDueDate;
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RepeatTaskHelperTest {
|
||||
|
||||
private final Task task = new Task();
|
||||
private final long dueDate;
|
||||
private final long completionDate;
|
||||
|
||||
{
|
||||
completionDate = new DateTime(2014, 1, 7, 17, 17, 32, 900).getMillis();
|
||||
dueDate = new DateTime(2013, 12, 31, 17, 17, 32, 900).getMillis();
|
||||
task.setDueDate(dueDate);
|
||||
task.setCompletionDate(completionDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinutelyRepeat() {
|
||||
checkFrequency(6, MINUTES.toMillis(1), MINUTELY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHourlyRepeat() {
|
||||
checkFrequency(6, HOURS.toMillis(1), HOURLY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDailyRepeat() {
|
||||
checkFrequency(6, DAYS.toMillis(1), DAILY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeeklyRepeat() {
|
||||
checkFrequency(6, DAYS.toMillis(7), WEEKLY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMonthlyRepeat() {
|
||||
assertEquals(
|
||||
new DateTime(2014, 7, 7, 17, 17, 1, 0).getMillis(),
|
||||
nextDueDate(6, Frequency.MONTHLY, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMonthlyRepeatAtEndOfMonth() {
|
||||
assertEquals(
|
||||
new DateTime(2014, 6, 30, 17, 17, 1, 0).getMillis(),
|
||||
nextDueDate(6, Frequency.MONTHLY, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testYearlyRepeat() {
|
||||
checkExpected(6, addCalendarMonthsToUnixtime(dueDate, 6 * 12), YEARLY, false);
|
||||
checkExpected(6, addCalendarMonthsToUnixtime(completionDate, 6 * 12), YEARLY, true);
|
||||
}
|
||||
|
||||
private void checkFrequency(int count, long interval, Frequency frequency) {
|
||||
checkExpected(count, dueDate + count * interval, frequency, false);
|
||||
checkExpected(count, completionDate + count * interval, frequency, true);
|
||||
}
|
||||
|
||||
private void checkExpected(int count, long expected, Frequency frequency, boolean repeatAfterCompletion) {
|
||||
assertEquals(
|
||||
new DateTime(expected)
|
||||
.withSecondOfMinute(1)
|
||||
.withMillisOfSecond(0)
|
||||
.getMillis(),
|
||||
nextDueDate(count, frequency, repeatAfterCompletion));
|
||||
}
|
||||
|
||||
private long nextDueDate(int count, Frequency frequency, boolean repeatAfterCompletion) {
|
||||
RRule rrule = new RRule();
|
||||
rrule.setInterval(count);
|
||||
rrule.setFreq(frequency);
|
||||
try {
|
||||
return computeNextDueDate(task, rrule.toIcal(), repeatAfterCompletion);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package com.todoroo.astrid.repeats
|
||||
|
||||
import org.tasks.data.entity.Task
|
||||
import com.todoroo.astrid.service.TaskCompleter
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
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.injection.InjectingTestCase
|
||||
import org.tasks.time.DateTimeUtils2.currentTimeMillis
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class RepeatWithSubtasksTests : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var taskCompleter: TaskCompleter
|
||||
|
||||
@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(),
|
||||
)
|
||||
)
|
||||
|
||||
assertTrue(taskDao.fetch(child)!!.isCompleted)
|
||||
|
||||
taskCompleter.setComplete(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)
|
||||
|
||||
taskCompleter.setComplete(parent)
|
||||
|
||||
assertFalse(taskDao.fetch(child)!!.isCompleted)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.service;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.injection.TestComponent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class QuickAddMarkupTest extends DatabaseTestCase {
|
||||
|
||||
@Inject TaskCreator taskCreator;
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTags() {
|
||||
whenTitleIs("this #cool");
|
||||
assertTitleBecomes("this");
|
||||
assertTagsAre("cool");
|
||||
|
||||
whenTitleIs("#cool task");
|
||||
assertTitleBecomes("task");
|
||||
assertTagsAre("cool");
|
||||
|
||||
whenTitleIs("doggie #nice #cute");
|
||||
assertTitleBecomes("doggie");
|
||||
assertTagsAre("nice", "cute");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContexts() {
|
||||
whenTitleIs("eat @home");
|
||||
assertTitleBecomes("eat");
|
||||
assertTagsAre("home");
|
||||
|
||||
whenTitleIs("buy oatmeal @store @morning");
|
||||
assertTitleBecomes("buy oatmeal");
|
||||
assertTagsAre("store", "morning");
|
||||
|
||||
whenTitleIs("look @ me");
|
||||
assertTitleBecomes("look @ me");
|
||||
assertTagsAre();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportances() {
|
||||
whenTitleIs("eat !1");
|
||||
assertTitleBecomes("eat");
|
||||
assertImportanceIs(Task.IMPORTANCE_SHOULD_DO);
|
||||
|
||||
whenTitleIs("super cool!");
|
||||
assertTitleBecomes("super cool!");
|
||||
|
||||
whenTitleIs("stay alive !4");
|
||||
assertTitleBecomes("stay alive");
|
||||
assertImportanceIs(Task.IMPORTANCE_DO_OR_DIE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixed() {
|
||||
whenTitleIs("eat #food !2");
|
||||
assertTitleBecomes("eat");
|
||||
assertTagsAre("food");
|
||||
assertImportanceIs(Task.IMPORTANCE_MUST_DO);
|
||||
}
|
||||
|
||||
// --- helpers
|
||||
|
||||
private Task task;
|
||||
private final ArrayList<String> tags = new ArrayList<>();
|
||||
|
||||
private void assertTagsAre(String... expectedTags) {
|
||||
List<String> expected = Arrays.asList(expectedTags);
|
||||
assertEquals(expected.toString(), tags.toString());
|
||||
}
|
||||
|
||||
private void assertTitleBecomes(String title) {
|
||||
assertEquals(title, task.getTitle());
|
||||
}
|
||||
|
||||
private void whenTitleIs(String title) {
|
||||
task = new Task();
|
||||
task.setTitle(title);
|
||||
tags.clear();
|
||||
taskCreator.parseQuickAddMarkup(task, tags);
|
||||
}
|
||||
|
||||
private void assertImportanceIs(int importance) {
|
||||
assertEquals(importance, (int)task.getImportance());
|
||||
}
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.service
|
||||
|
||||
import org.tasks.data.entity.Task
|
||||
import com.todoroo.astrid.utility.TitleParser
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.tasks.data.dao.TagDataDao
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class QuickAddMarkupTest : InjectingTestCase() {
|
||||
private val tags = ArrayList<String>()
|
||||
@Inject lateinit var tagDataDao: TagDataDao
|
||||
|
||||
private var task: Task? = null
|
||||
|
||||
@Test
|
||||
fun testTags() {
|
||||
whenTitleIs("this #cool")
|
||||
assertTitleBecomes("this")
|
||||
assertTagsAre("cool")
|
||||
whenTitleIs("#cool task")
|
||||
assertTitleBecomes("task")
|
||||
assertTagsAre("cool")
|
||||
whenTitleIs("doggie #nice #cute")
|
||||
assertTitleBecomes("doggie")
|
||||
assertTagsAre("nice", "cute")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testContexts() {
|
||||
whenTitleIs("eat @home")
|
||||
assertTitleBecomes("eat")
|
||||
assertTagsAre("home")
|
||||
whenTitleIs("buy oatmeal @store @morning")
|
||||
assertTitleBecomes("buy oatmeal")
|
||||
assertTagsAre("store", "morning")
|
||||
whenTitleIs("look @ me")
|
||||
assertTitleBecomes("look @ me")
|
||||
assertTagsAre()
|
||||
}
|
||||
|
||||
// --- helpers
|
||||
@Test
|
||||
fun testPriorities() {
|
||||
whenTitleIs("eat !1")
|
||||
assertTitleBecomes("eat")
|
||||
assertPriority(Task.Priority.LOW)
|
||||
whenTitleIs("super cool!")
|
||||
assertTitleBecomes("super cool!")
|
||||
whenTitleIs("stay alive !4")
|
||||
assertTitleBecomes("stay alive")
|
||||
assertPriority(Task.Priority.HIGH)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMixed() {
|
||||
whenTitleIs("eat #food !2")
|
||||
assertTitleBecomes("eat")
|
||||
assertTagsAre("food")
|
||||
assertPriority(Task.Priority.MEDIUM)
|
||||
}
|
||||
|
||||
private fun assertTagsAre(vararg expectedTags: String) {
|
||||
val expected = listOf(*expectedTags)
|
||||
assertEquals(expected.toString(), tags.toString())
|
||||
}
|
||||
|
||||
private fun assertTitleBecomes(title: String) {
|
||||
assertEquals(title, task!!.title)
|
||||
}
|
||||
|
||||
private fun whenTitleIs(title: String) = runBlocking {
|
||||
task = Task()
|
||||
task!!.title = title
|
||||
tags.clear()
|
||||
TitleParser.parse(tagDataDao, task!!, tags)
|
||||
}
|
||||
|
||||
private fun assertPriority(priority: Int) {
|
||||
assertEquals(priority, task!!.priority)
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
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 dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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.preferences.Preferences
|
||||
import org.tasks.time.DateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class TaskCreatorTest : InjectingTestCase() {
|
||||
@Inject lateinit var preferences: Preferences
|
||||
@Inject lateinit var taskCreator: TaskCreator
|
||||
|
||||
@Test
|
||||
fun setStartAndDueFromFilter() = runBlocking {
|
||||
val task = freezeAt(DateTime(2021, 2, 4, 14, 56, 34, 126)) {
|
||||
taskCreator.create(mapOf(
|
||||
HIDE_UNTIL.name!! to VALUE_EOD,
|
||||
DUE_DATE.name!! to VALUE_EOD_TOMORROW
|
||||
), null)
|
||||
}
|
||||
|
||||
assertEquals(DateTime(2021, 2, 4).millis, task.hideUntil)
|
||||
assertEquals(
|
||||
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
|
||||
task.dueDate
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setDefaultStartWithFilterDue() = runBlocking {
|
||||
preferences.setString(R.string.p_default_hideUntil_key, Task.HIDE_UNTIL_DUE.toString())
|
||||
val task = freezeAt(DateTime(2021, 2, 4, 14, 56, 34, 126)) {
|
||||
taskCreator.create(mapOf(
|
||||
DUE_DATE.name!! to VALUE_EOD
|
||||
), null)
|
||||
}
|
||||
|
||||
assertEquals(DateTime(2021, 2, 4).millis, task.hideUntil)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setStartAndDueFromPreferences() = runBlocking {
|
||||
preferences.setString(R.string.p_default_urgency_key, Task.URGENCY_TODAY.toString())
|
||||
preferences.setString(R.string.p_default_hideUntil_key, Task.HIDE_UNTIL_DUE.toString())
|
||||
|
||||
val task = freezeAt(DateTime(2021, 2, 4, 14, 56, 34, 126)) {
|
||||
taskCreator.create(null, "test")
|
||||
}
|
||||
|
||||
assertEquals(DateTime(2021, 2, 4).millis, task.hideUntil)
|
||||
assertEquals(
|
||||
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 4).millis),
|
||||
task.dueDate
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterStartOverridesDefaultStart() = runBlocking {
|
||||
preferences.setString(R.string.p_default_urgency_key, Task.URGENCY_TODAY.toString())
|
||||
preferences.setString(R.string.p_default_hideUntil_key, Task.HIDE_UNTIL_DUE.toString())
|
||||
|
||||
val task = freezeAt(DateTime(2021, 2, 4, 14, 56, 34, 126)) {
|
||||
taskCreator.create(mapOf(
|
||||
HIDE_UNTIL.name!! to VALUE_EOD_NEXT_WEEK
|
||||
), null)
|
||||
}
|
||||
|
||||
assertEquals(DateTime(2021, 2, 11).millis, task.hideUntil)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterDueOverridesDefaultDue() = runBlocking {
|
||||
preferences.setString(R.string.p_default_urgency_key, Task.URGENCY_TODAY.toString())
|
||||
|
||||
val task = freezeAt(DateTime(2021, 2, 4, 14, 56, 34, 126)) {
|
||||
taskCreator.create(mapOf(
|
||||
DUE_DATE.name!! to VALUE_EOD_TOMORROW
|
||||
), null)
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
createDueDate(URGENCY_SPECIFIC_DAY, DateTime(2021, 2, 5).millis),
|
||||
task.dueDate
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
package com.todoroo.astrid.service
|
||||
|
||||
import org.tasks.data.entity.Task
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
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.injection.InjectingTestCase
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class TaskDeleterTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var taskDeleter: TaskDeleter
|
||||
|
||||
@Test
|
||||
fun markTaskAsDeleted() = runBlocking {
|
||||
val task = Task()
|
||||
taskDao.createNew(task)
|
||||
|
||||
taskDeleter.markDeleted(task)
|
||||
|
||||
assertTrue(taskDao.fetch(task.id)!!.isDeleted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dontDeleteReadOnlyTasks() = runBlocking {
|
||||
val task = Task(
|
||||
readOnly = true
|
||||
)
|
||||
taskDao.createNew(task)
|
||||
|
||||
taskDeleter.markDeleted(task)
|
||||
|
||||
assertFalse(taskDao.fetch(task.id)!!.isDeleted)
|
||||
}
|
||||
}
|
||||
@ -1,329 +0,0 @@
|
||||
package com.todoroo.astrid.service
|
||||
|
||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||
import com.todoroo.astrid.dao.TaskDao
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
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.filters.CaldavFilter
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
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.TaskMaker.ID
|
||||
import org.tasks.makers.TaskMaker.PARENT
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class TaskMoverTest : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var googleTaskDao: GoogleTaskDao
|
||||
@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")))
|
||||
moveToGoogleTasks("2", 1)
|
||||
assertEquals("2", googleTaskDao.getByTaskId(1)?.calendar)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteGoogleTaskAfterMove() = runBlocking {
|
||||
createTasks(1)
|
||||
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
|
||||
moveToGoogleTasks("2", 1)
|
||||
val deleted = googleTaskDao.getDeletedByTaskId(1, "account1")
|
||||
assertEquals(1, deleted.size.toLong())
|
||||
assertEquals(1, deleted[0].task)
|
||||
assertTrue(deleted[0].deleted > 0)
|
||||
}
|
||||
|
||||
@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")))
|
||||
moveToGoogleTasks("2", 1)
|
||||
val deleted = googleTaskDao.getDeletedByTaskId(2, "account1")
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveBetweenCaldavList() = runBlocking {
|
||||
createTasks(1)
|
||||
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
|
||||
moveToCaldavList("2", 1)
|
||||
assertEquals("2", caldavDao.getTask(1)!!.calendar)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteCaldavTaskAfterMove() = runBlocking {
|
||||
createTasks(1)
|
||||
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
|
||||
moveToCaldavList("2", 1)
|
||||
val deleted = caldavDao.getMoved("1")
|
||||
assertEquals(1, deleted.size.toLong())
|
||||
assertEquals(1, deleted[0].task)
|
||||
assertTrue(deleted[0].deleted > 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveRecursiveCaldavChildren() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
createSubtask(3, 2)
|
||||
caldavDao.insert(
|
||||
listOf(
|
||||
newCaldavTask(
|
||||
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
|
||||
newCaldavTask(
|
||||
with(TASK, 2L),
|
||||
with(CALENDAR, "1"),
|
||||
with(REMOTE_ID, "b"),
|
||||
with(REMOTE_PARENT, "a")),
|
||||
newCaldavTask(
|
||||
with(TASK, 3L),
|
||||
with(CALENDAR, "1"),
|
||||
with(REMOTE_PARENT, "b"))))
|
||||
moveToCaldavList("2", 1)
|
||||
val deleted = caldavDao.getMoved("1")
|
||||
assertEquals(3, deleted.size.toLong())
|
||||
val task = caldavDao.getTask(3)
|
||||
assertEquals("2", task!!.calendar)
|
||||
assertEquals(2, taskDao.fetch(3)!!.parent)
|
||||
}
|
||||
|
||||
@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")))
|
||||
moveToCaldavList("2", 1)
|
||||
val task = caldavDao.getTask(2)
|
||||
assertEquals("2", task!!.calendar)
|
||||
assertEquals(1L, taskDao.fetch(2)?.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun flattenLocalSubtasksWhenMovingToGoogleTasks() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
createSubtask(3, 2)
|
||||
moveToGoogleTasks("1", 1)
|
||||
assertEquals(1L, taskDao.fetch(3)?.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveLocalChildToGoogleTasks() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
moveToGoogleTasks("1", 2)
|
||||
assertEquals(0L, taskDao.fetch(2)?.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveLocalChildToCaldav() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
moveToCaldavList("1", 2)
|
||||
assertEquals(0, taskDao.fetch(2)!!.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun flattenCaldavSubtasksWhenMovingToGoogleTasks() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
createSubtask(3, 2)
|
||||
caldavDao.insert(
|
||||
listOf(
|
||||
newCaldavTask(
|
||||
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
|
||||
newCaldavTask(
|
||||
with(TASK, 2L),
|
||||
with(CALENDAR, "1"),
|
||||
with(REMOTE_ID, "b"),
|
||||
with(REMOTE_PARENT, "a")),
|
||||
newCaldavTask(
|
||||
with(TASK, 3L),
|
||||
with(CALENDAR, "1"),
|
||||
with(REMOTE_PARENT, "b"))))
|
||||
moveToGoogleTasks("2", 1)
|
||||
val task = taskDao.fetch(3L)
|
||||
assertEquals(1L, 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")))
|
||||
moveToGoogleTasks("2", 2)
|
||||
assertEquals(0L, taskDao.fetch(2)?.parent)
|
||||
assertEquals("2", googleTaskDao.getByTaskId(2)?.calendar)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveCaldavChildWithoutParent() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
caldavDao.insert(
|
||||
listOf(
|
||||
newCaldavTask(
|
||||
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
|
||||
newCaldavTask(
|
||||
with(TASK, 2L),
|
||||
with(CALENDAR, "1"),
|
||||
with(REMOTE_PARENT, "a"))))
|
||||
moveToCaldavList("2", 2)
|
||||
assertEquals("2", caldavDao.getTask(2)!!.calendar)
|
||||
assertEquals(0, taskDao.fetch(2)!!.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveGoogleTaskToCaldav() = runBlocking {
|
||||
createTasks(1)
|
||||
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "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")))
|
||||
moveToGoogleTasks("2", 1)
|
||||
assertEquals("2", googleTaskDao.getByTaskId(1L)?.calendar)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveLocalToCaldav() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
createSubtask(3, 2)
|
||||
moveToCaldavList("1", 1)
|
||||
assertEquals("1", caldavDao.getTask(3)?.calendar)
|
||||
assertEquals(2L, taskDao.fetch(3)?.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveToSameGoogleTaskListIsNoop() = runBlocking {
|
||||
setAccountType("account1", TYPE_GOOGLE_TASKS)
|
||||
createTasks(1)
|
||||
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
|
||||
moveToGoogleTasks("1", 1)
|
||||
assertTrue(googleTaskDao.getDeletedByTaskId(1, "account1").isEmpty())
|
||||
assertEquals(1, googleTaskDao.getAllByTaskId(1).size.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveToSameCaldavListIsNoop() = runBlocking {
|
||||
createTasks(1)
|
||||
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
|
||||
moveToCaldavList("1", 1)
|
||||
assertTrue(caldavDao.getMoved("1").isEmpty())
|
||||
assertEquals(1, caldavDao.getTasks(1).size.toLong())
|
||||
}
|
||||
|
||||
@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")))
|
||||
moveToGoogleTasks("2", 1, 2)
|
||||
assertEquals(1, googleTaskDao.getAllByTaskId(2).filter { it.deleted == 0L }.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dontDuplicateWhenParentAndChildCaldavMoved() = runBlocking {
|
||||
createTasks(1)
|
||||
createSubtask(2, 1)
|
||||
caldavDao.insert(
|
||||
listOf(
|
||||
newCaldavTask(
|
||||
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
|
||||
newCaldavTask(
|
||||
with(TASK, 2L),
|
||||
with(CALENDAR, "1"),
|
||||
with(REMOTE_PARENT, "a"))))
|
||||
moveToCaldavList("2", 1, 2)
|
||||
assertEquals(1, caldavDao.getTasks(2).filter { it.deleted == 0L }.size)
|
||||
}
|
||||
|
||||
private suspend fun createTasks(vararg ids: Long) {
|
||||
for (id in ids) {
|
||||
taskDao.createNew(newTask(with(ID, id)))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createSubtask(id: Long, parent: Long) {
|
||||
taskDao.createNew(newTask(with(ID, id), with(PARENT, parent)))
|
||||
}
|
||||
|
||||
private suspend fun moveToGoogleTasks(list: String, vararg tasks: Long) {
|
||||
taskMover.move(
|
||||
tasks.toList(),
|
||||
CaldavFilter(
|
||||
calendar = CaldavCalendar(uuid = list),
|
||||
account = CaldavAccount(accountType = TYPE_GOOGLE_TASKS)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun moveToCaldavList(calendar: String, vararg tasks: Long) {
|
||||
taskMover.move(
|
||||
tasks.toList(),
|
||||
CaldavFilter(
|
||||
CaldavCalendar(name = "", uuid = calendar),
|
||||
account = CaldavAccount(accountType = TYPE_CALDAV)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun setAccountType(account: String, type: Int) {
|
||||
caldavDao.insert(
|
||||
CaldavAccount(
|
||||
uuid = account,
|
||||
accountType = type,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,604 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.service;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.ical.values.Frequency;
|
||||
import com.google.ical.values.RRule;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.tags.TagService;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
import com.todoroo.astrid.utility.TitleParser;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.R;
|
||||
import org.tasks.injection.TestComponent;
|
||||
import org.tasks.preferences.Preferences;
|
||||
import org.tasks.time.DateTime;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNotSame;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TitleParserTest extends DatabaseTestCase {
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
@Inject TagService tagService;
|
||||
@Inject Preferences preferences;
|
||||
@Inject TaskCreator taskCreator;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
preferences.setStringFromInteger(R.string.p_default_urgency_key, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
/** test that completing a task w/ no regular expressions creates a simple task with no date, no repeat, no lists*/
|
||||
@Test
|
||||
public void testNoRegexes() throws Exception{
|
||||
Task task = new Task();
|
||||
Task nothing = new Task();
|
||||
task.setTitle("Jog");
|
||||
taskDao.save(task);
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
assertEquals(task.getRecurrence(), nothing.getRecurrence());
|
||||
}
|
||||
|
||||
/** Tests correct date is parsed **/
|
||||
@Test
|
||||
public void testMonthDate() {
|
||||
Task task = new Task();
|
||||
String[] titleMonthStrings = {
|
||||
"Jan.", "January",
|
||||
"Feb.", "February",
|
||||
"Mar.", "March",
|
||||
"Apr.", "April",
|
||||
"May", "May",
|
||||
"Jun.", "June",
|
||||
"Jul.", "July",
|
||||
"Aug.", "August",
|
||||
"Sep.", "September",
|
||||
"Oct.", "October",
|
||||
"Nov.", "November",
|
||||
"Dec.", "December"
|
||||
};
|
||||
for (int i = 0; i < 23; i++) {
|
||||
String testTitle = "Jog on " + titleMonthStrings[i] + " 12.";
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getMonthOfYear(), i/2 + 1);
|
||||
assertEquals(date.getDayOfMonth(), 12);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMonthSlashDay() {
|
||||
Task task = new Task();
|
||||
for (int i = 1; i < 13; i++) {
|
||||
String testTitle = "Jog on " + i + "/12/13";
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getMonthOfYear(), i);
|
||||
assertEquals(date.getDayOfMonth(), 12);
|
||||
assertEquals(date.getYear(), 2013);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArmyTime() {
|
||||
Task task = new Task();
|
||||
String testTitle = "Jog on 23:21.";
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getHourOfDay(), 23);
|
||||
assertEquals(date.getMinuteOfHour(), 21);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_AM_PM() {
|
||||
Task task = new Task();
|
||||
String testTitle = "Jog at 8:33 PM.";
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getHourOfDay(), 20);
|
||||
assertEquals(date.getMinuteOfHour(), 33);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_at_hour() {
|
||||
Task task = new Task();
|
||||
String testTitle = "Jog at 8 PM.";
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getHourOfDay(), 20);
|
||||
assertEquals(date.getMinuteOfHour(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_oclock_AM() {
|
||||
Task task = new Task();
|
||||
String testTitle = "Jog at 8 o'clock AM.";
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getHourOfDay(), 8);
|
||||
assertEquals(date.getMinuteOfHour(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_several_forms_of_eight() {
|
||||
Task task = new Task();
|
||||
String[] testTitles = {
|
||||
"Jog 8 AM",
|
||||
"Jog 8 o'clock AM",
|
||||
"at 8:00 AM"
|
||||
};
|
||||
for (String testTitle: testTitles) {
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getHourOfDay(), 8);
|
||||
assertEquals(date.getMinuteOfHour(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_several_forms_of_1230PM() {
|
||||
Task task = new Task();
|
||||
String[] testTitles = {
|
||||
"Jog 12:30 PM",
|
||||
"at 12:30 PM",
|
||||
"Do something on 12:30 PM",
|
||||
"Jog at 12:30 PM Friday"
|
||||
|
||||
};
|
||||
for (String testTitle: testTitles) {
|
||||
insertTitleAddTask(testTitle, task);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getHourOfDay(), 12);
|
||||
assertEquals(date.getMinuteOfHour(), 30);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertTitleAddTask(String title, Task task) {
|
||||
task.clear();
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
}
|
||||
|
||||
|
||||
// ----------------Days begin----------------//
|
||||
@Test
|
||||
public void testDays() {
|
||||
Calendar today = Calendar.getInstance();
|
||||
Task task = new Task();
|
||||
|
||||
String title = "Jog today";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
DateTime date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getDayOfWeek(), today.get(Calendar.DAY_OF_WEEK));
|
||||
//Calendar starts 1-6, date.getDay() starts at 0
|
||||
|
||||
task = new Task();
|
||||
title = "Jog tomorrow";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
date = newDateTime(task.getDueDate());
|
||||
assertEquals((date.getDayOfWeek()) % 7, (today.get(Calendar.DAY_OF_WEEK)+1) % 7);
|
||||
|
||||
String[] days = {
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
};
|
||||
String[] abrevDays = {
|
||||
"sun.",
|
||||
"mon.",
|
||||
"tue.",
|
||||
"wed.",
|
||||
"thu.",
|
||||
"fri.",
|
||||
"sat."
|
||||
};
|
||||
|
||||
for (int i = 1; i <= 6; i++){
|
||||
task = new Task();
|
||||
title = "Jog "+ days[i];
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getDayOfWeek(), i + 1);
|
||||
|
||||
task = new Task();
|
||||
title = "Jog "+ abrevDays[i];
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
date = newDateTime(task.getDueDate());
|
||||
assertEquals(date.getDayOfWeek(), i + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//----------------Days end----------------//
|
||||
|
||||
|
||||
//----------------Priority begin----------------//
|
||||
/** tests all words using priority 0 */
|
||||
@Test
|
||||
public void testPriority0() throws Exception {
|
||||
String[] acceptedStrings = {
|
||||
"priority 0",
|
||||
"least priority",
|
||||
"lowest priority",
|
||||
"bang 0"
|
||||
};
|
||||
Task task;
|
||||
for (String acceptedString:acceptedStrings){
|
||||
task = new Task();
|
||||
String title = "Jog " + acceptedString;
|
||||
task.setTitle(title); //test at end of task. should set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_NONE);
|
||||
}
|
||||
for (String acceptedString:acceptedStrings){
|
||||
task = new Task();
|
||||
String title = acceptedString + " jog";
|
||||
task.setTitle(title); //test at beginning of task. should not set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertNotSame(task.getImportance(),Task.IMPORTANCE_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPriority1() throws Exception {
|
||||
String[] acceptedStringsAtEnd = {
|
||||
"priority 1",
|
||||
"low priority",
|
||||
"bang",
|
||||
"bang 1"
|
||||
};
|
||||
String[] acceptedStringsAnywhere = {
|
||||
"!1",
|
||||
"!"
|
||||
};
|
||||
Task task;
|
||||
for (String acceptedStringAtEnd:acceptedStringsAtEnd){
|
||||
task = new Task();
|
||||
task.setTitle("Jog " + acceptedStringAtEnd); //test at end of task. should set importance.
|
||||
taskDao.save(task);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_SHOULD_DO);
|
||||
}
|
||||
for (String acceptedStringAtEnd:acceptedStringsAtEnd){
|
||||
task = new Task();
|
||||
task.setTitle(acceptedStringAtEnd + " jog"); //test at beginning of task. should not set importance.
|
||||
taskDao.save(task);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_SHOULD_DO);
|
||||
}
|
||||
for (String acceptedStringAnywhere:acceptedStringsAnywhere){
|
||||
task = new Task();
|
||||
task.setTitle("Jog " + acceptedStringAnywhere); //test at end of task. should set importance.
|
||||
taskDao.save(task);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_SHOULD_DO);
|
||||
|
||||
task.setTitle(acceptedStringAnywhere + " jog"); //test at beginning of task. should set importance.
|
||||
taskDao.save(task);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_SHOULD_DO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPriority2() throws Exception {
|
||||
String[] acceptedStringsAtEnd = {
|
||||
"priority 2",
|
||||
"high priority",
|
||||
"bang bang",
|
||||
"bang 2"
|
||||
};
|
||||
String[] acceptedStringsAnywhere = {
|
||||
"!2",
|
||||
"!!"
|
||||
};
|
||||
Task task;
|
||||
for (String acceptedStringAtEnd:acceptedStringsAtEnd){
|
||||
task = new Task();
|
||||
String title = "Jog " + acceptedStringAtEnd;
|
||||
task.setTitle(title); //test at end of task. should set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_MUST_DO);
|
||||
|
||||
task = new Task();
|
||||
title = acceptedStringAtEnd + " jog";
|
||||
task.setTitle(title); //test at beginning of task. should not set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertNotSame(task.getImportance(), Task.IMPORTANCE_MUST_DO);
|
||||
}
|
||||
for (String acceptedStringAnywhere:acceptedStringsAnywhere){
|
||||
task = new Task();
|
||||
String title = "Jog " + acceptedStringAnywhere;
|
||||
task.setTitle(title); //test at end of task. should set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_MUST_DO);
|
||||
|
||||
title = acceptedStringAnywhere + " jog";
|
||||
task.setTitle(title); //test at beginning of task. should set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_MUST_DO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPriority3() throws Exception {
|
||||
String[] acceptedStringsAtEnd = {
|
||||
"priority 3",
|
||||
"highest priority",
|
||||
"bang bang bang",
|
||||
"bang 3",
|
||||
"bang bang bang bang bang bang bang"
|
||||
};
|
||||
String[] acceptedStringsAnywhere = {
|
||||
"!3",
|
||||
"!!!",
|
||||
"!6",
|
||||
"!!!!!!!!!!!!!"
|
||||
};
|
||||
Task task;
|
||||
for (String acceptedStringAtEnd:acceptedStringsAtEnd){
|
||||
task = new Task();
|
||||
String title = "Jog " + acceptedStringAtEnd;
|
||||
task.setTitle(title); //test at end of task. should set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_DO_OR_DIE);
|
||||
|
||||
task = new Task();
|
||||
title = acceptedStringAtEnd + " jog";
|
||||
task.setTitle(title); //test at beginning of task. should not set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertNotSame(task.getImportance(), Task.IMPORTANCE_DO_OR_DIE);
|
||||
}
|
||||
for (String acceptedStringAnywhere:acceptedStringsAnywhere){
|
||||
task = new Task();
|
||||
String title = "Jog " + acceptedStringAnywhere;
|
||||
task.setTitle(title); //test at end of task. should set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_DO_OR_DIE);
|
||||
|
||||
title = acceptedStringAnywhere + " jog";
|
||||
task.setTitle(title); //test at beginning of task. should set importance.
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals((int)task.getImportance(), Task.IMPORTANCE_DO_OR_DIE);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------Priority end----------------//
|
||||
|
||||
|
||||
//----------------Repeats begin----------------//
|
||||
/** test daily repeat from due date, but with no due date set */
|
||||
@Test
|
||||
public void testDailyWithNoDueDate() throws Exception {
|
||||
Task task = new Task();
|
||||
String title = "Jog daily";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
RRule rrule = new RRule();
|
||||
rrule.setFreq(Frequency.DAILY);
|
||||
rrule.setInterval(1);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
|
||||
title = "Jog every day";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
|
||||
for (int i = 1; i <= 12; i++){
|
||||
title = "Jog every " + i + " days.";
|
||||
task.setTitle(title);
|
||||
rrule.setInterval(i);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
task = new Task();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** test weekly repeat from due date, with no due date & time set */
|
||||
@Test
|
||||
public void testWeeklyWithNoDueDate() throws Exception {
|
||||
Task task = new Task();
|
||||
String title = "Jog weekly";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
RRule rrule = new RRule();
|
||||
rrule.setFreq(Frequency.WEEKLY);
|
||||
rrule.setInterval(1);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
|
||||
title = "Jog every week";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
|
||||
for (int i = 1; i <= 12; i++){
|
||||
title = "Jog every " + i + " weeks";
|
||||
task.setTitle(title);
|
||||
rrule.setInterval(i);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
task = new Task();
|
||||
}
|
||||
}
|
||||
|
||||
/** test hourly repeat from due date, with no due date but no time */
|
||||
@Test
|
||||
public void testMonthlyFromNoDueDate() throws Exception {
|
||||
Task task = new Task();
|
||||
String title = "Jog monthly";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
RRule rrule = new RRule();
|
||||
rrule.setFreq(Frequency.MONTHLY);
|
||||
rrule.setInterval(1);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
|
||||
title = "Jog every month";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
|
||||
for (int i = 1; i <= 12; i++){
|
||||
title = "Jog every " + i + " months";
|
||||
task.setTitle(title);
|
||||
rrule.setInterval(i);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertFalse(task.hasDueTime());
|
||||
assertFalse(task.hasDueDate());
|
||||
task = new Task();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDailyFromDueDate() throws Exception {
|
||||
Task task = new Task();
|
||||
String title = "Jog daily starting from today";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
RRule rrule = new RRule();
|
||||
rrule.setFreq(Frequency.DAILY);
|
||||
rrule.setInterval(1);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertTrue(task.hasDueDate());
|
||||
|
||||
task.clearValue(Task.ID);
|
||||
task.clearValue(Task.UUID);
|
||||
title = "Jog every day starting from today";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertTrue(task.hasDueDate());
|
||||
|
||||
for (int i = 1; i <= 12; i++){
|
||||
title = "Jog every " + i + " days starting from today";
|
||||
task.setTitle(title);
|
||||
rrule.setInterval(i);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertTrue(task.hasDueDate());
|
||||
task = new Task();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeeklyFromDueDate() throws Exception {
|
||||
Task task = new Task();
|
||||
String title = "Jog weekly starting from today";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
RRule rrule = new RRule();
|
||||
rrule.setFreq(Frequency.WEEKLY);
|
||||
rrule.setInterval(1);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertTrue(task.hasDueDate());
|
||||
|
||||
task.clearValue(Task.ID);
|
||||
task.clearValue(Task.UUID);
|
||||
title = "Jog every week starting from today";
|
||||
task.setTitle(title);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertTrue(task.hasDueDate());
|
||||
|
||||
for (int i = 1; i <= 12; i++){
|
||||
title = "Jog every " + i + " weeks starting from today";
|
||||
task.setTitle(title);
|
||||
rrule.setInterval(i);
|
||||
taskCreator.createWithValues(task, null, title);
|
||||
assertEquals(task.getRecurrence(), rrule.toIcal());
|
||||
assertTrue(task.hasDueDate());
|
||||
task = new Task();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------Repeats end----------------//
|
||||
|
||||
|
||||
//----------------Tags begin----------------//
|
||||
/** tests all words using priority 0 */
|
||||
@Test
|
||||
public void testTagsPound() throws Exception {
|
||||
String[] acceptedStrings = {
|
||||
"#tag",
|
||||
"#a",
|
||||
"#(a cool tag)",
|
||||
"#(cool)"
|
||||
};
|
||||
Task task;
|
||||
for (String acceptedString : acceptedStrings) {
|
||||
task = new Task();
|
||||
task.setTitle("Jog " + acceptedString); //test at end of task. should set importance.
|
||||
ArrayList<String> tags = new ArrayList<>();
|
||||
TitleParser.listHelper(tagService, task, tags);
|
||||
String tag = TitleParser.trimParenthesis(acceptedString);
|
||||
assertTrue("test pound at failed for string: " + acceptedString + " for tags: " + tags.toString(), tags.contains(tag));
|
||||
}
|
||||
}
|
||||
|
||||
/** tests all words using priority 0 */
|
||||
@Test
|
||||
public void testTagsAt() throws Exception {
|
||||
String[] acceptedStrings = {
|
||||
"@tag",
|
||||
"@a",
|
||||
"@(a cool tag)",
|
||||
"@(cool)"
|
||||
};
|
||||
Task task;
|
||||
for (String acceptedString : acceptedStrings) {
|
||||
task = new Task();
|
||||
task.setTitle("Jog " + acceptedString); //test at end of task. should set importance.
|
||||
ArrayList<String> tags = new ArrayList<>();
|
||||
TitleParser.listHelper(tagService, task, tags);
|
||||
String tag = TitleParser.trimParenthesis(acceptedString);
|
||||
assertTrue("testTagsAt failed for string: " + acceptedString+ " for tags: " + tags.toString(), tags.contains(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,442 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.service
|
||||
|
||||
import com.todoroo.astrid.utility.TitleParser
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.fortuna.ical4j.model.Recur.Frequency.DAILY
|
||||
import net.fortuna.ical4j.model.Recur.Frequency.MONTHLY
|
||||
import net.fortuna.ical4j.model.Recur.Frequency.WEEKLY
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.tasks.R
|
||||
import org.tasks.data.dao.CaldavDao
|
||||
import org.tasks.data.dao.TagDataDao
|
||||
import org.tasks.data.entity.Task
|
||||
import org.tasks.data.newLocalAccount
|
||||
import org.tasks.date.DateTimeUtils
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.repeats.RecurrenceUtils.newRecur
|
||||
import java.util.Calendar
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class TitleParserTest : InjectingTestCase() {
|
||||
@Inject lateinit var tagDataDao: TagDataDao
|
||||
@Inject lateinit var preferences: Preferences
|
||||
@Inject lateinit var taskCreator: TaskCreator
|
||||
@Inject lateinit var caldavDao: CaldavDao
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
runBlocking {
|
||||
super.setUp()
|
||||
preferences.setStringFromInteger(R.string.p_default_urgency_key, 0)
|
||||
caldavDao.newLocalAccount()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test that completing a task w/ no regular expressions creates a simple task with no date, no
|
||||
* repeat, no lists
|
||||
*/
|
||||
@Test
|
||||
fun testNoRegexes() = runBlocking {
|
||||
val task = taskCreator.basicQuickAddTask("Jog")
|
||||
val nothing = Task()
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
assertEquals(task.recurrence, nothing.recurrence)
|
||||
}
|
||||
|
||||
/** Tests correct date is parsed */
|
||||
@Test
|
||||
fun testMonthDate() {
|
||||
val titleMonthStrings = arrayOf(
|
||||
"Jan.", "January",
|
||||
"Feb.", "February",
|
||||
"Mar.", "March",
|
||||
"Apr.", "April",
|
||||
"May", "May",
|
||||
"Jun.", "June",
|
||||
"Jul.", "July",
|
||||
"Aug.", "August",
|
||||
"Sep.", "September",
|
||||
"Oct.", "October",
|
||||
"Nov.", "November",
|
||||
"Dec.", "December"
|
||||
)
|
||||
for (i in 0..22) {
|
||||
val testTitle = "Jog on " + titleMonthStrings[i] + " 12."
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.monthOfYear, i / 2 + 1)
|
||||
assertEquals(date.dayOfMonth, 12)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMonthSlashDay() {
|
||||
for (i in 1..12) {
|
||||
val testTitle = "Jog on $i/12/13"
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.monthOfYear, i)
|
||||
assertEquals(date.dayOfMonth, 12)
|
||||
assertEquals(date.year, 2013)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArmyTime() {
|
||||
val testTitle = "Jog on 23:21."
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.hourOfDay, 23)
|
||||
assertEquals(date.minuteOfHour, 21)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_AM_PM() {
|
||||
val testTitle = "Jog at 8:33 PM."
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.hourOfDay, 20)
|
||||
assertEquals(date.minuteOfHour, 33)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_at_hour() {
|
||||
val testTitle = "Jog at 8 PM."
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.hourOfDay, 20)
|
||||
assertEquals(date.minuteOfHour, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_oclock_AM() {
|
||||
val testTitle = "Jog at 8 o'clock AM."
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.hourOfDay, 8)
|
||||
assertEquals(date.minuteOfHour, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_several_forms_of_eight() {
|
||||
val testTitles = arrayOf("Jog 8 AM", "Jog 8 o'clock AM", "at 8:00 AM")
|
||||
for (testTitle in testTitles) {
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.hourOfDay, 8)
|
||||
assertEquals(date.minuteOfHour, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_several_forms_of_1230PM() {
|
||||
val testTitles = arrayOf(
|
||||
"Jog 12:30 PM", "at 12:30 PM", "Do something on 12:30 PM", "Jog at 12:30 PM Friday"
|
||||
)
|
||||
for (testTitle in testTitles) {
|
||||
val task = insertTitleAddTask(testTitle)
|
||||
val date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.hourOfDay, 12)
|
||||
assertEquals(date.minuteOfHour, 30)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertTitleAddTask(title: String): Task = runBlocking {
|
||||
taskCreator.createWithValues(title)
|
||||
}
|
||||
|
||||
// ----------------Days begin----------------//
|
||||
@Test
|
||||
@Ignore("Flaky test")
|
||||
fun testDays() = runBlocking {
|
||||
val today = Calendar.getInstance()
|
||||
var title = "Jog today"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
var date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.dayOfWeek, today[Calendar.DAY_OF_WEEK])
|
||||
// Calendar starts 1-6, date.getDay() starts at 0
|
||||
title = "Jog tomorrow"
|
||||
task = taskCreator.createWithValues(title)
|
||||
date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.dayOfWeek % 7, (today[Calendar.DAY_OF_WEEK] + 1) % 7)
|
||||
val days = arrayOf(
|
||||
"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday")
|
||||
val abrevDays = arrayOf("sun.", "mon.", "tue.", "wed.", "thu.", "fri.", "sat.")
|
||||
for (i in 1..6) {
|
||||
title = "Jog " + days[i]
|
||||
task = taskCreator.createWithValues(title)
|
||||
date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.dayOfWeek, i + 1)
|
||||
title = "Jog " + abrevDays[i]
|
||||
task = taskCreator.createWithValues(title)
|
||||
date = DateTimeUtils.newDateTime(task.dueDate)
|
||||
assertEquals(date.dayOfWeek, i + 1)
|
||||
}
|
||||
}
|
||||
// ----------------Days end----------------//
|
||||
// ----------------Priority begin----------------//
|
||||
/** tests all words using priority 0 */
|
||||
@Test
|
||||
fun testPriority0() = runBlocking {
|
||||
val acceptedStrings = arrayOf("priority 0", "least priority", "lowest priority", "bang 0")
|
||||
for (acceptedString in acceptedStrings) {
|
||||
val title = "Jog $acceptedString"
|
||||
val task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.priority, Task.Priority.NONE)
|
||||
}
|
||||
for (acceptedString in acceptedStrings) {
|
||||
val title = "$acceptedString jog"
|
||||
val task = taskCreator.createWithValues(title)
|
||||
assertNotSame(task.priority, Task.Priority.NONE)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPriority1() = runBlocking {
|
||||
val acceptedStringsAtEnd = arrayOf("priority 1", "low priority", "bang", "bang 1")
|
||||
val acceptedStringsAnywhere = arrayOf("!1", "!")
|
||||
var task: Task
|
||||
for (acceptedStringAtEnd in acceptedStringsAtEnd) {
|
||||
task = taskCreator.basicQuickAddTask(
|
||||
"Jog $acceptedStringAtEnd") // test at end of task. should set importance.
|
||||
assertEquals(task.priority, Task.Priority.LOW)
|
||||
}
|
||||
for (acceptedStringAtEnd in acceptedStringsAtEnd) {
|
||||
task = taskCreator.basicQuickAddTask(acceptedStringAtEnd
|
||||
+ " jog") // test at beginning of task. should not set importance.
|
||||
assertEquals(task.priority, Task.Priority.LOW)
|
||||
}
|
||||
for (acceptedStringAnywhere in acceptedStringsAnywhere) {
|
||||
task = taskCreator.basicQuickAddTask(
|
||||
"Jog $acceptedStringAnywhere") // test at end of task. should set importance.
|
||||
assertEquals(task.priority, Task.Priority.LOW)
|
||||
task = taskCreator.basicQuickAddTask(
|
||||
"$acceptedStringAnywhere jog") // test at beginning of task. should set importance.
|
||||
assertEquals(task.priority, Task.Priority.LOW)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPriority2() = runBlocking {
|
||||
val acceptedStringsAtEnd = arrayOf("priority 2", "high priority", "bang bang", "bang 2")
|
||||
val acceptedStringsAnywhere = arrayOf("!2", "!!")
|
||||
for (acceptedStringAtEnd in acceptedStringsAtEnd) {
|
||||
var title = "Jog $acceptedStringAtEnd"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.priority, Task.Priority.MEDIUM)
|
||||
title = "$acceptedStringAtEnd jog"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertNotSame(task.priority, Task.Priority.MEDIUM)
|
||||
}
|
||||
for (acceptedStringAnywhere in acceptedStringsAnywhere) {
|
||||
var title = "Jog $acceptedStringAnywhere"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.priority, Task.Priority.MEDIUM)
|
||||
title = "$acceptedStringAnywhere jog"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.priority, Task.Priority.MEDIUM)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPriority3() = runBlocking {
|
||||
val acceptedStringsAtEnd = arrayOf(
|
||||
"priority 3",
|
||||
"highest priority",
|
||||
"bang bang bang",
|
||||
"bang 3",
|
||||
"bang bang bang bang bang bang bang"
|
||||
)
|
||||
val acceptedStringsAnywhere = arrayOf("!3", "!!!", "!6", "!!!!!!!!!!!!!")
|
||||
for (acceptedStringAtEnd in acceptedStringsAtEnd) {
|
||||
var title = "Jog $acceptedStringAtEnd"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.priority, Task.Priority.HIGH)
|
||||
title = "$acceptedStringAtEnd jog"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertNotSame(task.priority, Task.Priority.HIGH)
|
||||
}
|
||||
for (acceptedStringAnywhere in acceptedStringsAnywhere) {
|
||||
var title = "Jog $acceptedStringAnywhere"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.priority, Task.Priority.HIGH)
|
||||
title = "$acceptedStringAnywhere jog"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.priority, Task.Priority.HIGH)
|
||||
}
|
||||
}
|
||||
// ----------------Priority end----------------//
|
||||
// ----------------Repeats begin----------------//
|
||||
/** test daily repeat from due date, but with no due date set */
|
||||
@Test
|
||||
fun testDailyWithNoDueDate() = runBlocking {
|
||||
var title = "Jog daily"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
val recur = newRecur()
|
||||
recur.setFrequency(DAILY.name)
|
||||
recur.interval = 1
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
title = "Jog every day"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
for (i in 1..12) {
|
||||
title = "Jog every $i days."
|
||||
recur.interval = i
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
}
|
||||
}
|
||||
|
||||
/** test weekly repeat from due date, with no due date & time set */
|
||||
@Test
|
||||
fun testWeeklyWithNoDueDate() = runBlocking {
|
||||
var title = "Jog weekly"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
val recur = newRecur()
|
||||
recur.setFrequency(WEEKLY.name)
|
||||
recur.interval = 1
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
title = "Jog every week"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
for (i in 1..12) {
|
||||
title = "Jog every $i weeks"
|
||||
recur.interval = i
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
}
|
||||
}
|
||||
|
||||
/** test hourly repeat from due date, with no due date but no time */
|
||||
@Test
|
||||
fun testMonthlyFromNoDueDate() = runBlocking {
|
||||
var title = "Jog monthly"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
val recur = newRecur()
|
||||
recur.setFrequency(MONTHLY.name)
|
||||
recur.interval = 1
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
title = "Jog every month"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
for (i in 1..12) {
|
||||
title = "Jog every $i months"
|
||||
recur.interval = i
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertFalse(task.hasDueTime())
|
||||
assertFalse(task.hasDueDate())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDailyFromDueDate() = runBlocking {
|
||||
var title = "Jog daily starting from today"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
val recur = newRecur()
|
||||
recur.setFrequency(DAILY.name)
|
||||
recur.interval = 1
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertTrue(task.hasDueDate())
|
||||
title = "Jog every day starting from today"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertTrue(task.hasDueDate())
|
||||
for (i in 1..12) {
|
||||
title = "Jog every $i days starting from today"
|
||||
recur.interval = i
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertTrue(task.hasDueDate())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWeeklyFromDueDate() = runBlocking {
|
||||
var title = "Jog weekly starting from today"
|
||||
var task = taskCreator.createWithValues(title)
|
||||
val recur = newRecur()
|
||||
recur.setFrequency(WEEKLY.name)
|
||||
recur.interval = 1
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertTrue(task.hasDueDate())
|
||||
title = "Jog every week starting from today"
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertTrue(task.hasDueDate())
|
||||
for (i in 1..12) {
|
||||
title = "Jog every $i weeks starting from today"
|
||||
recur.interval = i
|
||||
task = taskCreator.createWithValues(title)
|
||||
assertEquals(task.recurrence, recur.toString())
|
||||
assertTrue(task.hasDueDate())
|
||||
}
|
||||
}
|
||||
// ----------------Repeats end----------------//
|
||||
// ----------------Tags begin----------------//
|
||||
/** tests all words using priority 0 */
|
||||
@Test
|
||||
fun testTagsPound() = runBlocking {
|
||||
val acceptedStrings = arrayOf("#tag", "#a", "#(a cool tag)", "#(cool)")
|
||||
var task: Task
|
||||
for (acceptedString in acceptedStrings) {
|
||||
task = Task()
|
||||
task.title = "Jog $acceptedString" // test at end of task. should set importance.
|
||||
val tags = ArrayList<String>()
|
||||
TitleParser.listHelper(tagDataDao, task, tags)
|
||||
val tag = TitleParser.trimParenthesis(acceptedString)
|
||||
assertTrue(
|
||||
"test pound at failed for string: $acceptedString for tags: $tags",
|
||||
tags.contains(tag))
|
||||
}
|
||||
}
|
||||
|
||||
/** tests all words using priority 0 */
|
||||
@Test
|
||||
fun testTagsAt() = runBlocking {
|
||||
val acceptedStrings = arrayOf("@tag", "@a", "@(a cool tag)", "@(cool)")
|
||||
var task: Task
|
||||
for (acceptedString in acceptedStrings) {
|
||||
task = Task()
|
||||
task.title = "Jog $acceptedString" // test at end of task. should set importance.
|
||||
val tags = ArrayList<String>()
|
||||
TitleParser.listHelper(tagDataDao, task, tags)
|
||||
val tag = TitleParser.trimParenthesis(acceptedString)
|
||||
assertTrue(
|
||||
"testTagsAt failed for string: $acceptedString for tags: $tags",
|
||||
tags.contains(tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -1,217 +0,0 @@
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package com.todoroo.astrid.service
|
||||
|
||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
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.injection.InjectingTestCase
|
||||
import org.tasks.makers.CaldavTaskMaker.CALENDAR
|
||||
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
|
||||
import org.tasks.makers.CaldavTaskMaker.TASK
|
||||
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
|
||||
import org.tasks.makers.TaskMaker.DUE_DATE
|
||||
import org.tasks.makers.TaskMaker.HIDE_TYPE
|
||||
import org.tasks.makers.TaskMaker.MODIFICATION_TIME
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
import org.tasks.opentasks.TestOpenTaskDao
|
||||
import org.tasks.time.DateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
class Upgrade_11_3_Test : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@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)
|
||||
|
||||
upgrader.applyiCalendarStartDates()
|
||||
|
||||
assertEquals(DateTime(2021, 1, 21), taskDao.fetch(taskId)?.hideUntil)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ignoreRemoteiCalendarStartDate() = runBlocking {
|
||||
val taskId = taskDao.insert(newTask(
|
||||
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)
|
||||
|
||||
upgrader.applyiCalendarStartDates()
|
||||
|
||||
assertEquals(DateTime(2021, 1, 20), taskDao.fetch(taskId)?.hideUntil)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun touchTaskWithLocaliCalendarStartDate() = runBlocking {
|
||||
val upgradeTime = DateTime(2021, 1, 21, 11, 47, 32, 450)
|
||||
val taskId = taskDao.insert(newTask(
|
||||
with(DUE_DATE, DateTime(2021, 1, 20)),
|
||||
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)
|
||||
|
||||
freezeAt(upgradeTime) {
|
||||
upgrader.applyiCalendarStartDates()
|
||||
}
|
||||
|
||||
assertEquals(upgradeTime, taskDao.fetch(taskId)?.modificationDate)
|
||||
}
|
||||
|
||||
@Test
|
||||
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)
|
||||
|
||||
upgrader.applyiCalendarStartDates()
|
||||
|
||||
assertEquals(modificationTime, taskDao.fetch(taskId)?.modificationDate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun applyRemoteOpenTaskStartDate() = runBlocking {
|
||||
val (listId, list) = openTaskDao.insertList()
|
||||
openTaskDao.insertTask(listId, VTODO_WITH_START_DATE)
|
||||
val taskId = taskDao.insert(newTask())
|
||||
caldavDao.insert(newCaldavTask(
|
||||
with(CALENDAR, list.uuid),
|
||||
with(REMOTE_ID, "4586964443060640060"),
|
||||
with(TASK, taskId)
|
||||
))
|
||||
|
||||
upgrader.applyOpenTaskStartDates()
|
||||
|
||||
assertEquals(DateTime(2021, 1, 21), taskDao.fetch(taskId)?.hideUntil)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ignoreRemoteOpenTaskStartDate() = runBlocking {
|
||||
val (listId, list) = openTaskDao.insertList()
|
||||
openTaskDao.insertTask(listId, VTODO_WITH_START_DATE)
|
||||
val taskId = taskDao.insert(newTask(
|
||||
with(DUE_DATE, DateTime(2021, 1, 20)),
|
||||
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE)
|
||||
))
|
||||
caldavDao.insert(newCaldavTask(
|
||||
with(CALENDAR, list.uuid),
|
||||
with(REMOTE_ID, "4586964443060640060"),
|
||||
with(TASK, taskId)
|
||||
))
|
||||
|
||||
upgrader.applyOpenTaskStartDates()
|
||||
|
||||
assertEquals(DateTime(2021, 1, 20), taskDao.fetch(taskId)?.hideUntil)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun touchWithOpenTaskStartDate() = runBlocking {
|
||||
val upgradeTime = DateTime(2021, 1, 21, 11, 47, 32, 450)
|
||||
val (listId, list) = openTaskDao.insertList()
|
||||
openTaskDao.insertTask(listId, VTODO_WITH_START_DATE)
|
||||
val taskId = taskDao.insert(newTask(
|
||||
with(DUE_DATE, DateTime(2021, 1, 20)),
|
||||
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE),
|
||||
with(MODIFICATION_TIME, DateTime(2021, 1, 21, 9, 50, 4, 348))
|
||||
))
|
||||
caldavDao.insert(newCaldavTask(
|
||||
with(CALENDAR, list.uuid),
|
||||
with(REMOTE_ID, "4586964443060640060"),
|
||||
with(TASK, taskId)
|
||||
))
|
||||
|
||||
freezeAt(upgradeTime) {
|
||||
upgrader.applyOpenTaskStartDates()
|
||||
}
|
||||
|
||||
assertEquals(upgradeTime, taskDao.fetch(taskId)?.modificationDate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dontTouchNoOpenTaskStartDate() = runBlocking {
|
||||
val modificationTime = DateTime(2021, 1, 21, 9, 50, 4, 348)
|
||||
val (listId, list) = openTaskDao.insertList()
|
||||
openTaskDao.insertTask(listId, VTODO_NO_START_DATE)
|
||||
val taskId = taskDao.insert(newTask(with(MODIFICATION_TIME, modificationTime)))
|
||||
caldavDao.insert(newCaldavTask(
|
||||
with(CALENDAR, list.uuid),
|
||||
with(REMOTE_ID, "4586964443060640060"),
|
||||
with(TASK, taskId)
|
||||
))
|
||||
|
||||
upgrader.applyOpenTaskStartDates()
|
||||
|
||||
assertEquals(modificationTime, taskDao.fetch(taskId)?.modificationDate)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val VTODO_WITH_START_DATE = """
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:+//IDN tasks.org//android-110301//EN
|
||||
BEGIN:VTODO
|
||||
DTSTAMP:20210121T153032Z
|
||||
UID:4586964443060640060
|
||||
CREATED:20210121T153000Z
|
||||
LAST-MODIFIED:20210121T153029Z
|
||||
SUMMARY:Test
|
||||
PRIORITY:9
|
||||
X-APPLE-SORT-ORDER:-27
|
||||
DTSTART;VALUE=DATE:20210121
|
||||
END:VTODO
|
||||
END:VCALENDAR
|
||||
""".trimIndent()
|
||||
|
||||
val VTODO_NO_START_DATE = """
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:+//IDN tasks.org//android-110301//EN
|
||||
BEGIN:VTODO
|
||||
DTSTAMP:20210121T153032Z
|
||||
UID:4586964443060640060
|
||||
CREATED:20210121T153000Z
|
||||
LAST-MODIFIED:20210121T153029Z
|
||||
SUMMARY:Test
|
||||
PRIORITY:9
|
||||
X-APPLE-SORT-ORDER:-27
|
||||
END:VTODO
|
||||
END:VCALENDAR
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.data.TaskListMetadata;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.injection.TestComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SubtasksHelperTest extends SubtasksTestCase {
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
createTasks();
|
||||
TaskListMetadata m = new TaskListMetadata();
|
||||
m.setFilter(TaskListMetadata.FILTER_ID_ALL);
|
||||
updater.initializeFromSerializedTree(m, filter, SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE));
|
||||
}
|
||||
|
||||
private void createTask(String title, String uuid) {
|
||||
Task t = new Task();
|
||||
t.setTitle(title);
|
||||
t.setUuid(uuid);
|
||||
taskDao.save(t);
|
||||
}
|
||||
|
||||
private void createTasks() {
|
||||
createTask("A", "6"); // Local id 1
|
||||
createTask("B", "4"); // Local id 2
|
||||
createTask("C", "3"); // Local id 3
|
||||
createTask("D", "1"); // Local id 4
|
||||
createTask("E", "2"); // Local id 5
|
||||
createTask("F", "5"); // Local id 6
|
||||
}
|
||||
|
||||
private static final String[] EXPECTED_ORDER = { "-1", "1", "2", "3", "4", "5", "6" };
|
||||
|
||||
@Test
|
||||
public void testOrderedIdArray() {
|
||||
String[] ids = SubtasksHelper.getStringIdArray(DEFAULT_SERIALIZED_TREE);
|
||||
assertEquals(EXPECTED_ORDER.length, ids.length);
|
||||
for (int i = 0; i < EXPECTED_ORDER.length; i++) {
|
||||
assertEquals(EXPECTED_ORDER[i], ids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Default order: "[-1, [1, 2, [3, 4]], 5, 6]"
|
||||
|
||||
private static String EXPECTED_REMOTE = "[\"-1\", [\"6\", \"4\", [\"3\", \"1\"]], \"2\", \"5\"]".replaceAll("\\s", "");
|
||||
@Test
|
||||
public void testLocalToRemoteIdMapping() {
|
||||
String mapped = SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE).replaceAll("\\s", "");
|
||||
assertEquals(EXPECTED_REMOTE, mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
super.inject(component);
|
||||
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
package com.todoroo.astrid.subtasks
|
||||
|
||||
import org.tasks.data.entity.Task
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.tasks.data.entity.TaskListMetadata
|
||||
|
||||
@HiltAndroidTest
|
||||
class SubtasksHelperTest : SubtasksTestCase() {
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
createTasks()
|
||||
val m = TaskListMetadata()
|
||||
m.filter = TaskListMetadata.FILTER_ID_ALL
|
||||
runBlocking {
|
||||
updater.initializeFromSerializedTree(
|
||||
m, filter, SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTask(title: String, uuid: String) = runBlocking {
|
||||
val t = Task()
|
||||
t.title = title
|
||||
t.uuid = uuid
|
||||
taskDao.createNew(t)
|
||||
}
|
||||
|
||||
private fun createTasks() {
|
||||
createTask("A", "6") // Local id 1
|
||||
createTask("B", "4") // Local id 2
|
||||
createTask("C", "3") // Local id 3
|
||||
createTask("D", "1") // Local id 4
|
||||
createTask("E", "2") // Local id 5
|
||||
createTask("F", "5") // Local id 6
|
||||
}
|
||||
|
||||
// Default order: "[-1, [1, 2, [3, 4]], 5, 6]"
|
||||
@Test
|
||||
fun testOrderedIdArray() {
|
||||
val ids = SubtasksHelper.getStringIdArray(DEFAULT_SERIALIZED_TREE)
|
||||
assertEquals(EXPECTED_ORDER.size, ids.size)
|
||||
for (i in EXPECTED_ORDER.indices) {
|
||||
assertEquals(EXPECTED_ORDER[i], ids[i])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLocalToRemoteIdMapping() = runBlocking {
|
||||
val mapped = SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE)
|
||||
.replace("\\s".toRegex(), "")
|
||||
assertEquals(EXPECTED_REMOTE, mapped)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EXPECTED_ORDER = arrayOf("-1", "1", "2", "3", "4", "5", "6")
|
||||
private val EXPECTED_REMOTE = """["-1", ["6", "4", ["3", "1"]], "2", "5"]""".replace("\\s".toRegex(), "")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.data.TaskListMetadata;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SubtasksMovingTest extends SubtasksTestCase {
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
|
||||
private Task A, B, C, D, E, F;
|
||||
|
||||
// @Override
|
||||
protected void disabled_setUp() {
|
||||
super.setUp();
|
||||
createTasks();
|
||||
TaskListMetadata m = new TaskListMetadata();
|
||||
m.setFilter(TaskListMetadata.FILTER_ID_ALL);
|
||||
updater.initializeFromSerializedTree(m, filter, SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE));
|
||||
|
||||
// Assert initial state is correct
|
||||
expectParentAndPosition(A, null, 0);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 1);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
private void createTasks() {
|
||||
A = createTask("A");
|
||||
B = createTask("B");
|
||||
C = createTask("C");
|
||||
D = createTask("D");
|
||||
E = createTask("E");
|
||||
F = createTask("F");
|
||||
}
|
||||
|
||||
private Task createTask(String title) {
|
||||
Task task = new Task();
|
||||
task.setTitle(title);
|
||||
taskDao.save(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
private void whenTriggerMoveBefore(Task target, Task before) {
|
||||
String beforeId = (before == null ? "-1" : before.getUuid());
|
||||
updater.moveTo(null, filter, target.getUuid(), beforeId);
|
||||
}
|
||||
|
||||
/* Starting State (see SubtasksTestCase):
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testMoveBeforeIntoSelf() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, B);
|
||||
|
||||
expectParentAndPosition(A, null, 0);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 1);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testMoveIntoDescendant() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, C);
|
||||
|
||||
expectParentAndPosition(A, null, 0);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 1);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testMoveToEndOfChildren() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, E);
|
||||
|
||||
expectParentAndPosition(A, null, 0);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 1);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testStandardMove() {
|
||||
whenTriggerMoveBefore(A, F);
|
||||
|
||||
expectParentAndPosition(A, null, 1);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 0);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testMoveToEndOfList() {
|
||||
whenTriggerMoveBefore(A, null);
|
||||
|
||||
expectParentAndPosition(A, null, 2);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 0);
|
||||
expectParentAndPosition(F, null, 1);
|
||||
}
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
package com.todoroo.astrid.subtasks
|
||||
|
||||
import org.tasks.data.entity.Task
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.tasks.data.entity.TaskListMetadata
|
||||
|
||||
@HiltAndroidTest
|
||||
class SubtasksMovingTest : SubtasksTestCase() {
|
||||
private lateinit var A: Task
|
||||
private lateinit var B: Task
|
||||
private lateinit var C: Task
|
||||
private lateinit var D: Task
|
||||
private lateinit var E: Task
|
||||
private lateinit var F: Task
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
createTasks()
|
||||
val m = TaskListMetadata()
|
||||
m.filter = TaskListMetadata.FILTER_ID_ALL
|
||||
runBlocking {
|
||||
updater.initializeFromSerializedTree(
|
||||
m, filter, SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE))
|
||||
}
|
||||
|
||||
// Assert initial state is correct
|
||||
expectParentAndPosition(A, null, 0)
|
||||
expectParentAndPosition(B, A, 0)
|
||||
expectParentAndPosition(C, A, 1)
|
||||
expectParentAndPosition(D, C, 0)
|
||||
expectParentAndPosition(E, null, 1)
|
||||
expectParentAndPosition(F, null, 2)
|
||||
}
|
||||
|
||||
private fun createTasks() {
|
||||
A = createTask("A")
|
||||
B = createTask("B")
|
||||
C = createTask("C")
|
||||
D = createTask("D")
|
||||
E = createTask("E")
|
||||
F = createTask("F")
|
||||
}
|
||||
|
||||
private fun createTask(title: String): Task = runBlocking {
|
||||
val task = Task()
|
||||
task.title = title
|
||||
taskDao.createNew(task)
|
||||
task
|
||||
}
|
||||
|
||||
private fun whenTriggerMoveBefore(target: Task?, before: Task?) = runBlocking {
|
||||
val beforeId = before?.uuid ?: "-1"
|
||||
updater.moveTo(TaskListMetadata(), filter, target!!.uuid, beforeId)
|
||||
}
|
||||
|
||||
/* Starting State (see SubtasksTestCase):
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
@Test
|
||||
fun testMoveBeforeIntoSelf() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, B)
|
||||
expectParentAndPosition(A, null, 0)
|
||||
expectParentAndPosition(B, A, 0)
|
||||
expectParentAndPosition(C, A, 1)
|
||||
expectParentAndPosition(D, C, 0)
|
||||
expectParentAndPosition(E, null, 1)
|
||||
expectParentAndPosition(F, null, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMoveIntoDescendant() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, C)
|
||||
expectParentAndPosition(A, null, 0)
|
||||
expectParentAndPosition(B, A, 0)
|
||||
expectParentAndPosition(C, A, 1)
|
||||
expectParentAndPosition(D, C, 0)
|
||||
expectParentAndPosition(E, null, 1)
|
||||
expectParentAndPosition(F, null, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMoveToEndOfChildren() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, E)
|
||||
expectParentAndPosition(A, null, 0)
|
||||
expectParentAndPosition(B, A, 0)
|
||||
expectParentAndPosition(C, A, 1)
|
||||
expectParentAndPosition(D, C, 0)
|
||||
expectParentAndPosition(E, null, 1)
|
||||
expectParentAndPosition(F, null, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStandardMove() {
|
||||
whenTriggerMoveBefore(A, F)
|
||||
expectParentAndPosition(A, null, 1)
|
||||
expectParentAndPosition(B, A, 0)
|
||||
expectParentAndPosition(C, A, 1)
|
||||
expectParentAndPosition(D, C, 0)
|
||||
expectParentAndPosition(E, null, 0)
|
||||
expectParentAndPosition(F, null, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMoveToEndOfList() {
|
||||
whenTriggerMoveBefore(A, null)
|
||||
expectParentAndPosition(A, null, 2)
|
||||
expectParentAndPosition(B, A, 0)
|
||||
expectParentAndPosition(C, A, 1)
|
||||
expectParentAndPosition(D, C, 0)
|
||||
expectParentAndPosition(E, null, 0)
|
||||
expectParentAndPosition(F, null, 1)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.core.BuiltInFilterExposer;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.dao.TaskListMetadataDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.subtasks.SubtasksFilterUpdater.Node;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
import org.tasks.injection.TestComponent;
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* Contains useful methods common to all subtasks tests
|
||||
* @author Sam
|
||||
*
|
||||
*/
|
||||
public class SubtasksTestCase extends DatabaseTestCase {
|
||||
|
||||
@Inject TaskListMetadataDao taskListMetadataDao;
|
||||
@Inject TaskDao taskDao;
|
||||
@Inject Preferences preferences;
|
||||
|
||||
protected SubtasksFilterUpdater updater;
|
||||
protected Filter filter;
|
||||
|
||||
/* Starting State:
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
public static final String DEFAULT_SERIALIZED_TREE = "[-1, [1, 2, [3, 4]], 5, 6]".replaceAll("\\s", "");
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
filter = BuiltInFilterExposer.getMyTasksFilter(getTargetContext().getResources());
|
||||
preferences.clear(SubtasksFilterUpdater.ACTIVE_TASKS_ORDER);
|
||||
updater = new SubtasksFilterUpdater(taskListMetadataDao, taskDao);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
protected void expectParentAndPosition(Task task, Task parent, int positionInParent) {
|
||||
String parentId = (parent == null ? "-1" : parent.getUuid());
|
||||
Node n = updater.findNodeForTask(task.getUuid());
|
||||
assertNotNull("No node found for task " + task.getTitle(), n);
|
||||
assertEquals("Parent mismatch", parentId, n.parent.uuid);
|
||||
assertEquals("Position mismatch", positionInParent, n.parent.children.indexOf(n));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
package com.todoroo.astrid.subtasks
|
||||
|
||||
import com.todoroo.astrid.dao.TaskDao
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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.injection.InjectingTestCase
|
||||
import org.tasks.preferences.Preferences
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SubtasksTestCase : InjectingTestCase() {
|
||||
lateinit var updater: SubtasksFilterUpdater
|
||||
lateinit var filter: AstridOrderingFilter
|
||||
@Inject lateinit var taskListMetadataDao: TaskListMetadataDao
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var preferences: Preferences
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
filter = runBlocking { MyTasksFilter.create() }
|
||||
preferences.clear(SubtasksFilterUpdater.ACTIVE_TASKS_ORDER)
|
||||
updater = SubtasksFilterUpdater(taskListMetadataDao, taskDao)
|
||||
}
|
||||
|
||||
fun expectParentAndPosition(task: Task, parent: Task?, positionInParent: Int) {
|
||||
val parentId = parent?.uuid ?: "-1"
|
||||
val n = updater.findNodeForTask(task.uuid)
|
||||
assertNotNull("No node found for task " + task.title, n)
|
||||
assertEquals("Parent mismatch", parentId, n!!.parent!!.uuid)
|
||||
assertEquals("Position mismatch", positionInParent, n.parent!!.children.indexOf(n))
|
||||
}
|
||||
|
||||
companion object {
|
||||
/* Starting State:
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
val DEFAULT_SERIALIZED_TREE = "[-1, [1, 2, [3, 4]], 5, 6]".replace("\\s".toRegex(), "")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.todoroo.astrid.sync;
|
||||
|
||||
import com.todoroo.astrid.dao.TagDataDao;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
import org.tasks.injection.TestComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class NewSyncTestCase extends DatabaseTestCase {
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
@Inject TagDataDao tagDataDao;
|
||||
|
||||
protected Task createTask(String title) {
|
||||
Task task = new Task();
|
||||
task.setTitle(title);
|
||||
task.setImportance(SYNC_TASK_IMPORTANCE);
|
||||
|
||||
taskDao.createNew(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public static final String SYNC_TASK_TITLE = "new title";
|
||||
public static final int SYNC_TASK_IMPORTANCE = Task.IMPORTANCE_MUST_DO;
|
||||
|
||||
protected Task createTask() {
|
||||
return createTask(SYNC_TASK_TITLE);
|
||||
}
|
||||
|
||||
protected TagData createTagData(String name) {
|
||||
TagData tag = new TagData();
|
||||
tag.setName(name);
|
||||
|
||||
tagDataDao.createNew(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
protected TagData createTagData() {
|
||||
return createTagData("new tag");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(TestComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
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 org.tasks.injection.InjectingTestCase
|
||||
import javax.inject.Inject
|
||||
|
||||
open class NewSyncTestCase : InjectingTestCase() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var tagDataDao: TagDataDao
|
||||
|
||||
suspend fun createTask(): Task {
|
||||
val task = Task(
|
||||
title = SYNC_TASK_TITLE,
|
||||
priority = SYNC_TASK_IMPORTANCE,
|
||||
)
|
||||
taskDao.createNew(task)
|
||||
return task
|
||||
}
|
||||
|
||||
suspend fun createTagData(): TagData {
|
||||
val tag = TagData(name = "new tag")
|
||||
tagDataDao.insert(tag)
|
||||
return tag
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SYNC_TASK_TITLE = "new title"
|
||||
private const val SYNC_TASK_IMPORTANCE = Task.Priority.MEDIUM
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.todoroo.astrid.sync;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.todoroo.astrid.data.RemoteModel;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SyncModelTest extends NewSyncTestCase {
|
||||
|
||||
@Test
|
||||
public void testCreateTaskMakesUuid() {
|
||||
Task task = createTask();
|
||||
assertFalse(RemoteModel.NO_UUID.equals(task.getUUID()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTagMakesUuid() {
|
||||
TagData tag = createTagData();
|
||||
assertFalse(RemoteModel.NO_UUID.equals(tag.getUUID()));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package com.todoroo.astrid.sync
|
||||
|
||||
import org.tasks.data.entity.Task
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class SyncModelTest : NewSyncTestCase() {
|
||||
|
||||
@Test
|
||||
fun testCreateTaskMakesUuid() = runBlocking{
|
||||
val task = createTask()
|
||||
assertNotEquals(Task.NO_UUID, task.uuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateTagMakesUuid() = runBlocking{
|
||||
val tag = createTagData()
|
||||
assertNotEquals(Task.NO_UUID, tag.remoteId)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.test;
|
||||
|
||||
import com.todoroo.astrid.dao.Database;
|
||||
|
||||
import org.junit.After;
|
||||
import org.tasks.injection.InjectingTestCase;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
|
||||
public abstract class DatabaseTestCase extends InjectingTestCase {
|
||||
|
||||
protected Database database;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
|
||||
database = component.getDatabase();
|
||||
|
||||
database.close();
|
||||
getTargetContext().deleteDatabase(database.getName());
|
||||
database.openForWriting();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package org.tasks;
|
||||
|
||||
import org.tasks.time.DateTime;
|
||||
import org.tasks.time.DateTimeUtils;
|
||||
|
||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
||||
|
||||
public class Freeze {
|
||||
|
||||
public static Freeze freezeClock() {
|
||||
return freezeAt(currentTimeMillis());
|
||||
}
|
||||
|
||||
public static Freeze freezeAt(DateTime dateTime) {
|
||||
return freezeAt(dateTime.getMillis());
|
||||
}
|
||||
|
||||
public static Freeze freezeAt(long millis) {
|
||||
DateTimeUtils.setCurrentMillisFixed(millis);
|
||||
return new Freeze();
|
||||
}
|
||||
|
||||
public static void thaw() {
|
||||
DateTimeUtils.setCurrentMillisSystem();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
public void thawAfter(Snippet snippet) {
|
||||
thaw();
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
../../../../test/java/org/tasks/Freeze.kt
|
||||
@ -0,0 +1,53 @@
|
||||
package org.tasks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentValues;
|
||||
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.Table;
|
||||
import com.todoroo.astrid.data.RemoteModel;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class RemoteModelHelpers {
|
||||
public static Property[] asQueryProperties(Table table, ContentValues contentValues) {
|
||||
Set<String> keys = contentValues.keySet();
|
||||
Property[] result = new Property[keys.size()];
|
||||
int index = 0;
|
||||
for (String key : keys) {
|
||||
result[index++] = new Property.StringProperty(table, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void compareRemoteModel(RemoteModel expected, RemoteModel actual) {
|
||||
compareContentValues(expected.getSetValues(), actual.getSetValues());
|
||||
compareContentValues(expected.getDatabaseValues(), actual.getDatabaseValues());
|
||||
}
|
||||
|
||||
private static void compareContentValues(ContentValues expected, ContentValues actual) {
|
||||
if (expected == null && actual == null) {
|
||||
return;
|
||||
}
|
||||
if (expected == null || actual == null) {
|
||||
fail();
|
||||
}
|
||||
for (String key : expected.keySet()) {
|
||||
Object entry = expected.get(key);
|
||||
if (entry instanceof Integer) {
|
||||
assertEquals(entry, actual.getAsInteger(key));
|
||||
} else if (entry instanceof String) {
|
||||
assertEquals(entry, actual.getAsString(key));
|
||||
} else if (entry instanceof Long) {
|
||||
assertEquals(entry, actual.getAsLong(key));
|
||||
} else {
|
||||
fail("Unhandled property type: " + entry.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue