Compare commits

..

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

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

3
.gitignore vendored

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

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

@ -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,35 @@
language: android
sudo: required
jdk: oraclejdk8
env:
global:
- TARGET_API=29
- BUILD_TOOLS=29.0.2
- ADB_INSTALL_TIMEOUT=10
matrix:
- EMULATOR_API=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-$TARGET_API
- android-$EMULATOR_API
- sys-img-$ANDROID_ABI-android-$EMULATOR_API
- platform-tools
- build-tools-$BUILD_TOOLS
- extra-android-m2repository
- extra-google-m2repository
before_install:
- yes | sdkmanager "platforms;android-28" # ical4android
- echo no | android create avd --force -n test -t android-$EMULATOR_API --abi $ANDROID_ABI
- emulator -avd test -no-skin -no-audio -no-window &
- ./.wait_for_emulator.sh
- adb shell input keyevent 82 &
script:
- ./gradlew :app:lintGoogleplayRelease
- ./gradlew -Pcoverage :app:createGoogleplayDebugAndroidTestCoverageReport
after_success:
- bash <(curl -s https://codecov.io/bash)

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

File diff suppressed because it is too large Load Diff

@ -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 +1,3 @@
source "https://rubygems.org"
gem "fastlane"
gem "abbrev"

@ -1,230 +1,161 @@
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)
CFPropertyList (3.0.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
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)
babosa (1.0.3)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.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)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
excon (0.72.0)
faraday (0.17.3)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.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)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.141.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
faraday_middleware (~> 0.13.1)
fastimage (>= 2.1.0, < 3.0.0)
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)
google-api-client (>= 0.29.2, < 0.37.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
public_suffix (~> 2.0.0)
rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
google-api-client (0.36.4)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.5.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
google-cloud-env (1.3.0)
faraday (~> 0.11)
google-cloud-errors (1.0.0)
google-cloud-storage (1.25.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.31.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
googleauth (0.10.0)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
signet (~> 0.12)
highline (1.7.10)
http-cookie (1.0.3)
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)
httpclient (2.8.3)
json (2.3.0)
jwt (2.1.0)
memoist (0.16.2)
mini_magick (4.10.1)
mini_mime (1.0.2)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.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)
rouge (2.0.7)
rubyzip (1.3.0)
security (0.1.3)
signet (0.12.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
simctl (1.6.8)
CFPropertyList
naturally
sysrandom (1.0.5)
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-screen (0.7.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.6.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.1)
word_wrap (1.0.0)
xcodeproj (1.27.0)
xcodeproj (1.15.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.1)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
abbrev
fastlane
BUNDLED WITH
2.6.9
2.1.2

@ -11,18 +11,30 @@ Please visit [tasks.org](https://tasks.org) for end user documentation and suppo
---
[![Donate with Bitcoin](https://img.shields.io/badge/bitcoin-donate-yellow.svg?logo=bitcoin)](https://tasks.org/docs/donate)
[![Donate with Bitcoin](https://img.shields.io/badge/bitcoin-donate-yellow.svg?logo=bitcoin)](https://en.cryptobadges.io/donate/136mW34jW3cmZKhxuTDn3tHXMRwbbaRU8s)
[![PayPal donate button](https://img.shields.io/badge/paypal-donate-yellow.svg?logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=alex@tasks.org)
[![Liberapay donate button](https://img.shields.io/liberapay/receives/tasks.svg?logo=liberapay)](https://liberapay.com/tasks/donate)
[![build](https://github.com/tasks/tasks/actions/workflows/bundle.yml/badge.svg)](https://github.com/tasks/tasks/actions/workflows/bundle.yml) [![weblate](https://hosted.weblate.org/widgets/tasks/-/android/svg-badge.svg)](https://hosted.weblate.org/engage/tasks/?utm_source=widget)
### 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).
[![Build Status](https://travis-ci.org/tasks/tasks.svg?branch=master)](https://travis-ci.org/tasks/tasks) [![weblate](https://hosted.weblate.org/widgets/tasks/-/android/svg-badge.svg)](https://hosted.weblate.org/engage/tasks/?utm_source=widget) [![codecov](https://codecov.io/gh/tasks/tasks/branch/master/graph/badge.svg)](https://codecov.io/gh/tasks/tasks)
#### 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
* command line users: clone with `--recurse-submodules` or run `git submodule update --init` after cloning
2. Install and launch [Android Studio](https://developer.android.com/studio/index.html)
3. Select `File > Open`, select the Tasks directory, and accept prompts to install missing SDK components
#### Set up Mapbox
1. 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,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)

@ -1,29 +1,17 @@
@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")
id("com.android.application")
id("checkstyle")
id("io.fabric")
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)
}
kotlin("kapt")
id("com.cookpad.android.plugin.license-tools") version "1.2.2"
id("com.github.ben-manes.versions") version "0.28.0"
}
composeCompiler {
enableStrongSkippingMode = true
repositories {
jcenter()
google()
maven(url = "https://jitpack.io")
}
android {
@ -33,29 +21,33 @@ android {
}
}
buildFeatures {
viewBinding = true
dataBinding = true
compose = true
buildConfig = true
dexOptions {
javaMaxHeapSize = "4g"
}
lint {
lintConfig = file("lint.xml")
textOutput = File("stdout")
lintOptions {
setLintConfig(file("lint.xml"))
textOutput("stdout")
textReport = true
}
compileSdk = libs.versions.android.compileSdk.get().toInt()
compileSdkVersion(Versions.targetSdk)
defaultConfig {
testApplicationId = "org.tasks.test"
applicationId = "org.tasks"
versionCode = libs.versions.versionCode.get().toInt()
versionName = libs.versions.versionName.get()
targetSdk = libs.versions.android.targetSdk.get().toInt()
minSdk = libs.versions.android.minSdk.get().toInt()
testInstrumentationRunner = "org.tasks.TestRunner"
versionCode = 728
versionName = "8.9.1"
targetSdkVersion(Versions.targetSdk)
minSdkVersion(Versions.minSdk)
multiDexEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
}
signingConfigs {
@ -73,32 +65,25 @@ android {
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
flavorDimensions += listOf("store")
@Suppress("LocalVariableName")
buildTypes {
debug {
configure<CrashlyticsExtension> {
mappingFileUploadEnabled = false
}
getByName("debug") {
val tasks_mapbox_key_debug: String? by project
val tasks_google_key_debug: String? by project
val tasks_caldav_url: String? by project
applicationIdSuffix = ".debug"
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")
isTestCoverageEnabled = project.hasProperty("coverage")
}
release {
getByName("release") {
val tasks_mapbox_key: String? by project
val tasks_google_key: String? by project
resValue("string", "mapbox_key", tasks_mapbox_key ?: "")
resValue("string", "google_key", tasks_google_key ?: "")
isMinifyEnabled = true
@ -107,187 +92,121 @@ android {
}
}
flavorDimensions("store")
productFlavors {
create("generic") {
dimension = "store"
setDimension("store")
proguardFile("generic.pro")
}
create("googleplay") {
isDefault = true
dimension = "store"
setDimension("store")
}
}
packaging {
resources {
excludes += setOf("META-INF/*.kotlin_module", "META-INF/INDEX.LIST")
}
viewBinding {
isEnabled = true
}
testOptions {
managedDevices {
localDevices {
create("pixel2api30") {
device = "Pixel 2"
apiLevel = 30
systemImageSource = "aosp-atd"
}
}
}
dataBinding {
isEnabled = true
}
packagingOptions {
exclude("META-INF/*.kotlin_module")
}
}
namespace = "org.tasks"
configure<CheckstyleExtension> {
configFile = project.file("google_checks.xml")
toolVersion = "8.16"
}
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")
exclude(group = "com.google.guava", module = "guava-jdk5")
exclude(group = "org.apache.httpcomponents", module = "httpclient")
exclude(group = "com.google.http-client", module = "google-http-client-apache")
resolutionStrategy {
force("com.squareup.okhttp3:okhttp:" + Versions.okhttp)
}
}
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) {
implementation("com.gitlab.bitfireAT:dav4jvm:1.0.1")
implementation("com.gitlab.bitfireAT:ical4android:1.0") {
exclude(group = "org.threeten", module = "threetenbp")
}
implementation("com.gitlab.bitfireAT:cert4android:1488e39a66")
kapt("com.google.dagger:dagger-compiler:${Versions.dagger}")
implementation("com.google.dagger:dagger:${Versions.dagger}")
implementation("androidx.room:room-rxjava2:${Versions.room}")
kapt("androidx.room:room-compiler:${Versions.room}")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
implementation("androidx.paging:paging-runtime:2.1.2")
kapt("com.jakewharton:butterknife-compiler:${Versions.butterknife}")
implementation("com.jakewharton:butterknife:${Versions.butterknife}")
debugImplementation("com.facebook.flipper:flipper:${Versions.flipper}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${Versions.flipper}")
debugImplementation("com.facebook.soloader:soloader:0.8.2")
debugImplementation("com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}")
implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}")
implementation("io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:2.0.0")
implementation("androidx.multidex:multidex:2.0.1")
implementation("com.squareup.okhttp3:okhttp:${Versions.okhttp}")
implementation("com.google.code.gson:gson:2.8.6")
implementation("com.google.android.material:material:1.1.0")
implementation("androidx.annotation:annotation:1.1.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.0-beta4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
implementation("androidx.preference:preference:1.1.0")
implementation("com.jakewharton.timber:timber:4.7.1")
implementation("com.jakewharton.threetenabp:threetenabp:1.2.3")
implementation("com.google.guava:guava:27.1-android")
implementation("com.jakewharton:process-phoenix:2.0.0")
implementation("com.google.android.apps.dashclock:dashclock-api:2.0.0")
implementation("com.twofortyfouram:android-plugin-api-for-locale:1.0.2")
implementation("com.rubiconproject.oss:jchronic:0.2.6") {
isTransitive = false
}
implementation(libs.jchronic) {
implementation("org.scala-saddle:google-rfc-2445:20110304") {
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)
implementation("com.wdullaer:materialdatetimepicker:4.2.3")
implementation("me.leolin:ShortcutBadger:1.1.22@aar")
implementation("com.google.apis:google-api-services-tasks:v1-rev20200129-1.30.9")
implementation("com.google.apis:google-api-services-drive:v3-rev20200306-1.30.9")
implementation("com.google.api-client:google-api-client-android:1.30.9")
implementation("com.google.auth:google-auth-library-oauth2-http:0.20.0")
implementation("androidx.work:work-runtime:${Versions.work}")
implementation("com.mapbox.mapboxsdk:mapbox-android-sdk:9.0.1")
implementation("com.mapbox.mapboxsdk:mapbox-sdk-services:5.1.0")
implementation("com.etesync:journalmanager:1.1.0")
implementation("com.github.QuadFlask:colorpicker:0.0.15")
googleplayImplementation("com.crashlytics.sdk.android:crashlytics:${Versions.crashlytics}")
googleplayImplementation("com.google.firebase:firebase-analytics:${Versions.firebase}")
googleplayImplementation("com.google.android.gms:play-services-location:17.0.0")
googleplayImplementation("com.google.android.gms:play-services-maps:17.0.0")
googleplayImplementation("com.google.android.libraries.places:places:2.2.0")
googleplayImplementation("com.android.billingclient:billing:1.2.2")
kaptAndroidTest("com.google.dagger:dagger-compiler:${Versions.dagger}")
kaptAndroidTest("com.jakewharton:butterknife-compiler:${Versions.butterknife}")
androidTestImplementation("com.google.dexmaker:dexmaker-mockito:1.2")
androidTestImplementation("com.natpryce:make-it-easy:4.0.1")
androidTestImplementation("androidx.test:runner:1.2.0")
androidTestImplementation("androidx.test:rules:1.2.0")
androidTestImplementation("androidx.test.ext:junit:1.1.1")
androidTestImplementation("androidx.annotation:annotation:1.1.0")
}
apply(mapOf("plugin" to "com.google.gms.google-services"))

@ -0,0 +1 @@
-dontwarn com.google.api.client.googleapis.extensions.android.gms.auth.*

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

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

@ -0,0 +1,810 @@
- artifact: com.gitlab.bitfireAT:dav4jvm:+
name: dav4jvm
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
license: Mozilla Public License, Version 2.0
licenseUrl: https://www.mozilla.org/en-US/MPL/2.0/
- artifact: com.gitlab.bitfireAT:ical4android:+
name: ical4android
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
license: GNU General Public License, Version 3.0
licenseUrl: https://www.gnu.org/licenses/gpl.txt
- artifact: com.gitlab.bitfireAT:cert4android:+
name: cert4android
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
licenseUrl: https://www.gnu.org/licenses/gpl.txt
license: GNU General Public License, Version 3.0
- artifact: androidx.coordinatorlayout:coordinatorlayout:+
name: Android Support Library Coordinator Layout
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.constraintlayout:constraintlayout:+
name: Android ConstraintLayout
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
- artifact: androidx.sqlite:sqlite:+
name: Android DB
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.apis:google-api-services-drive:+
name: Drive API v3-rev136-1.25.0
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.fragment:fragment:+
name: Android Support Library fragment
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.vectordrawable:vectordrawable-animated:+
name: Android Support AnimatedVectorDrawable
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.mapbox.mapboxsdk:mapbox-sdk-services:+
name: Mapbox services
copyrightHolder: Mapbox
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: https://github.com/mapbox/mapbox-java
forceGenerate: true
- artifact: androidx.core:core:+
name: Android Support Library compat
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.arch.core:core-common:+
name: Android Arch-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.j2objc:j2objc-annotations:+
name: J2ObjC Annotations
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/google/j2objc/
- artifact: androidx.room:room-common:+
name: Android Room-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.room:room-runtime:+
name: Android Room-Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.code.findbugs:jsr305:+
name: FindBugs-jsr305
copyrightHolder: FindBugs Project
license: BSD 2-Clause
licenseUrl: https://opensource.org/licenses/BSD-2-Clause
url: http://findbugs.sourceforge.net/
forceGenerate: true
- artifact: com.google.code.gson:gson:+
name: Gson
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: me.leolin:ShortcutBadger:+
name: ShortcutBadger
copyrightHolder: Leo Lin
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/leolin310148/ShortcutBadger
- artifact: androidx.lifecycle:lifecycle-runtime:+
name: Android Lifecycle Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.versionedparcelable:versionedparcelable:+
name: VersionedParcelable and friends
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.mapbox.mapboxsdk:mapbox-sdk-turf:+
name: Mapbox services-turf
copyrightHolder: Mapbox
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: https://github.com/mapbox/mapbox-java
forceGenerate: true
- artifact: androidx.viewpager:viewpager:+
name: Android Support Library View Pager
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.mapbox.mapboxsdk:mapbox-android-core:+
name: Mapbox Android Core Library
copyrightHolder: Mapbox
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: https://github.com/mapbox/mapbox-events-android
forceGenerate: true
- artifact: androidx.lifecycle:lifecycle-livedata:+
name: Android Lifecycle LiveData
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: commons-codec:commons-codec:+
name: Apache Commons Codec
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://commons.apache.org/proper/commons-codec/
- artifact: com.mapbox.mapboxsdk:mapbox-android-sdk:+
name: Mapbox Maps SDK for Android
copyrightHolder: Mapbox
license: BSD 2-Clause
licenseUrl: https://opensource.org/licenses/BSD-2-Clause
url: https://github.com/mapbox/mapbox-gl-native
forceGenerate: true
- artifact: androidx.annotation:annotation:+
name: Android Support Library Annotations
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.interpolator:interpolator:+
name: Android Support Library Interpolators
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: javax.inject:javax.inject:+
name: javax.inject
copyrightHolder: The JSR-330 Expert Group
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://code.google.com/p/atinject/
- artifact: com.twofortyfouram:android-plugin-api-for-locale:+
name: android-plugin-api-for-locale
copyrightHolder: two forty four a.m. LLC.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-viewmodel:+
name: Android Lifecycle ViewModel
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: org.scala-saddle:google-rfc-2445:+
name: google-rfc-2445
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://code.google.com/p/google-rfc-2445/
- artifact: com.google.dagger:dagger:+
name: Dagger
copyrightHolder: The Dagger Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/google/dagger
- artifact: com.google.guava:guava:+
name: Guava Google Core Libraries for Java
copyrightHolder: The Guava Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.jetbrains:annotations:+
name: JetBrains Java Annotations
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JetBrains/java-annotations
- artifact: com.wdullaer:materialdatetimepicker:+
name: MaterialDateTimePicker
copyrightHolder: Wouter Dullaert
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/wdullaer/MaterialDateTimePicker
- artifact: org.apache.commons:commons-lang3:+
name: Apache Commons Lang
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://commons.apache.org/proper/commons-lang/
- artifact: com.mapbox.mapboxsdk:mapbox-sdk-geojson:+
name: Mapbox services-geojson
copyrightHolder: Mapbox
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: https://github.com/mapbox/mapbox-java
forceGenerate: true
- artifact: com.mapbox.mapboxsdk:mapbox-android-telemetry:+
name: Mapbox Android Telemetry Library
copyrightHolder: Mapbox
license: BSD 2-Clause
licenseUrl: https://opensource.org/licenses/BSD-2-Clause
url: https://github.com/mapbox/mapbox-events-android
forceGenerate: true
- artifact: androidx.loader:loader:+
name: Android Support Library loader
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.cursoradapter:cursoradapter:+
name: Android Support Library Cursor Adapter
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-livedata-core:+
name: Android Lifecycle LiveData Core
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.customview:customview:+
name: Android Support Library Custom View
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: io.reactivex.rxjava2:rxandroid:+
name: RxAndroid
copyrightHolder: The RxAndroid authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/ReactiveX/RxAndroid
- artifact: org.threeten:threetenbp:+
name: ThreeTen backport
copyrightHolder: Stephen Colebourne & Michael Nascimento Santos
license: BSD 3-Clause
licenseUrl: https://opensource.org/licenses/BSD-3-Clause
url: https://www.threeten.org/threetenbp
- artifact: androidx.swiperefreshlayout:swiperefreshlayout:+
name: Android Support Library Custom View
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.documentfile:documentfile:+
name: Android Support Library Document File
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.google.api-client:google-api-client-android:+
name: Android Platform Extensions to the Google APIs Client Library for Java.
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-extensions:+
name: Android Lifecycle Extensions
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.getkeepsafe.relinker:relinker:+
name: ReLinker
copyrightHolder: Keepsafe Software Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/KeepSafe/ReLinker
- artifact: androidx.legacy:legacy-support-core-utils:+
name: Android Support Library core utils
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.arch.core:core-runtime:+
name: Android Arch-Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: org.apache.commons:commons-collections4:+
name: Apache Commons Collections
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://commons.apache.org/proper/commons-collections/
- artifact: org.mnode.ical4j:ical4j:+
name: ical4j
copyrightHolder: Ben Fortuna
license: BSD 3-Clause
licenseUrl: https://opensource.org/licenses/BSD-3-Clause
url: http://ical4j.github.io
forceGenerate: true
- artifact: androidx.recyclerview:recyclerview:+
name: Android Support RecyclerView v7
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: net.jcip:jcip-annotations:+
name: Java Concurrency in Practice book annotations
copyrightHolder: Stephen Connolly
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://jcip.net/
- artifact: androidx.collection:collection:+
name: Android Support Library collections
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.fasterxml.jackson.core:jackson-core:+
name: Jackson-core
copyrightHolder: FasterXML
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/FasterXML/jackson-core
- artifact: androidx.cardview:cardview:+
name: Android Support CardView v7
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.print:print:+
name: Android Support Library Print
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.rubiconproject.oss:jchronic:+
name: jchronic
copyrightHolder: The jchronic authors
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: http://github.com/samtingleff/jchronic
- artifact: androidx.sqlite:sqlite-framework:+
name: Android Support SQLite - Framework Implementation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.google.android.material:material:+
name: Material Components for Android
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.localbroadcastmanager:localbroadcastmanager:+
name: Android Support Library Local Broadcast Manager
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.google.android.apps.dashclock:dashclock-api:+
name: DashClock API
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://dashclock.com/api
- artifact: androidx.vectordrawable:vectordrawable:+
name: Android Support VectorDrawable
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: org.reactivestreams:reactive-streams:+
name: reactive-streams
copyrightHolder: Public domain
license: CC0
licenseUrl: http://creativecommons.org/publicdomain/zero/1.0/
url: http://www.reactive-streams.org/
- artifact: androidx.work:work-runtime:+
name: Android WorkManager Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.appcompat:appcompat:+
name: Android AppCompat Library v7
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.lifecycle:lifecycle-common:+
name: Android Lifecycle-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.mapbox.mapboxsdk:mapbox-sdk-core:+
name: Mapbox services-core
copyrightHolder: Mapbox
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: https://github.com/mapbox/mapbox-java
forceGenerate: true
- artifact: androidx.multidex:multidex:+
name: Android Multi-Dex Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.mapbox.mapboxsdk:mapbox-android-gestures:+
name: Mapbox Android Gestures Library
copyrightHolder: Mapbox
license: BSD 2-Clause
licenseUrl: https://opensource.org/licenses/BSD-2-Clause
url: https://github.com/mapbox/mapbox-gestures-android
- artifact: com.jakewharton.threetenabp:threetenabp:+
name: ThreeTenAbp
copyrightHolder: Jake Wharton
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JakeWharton/ThreeTenABP/
- artifact: androidx.lifecycle:lifecycle-process:+
name: Android Lifecycle Process
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-service:+
name: Android Lifecycle Service
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.transition:transition:+
name: Android Transition Support Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: com.jakewharton.timber:timber:+
name: Timber
copyrightHolder: Jake Wharton
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JakeWharton/timber
- artifact: com.google.oauth-client:google-oauth-client:+
name: Google OAuth Client Library for Java
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.room:room-rxjava2:+
name: Android Room RXJava2
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.drawerlayout:drawerlayout:+
name: Android Support Library Drawer Layout
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: io.reactivex.rxjava2:rxjava:+
name: RxJava
copyrightHolder: RxJava Contributors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/ReactiveX/RxJava
- artifact: com.google.apis:google-api-services-tasks:+
name: Tasks API v1-rev55-1.25.0
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.google.api-client:google-api-client:+
name: Google APIs Client Library for Java
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.constraintlayout:constraintlayout-solver:+
name: Android ConstraintLayout Solver
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
- artifact: io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:+
name: sectionedrecyclerviewadapter
copyrightHolder: Gustavo Pagani
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
- artifact: org.jetbrains.kotlin:kotlin-stdlib:+
name: org.jetbrains.kotlin:kotlin-stdlib
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: com.google.http-client:google-http-client:+
name: Google HTTP Client Library for Java
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.slf4j:slf4j-jdk14:+
name: SLF4J JDK14 Binding
copyrightHolder: QOS.ch
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: http://www.slf4j.org
- artifact: com.google.http-client:google-http-client-android:+
name: Android Platform Extensions to the Google HTTP Client Library for Java.
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.squareup.okhttp3:okhttp:+
name: OkHttp
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.squareup.retrofit2:retrofit:+
name: Retrofit
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.slf4j:slf4j-api:+
name: SLF4J API Module
copyrightHolder: QOS.ch
license: MIT License
licenseUrl: http://www.opensource.org/licenses/mit-license.php
url: http://www.slf4j.org
- artifact: org.jetbrains.kotlin:kotlin-stdlib-common:+
name: org.jetbrains.kotlin:kotlin-stdlib-common
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: com.google.http-client:google-http-client-jackson2:+
name: Jackson 2 extensions to the Google HTTP Client Library for Java.
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.jakewharton:butterknife:+
name: ButterKnife
copyrightHolder: Jake Wharton
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JakeWharton/butterknife/
- artifact: com.jakewharton:butterknife-annotations:+
name: ButterKnife Annotations
copyrightHolder: Jake Wharton
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JakeWharton/butterknife/
- artifact: com.squareup.retrofit2:converter-gson:+
name: "Converter: Gson"
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.squareup.okhttp3:logging-interceptor:+
name: OkHttp Logging Interceptor
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.jetbrains.kotlin:kotlin-stdlib-jdk7:+
name: org.jetbrains.kotlin:kotlin-stdlib-jdk7
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: com.jakewharton:butterknife-runtime:+
name: ButterKnife Runtime
copyrightHolder: Jake Wharton
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JakeWharton/butterknife/
- artifact: com.jakewharton:process-phoenix:+
name: Process Phoenix
copyrightHolder: Jake Wharton
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/JakeWharton/ProcessPhoenix/
- artifact: io.grpc:grpc-context:+
name: io.grpc:grpc-context
copyrightHolder: The gRPC Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/grpc/grpc-java
- artifact: com.google.guava:listenablefuture:+
name: Guava ListenableFuture only
copyrightHolder: The Guava Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.google.errorprone:error_prone_annotations:+
name: error-prone annotations
copyrightHolder: The Error Prone Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: io.opencensus:opencensus-api:+
name: OpenCensus API
copyrightHolder: OpenCensus Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/census-instrumentation/opencensus-java
- artifact: com.google.guava:failureaccess:+
name: Guava InternalFutureFailureAccess and InternalFutures
copyrightHolder: The Guava Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.checkerframework:checker-compat-qual:+
name: Checker Qual
copyrightHolder: The Checker Framework Developers
license: GNU General Public License, Version 2.0 + classpath exception
licenseUrl: http://www.gnu.org/software/classpath/license.html
url: https://checkerframework.org
- artifact: io.opencensus:opencensus-contrib-http-util:+
name: OpenCensus contrib-http-util
copyrightHolder: OpenCensus Authors
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/census-instrumentation/opencensus-java
- artifact: org.ogce:xpp3:+
skip: true
- artifact: junit:junit:+
skip: true
- artifact: jakarta-regexp:jakarta-regexp:+
skip: true
- artifact: androidx.core:core-ktx:+
name: Core Kotlin Extensions
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.appcompat:appcompat-resources:+
name: Android Resources Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.viewpager2:viewpager2:+
name: AndroidX Widget ViewPager2
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.savedstate:savedstate:+
name: Activity
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.activity:activity:+
name: Activity
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.paging:paging-runtime:+
name: Android Paging-Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.paging:paging-common:+
name: Android Paging-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-core-common:+
name: kotlinx-coroutines-core-common
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: org.conscrypt:conscrypt-android:+
name: org.conscrypt:conscrypt-android
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0
url: https://conscrypt.org/
- artifact: org.jetbrains.kotlin:kotlin-stdlib-jdk8:+
name: org.jetbrains.kotlin:kotlin-stdlib-jdk8
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-android:+
name: kotlinx-coroutines-android
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: androidx.databinding:databinding-adapters:+
name: databinding-adapters
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-viewmodel-ktx:+
name: Android Lifecycle ViewModel Kotlin Extensions
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.annotation:annotation-experimental:+
name: Experimental annotation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-core:+
name: kotlinx-coroutines-core
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: androidx.databinding:databinding-common:+
name: Data Binding Base Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/studio
- artifact: androidx.databinding:databinding-runtime:+
name: databinding-runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.apache.httpcomponents:httpcore:+
name: Apache HttpCore
copyrightHolder: The Apache Software Foundation
license: The Apache Software License, Version 2.0
url: http://hc.apache.org/httpcomponents-core-ga
- artifact: androidx.databinding:viewbinding:+
name: viewbinding
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: com.madgag.spongycastle:core:+
name: Spongy Castle Core
copyrightHolder: The Legion of the Bouncy Castle Inc.
license: Bouncy Castle Licence
licenseUrl: http://www.bouncycastle.org/licence.html
url: http://rtyley.github.io/spongycastle/
- artifact: com.etesync:journalmanager:+
name: EteSync JVM
copyrightHolder: Tom Hacohen
license: LGPL-3.0-only
licenseUrl: https://spdx.org/licenses/LGPL-3.0-only.html
url: https://www.etesync.com
- artifact: com.madgag.spongycastle:prov:+
name: Spongy Castle
copyrightHolder: The Legion of the Bouncy Castle Inc.
license: Bouncy Castle Licence
licenseUrl: http://www.bouncycastle.org/licence.html
url: http://rtyley.github.io/spongycastle/
- artifact: androidx.preference:preference:+
name: AndroidX Preference
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.lifecycle:lifecycle-viewmodel-savedstate:+
name: Android Lifecycle ViewModel with SavedState
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: com.github.QuadFlask:colorpicker:+
name: QuadFlask/colorpicker
copyrightHolder: QuadFlask
license: The Apache Software License, Version 2.0
url: https://github.com/QuadFlask/colorpicker
- artifact: com.google.auth:google-auth-library-credentials:+
name: Google Auth Library for Java - Credentials
copyrightHolder: Google Inc.
license: BSD 3-Clause
- artifact: com.google.auth:google-auth-library-oauth2-http:+
name: Google Auth Library for Java - OAuth2 HTTP
copyrightHolder: Google Inc.
license: BSD 3-Clause
- artifact: com.google.auto.value:auto-value-annotations:+
name: AutoValue Annotations
copyrightHolder: Google LLC
license: The Apache Software License, Version 2.0
url: https://github.com/google/auto/tree/master/value
- artifact: com.squareup.okio:okio:+
name: Okio
copyrightHolder: Square, Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/square/okio/

52
app/proguard.pro vendored

@ -2,6 +2,30 @@
-keep class org.tasks.** { *; }
# remove logging statements
-assumenosideeffects class timber.log.Timber* {
public static *** v(...);
public static *** d(...);
public static *** i(...);
}
# https://code.google.com/p/android/issues/detail?id=78293
-keep public class android.support.v7.widget.** { *; }
-keep public class android.support.v7.internal.widget.** { *; }
-keep public class android.support.v7.internal.view.menu.** { *; }
-keep public class * extends android.support.v4.view.ActionProvider {
public <init>(android.content.Context);
}
# google-rfc-2445-20110304
-dontwarn com.google.ical.compat.jodatime.**
# https://github.com/JakeWharton/butterknife/blob/581666a28022796fdd62caaf3420e621215abfda/butterknife/proguard-rules.txt
-keep public class * implements butterknife.Unbinder { public <init>(**, android.view.View); }
-keep class butterknife.*
-keepclasseswithmembernames class * { @butterknife.* <methods>; }
-keepclasseswithmembernames class * { @butterknife.* <fields>; }
# guava
-dontwarn sun.misc.Unsafe
-dontwarn java.lang.ClassValue
@ -9,7 +33,9 @@
-dontwarn javax.inject.**
-dontwarn com.google.j2objc.annotations.**
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn com.google.errorprone.annotations.**
-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
-dontwarn com.google.errorprone.annotations.concurrent.LazyInit
-dontwarn com.google.errorprone.annotations.ForOverride
# https://github.com/square/okhttp/blob/0b74bba08805c28f6aede626cf06f213ef6480f2/README.md
-dontwarn okhttp3.**
@ -26,9 +52,8 @@
-dontwarn net.fortuna.ical4j.model.**
-dontwarn org.codehaus.groovy.**
-dontwarn org.apache.log4j.** # ignore warnings from log4j dependency
-dontwarn com.github.erosb.jsonsKema.** # ical4android
-dontwarn org.jparsec.** # ical4android
-keep class net.fortuna.ical4j.** { *; } # keep all model classes (properties/factories, created at runtime)
-keep class org.threeten.bp.** { *; } # keep ThreeTen (for time zone processing)
-keep class at.bitfire.** { *; } # all DAVdroid code is required
# https://github.com/google/google-api-java-client-samples/blob/34c3b43cb15f4ee1b636a0e01521cc81a2451dcd/tasks-android-sample/proguard-google-api-client.txt
@ -42,23 +67,4 @@
-dontnote java.nio.file.Files, java.nio.file.Path
-dontnote **.ILicensingService
-dontnote sun.misc.Unsafe
-dontwarn sun.misc.Unsafe
# errors from upgrading to AGP 8
-dontwarn java.beans.Transient
-dontwarn org.joda.convert.FromString
-dontwarn org.joda.convert.ToString
-dontwarn org.json.JSONString
# material icons
-keep class androidx.compose.material.icons.outlined.** { *; }
# microsoft authentication
-dontwarn com.microsoft.device.display.DisplayMask
-dontwarn com.google.android.libraries.identity.**
-dontwarn edu.umd.cs.findbugs.annotations.**
-dontwarn com.google.crypto.tink.subtle.**
-dontwarn net.jcip.annotations.**
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite { <fields>; }
-dontwarn sun.misc.Unsafe

@ -0,0 +1,261 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.test;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.content.res.Configuration;
import android.content.res.Resources;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.R;
/**
* 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);
}
/** 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() {
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() {
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;
}
public interface Callback<T> {
void apply(T entry);
}
private static final class FormatStringData {
private static final char[] scratch = new char[10];
/** format characters */
final char[] characters;
/** the original string */
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 */
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();
}
}
}

@ -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,440 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.utility;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.todoroo.andlib.utility.DateUtilities.getDateString;
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 junit.framework.Assert.assertEquals;
import static org.tasks.Freeze.freezeAt;
import static org.tasks.date.DateTimeUtils.newDate;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.Locale;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.Snippet;
import org.tasks.time.DateTime;
import org.threeten.bp.format.FormatStyle;
@RunWith(AndroidJUnit4.class)
public class DateUtilitiesTest {
@After
public void after() {
DateUtilities.is24HourOverride = null;
}
@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, 2014", getDateString(getApplicationContext(), new DateTime(2014, 1, 4, 0, 0, 0)));
}
@Test
public void testGetDateStringHidingYear() {
freezeAt(newDate(2014, 2, 1))
.thawAfter(
new Snippet() {
{
assertEquals("Jan 1", getDateString(getApplicationContext(), new DateTime(2014, 1, 1)));
}
});
}
@Test
public void testGetDateStringWithDifferentYear() {
freezeAt(newDate(2013, 12, 1))
.thawAfter(
new Snippet() {
{
assertEquals("Jan 1, 2014", getDateString(getApplicationContext(),new DateTime(2014, 1, 1, 0, 0, 0)));
}
});
}
@Test
public void testGetWeekdayLongString() {
assertEquals("Sunday", getWeekday(newDate(2013, 12, 29), Locale.US));
assertEquals("Monday", getWeekday(newDate(2013, 12, 30), Locale.US));
assertEquals("Tuesday", getWeekday(newDate(2013, 12, 31), Locale.US));
assertEquals("Wednesday", getWeekday(newDate(2014, 1, 1), Locale.US));
assertEquals("Thursday", getWeekday(newDate(2014, 1, 2), Locale.US));
assertEquals("Friday", getWeekday(newDate(2014, 1, 3), Locale.US));
assertEquals("Saturday", getWeekday(newDate(2014, 1, 4), Locale.US));
}
@Test
public void testGetWeekdayShortString() {
assertEquals("Sun", getWeekdayShort(newDate(2013, 12, 29), Locale.US));
assertEquals("Mon", getWeekdayShort(newDate(2013, 12, 30), Locale.US));
assertEquals("Tue", getWeekdayShort(newDate(2013, 12, 31), Locale.US));
assertEquals("Wed", getWeekdayShort(newDate(2014, 1, 1), Locale.US));
assertEquals("Thu", getWeekdayShort(newDate(2014, 1, 2), Locale.US));
assertEquals("Fri", getWeekdayShort(newDate(2014, 1, 3), Locale.US));
assertEquals("Sat", getWeekdayShort(newDate(2014, 1, 4), Locale.US));
}
@Test
public void getRelativeFullDate() {
freezeAt(new DateTime(2018, 1, 1))
.thawAfter(
() ->
assertEquals(
"Sunday, January 14",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.US,
FormatStyle.FULL)));
}
@Test
public void getRelativeFullDateWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
assertEquals(
"Sunday, January 14, 2018",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.US,
FormatStyle.FULL)));
}
@Test
public void getRelativeFullDateTime() {
freezeAt(new DateTime(2018, 1, 1))
.thawAfter(
() ->
assertEquals(
"Sunday, January 14 1:43 PM",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 43, 1).getMillis(),
Locale.US,
FormatStyle.FULL)));
}
@Test
public void getRelativeFullDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
assertEquals(
"Sunday, January 14, 2018 11:50 AM",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 11, 50, 1).getMillis(),
Locale.US,
FormatStyle.FULL)));
}
@Test
public void germanDateNoYear() {
freezeAt(new DateTime(2018, 1, 1))
.thawAfter(
() ->
Assert.assertEquals(
"Sonntag, 14. Januar",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.GERMAN,
FormatStyle.FULL)));
}
@Test
public void germanDateWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"Sonntag, 14. Januar 2018",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.GERMAN,
FormatStyle.FULL)));
}
@Test
public void koreanDateNoYear() {
freezeAt(new DateTime(2018, 1, 1))
.thawAfter(
() ->
Assert.assertEquals(
"1월 14일 일요일",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.KOREAN,
FormatStyle.FULL)));
}
@Test
public void koreanDateWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"2018년 1월 14일 일요일",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.KOREAN,
FormatStyle.FULL)));
}
@Test
public void japaneseDateNoYear() {
freezeAt(new DateTime(2018, 1, 1))
.thawAfter(
() ->
Assert.assertEquals(
"1月14日日曜日",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.JAPANESE,
FormatStyle.FULL)));
}
@Test
public void japaneseDateWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"2018年1月14日日曜日",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.JAPANESE,
FormatStyle.FULL)));
}
@Test
public void chineseDateNoYear() {
freezeAt(new DateTime(2018, 1, 1))
.thawAfter(
() ->
Assert.assertEquals(
"1月14日星期日",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.CHINESE,
FormatStyle.FULL)));
}
@Test
public void chineseDateWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"2018年1月14日星期日",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14).getMillis(),
Locale.CHINESE,
FormatStyle.FULL)));
}
@Test
public void chineseDateTimeNoYear() {
freezeAt(new DateTime(2018, 1, 1))
.thawAfter(
() ->
Assert.assertEquals(
"1月14日星期日 上午11:53",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 11, 53, 1).getMillis(),
Locale.CHINESE,
FormatStyle.FULL)));
}
@Test
public void chineseDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"2018年1月14日星期日 下午1:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.CHINESE,
FormatStyle.FULL)));
}
@Test
public void frenchDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"dimanche 14 janvier 2018 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.FRENCH,
FormatStyle.FULL)));
}
@Test
public void indiaDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"रविवार, 14 जनवरी 2018 1:45 pm",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("hi-IN"),
FormatStyle.FULL)));
}
@Test
public void russiaDateTimeNoYear() {
freezeAt(new DateTime(2018, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"воскресенье, 14 января 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("ru"),
FormatStyle.FULL)));
}
@Test
public void russiaDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"воскресенье, 14 января 2018 г. 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("ru"),
FormatStyle.FULL)));
}
@Test
public void brazilDateTimeNoYear() {
freezeAt(new DateTime(2018, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"domingo, 14 de janeiro 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("pt-br"),
FormatStyle.FULL)));
}
@Test
public void brazilDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"domingo, 14 de janeiro de 2018 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("pt-br"),
FormatStyle.FULL)));
}
@Test
public void spainDateTimeNoYear() {
freezeAt(new DateTime(2018, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"domingo, 14 de enero 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("es"),
FormatStyle.FULL)));
}
@Test
public void spainDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"domingo, 14 de enero de 2018 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("es"),
FormatStyle.FULL)));
}
@Test
public void hebrewDateTimeNoYear() {
freezeAt(new DateTime(2018, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"יום ראשון, 14 בינואר 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("iw"),
FormatStyle.FULL)));
}
@Test
public void hebrewDateTimeWithYear() {
freezeAt(new DateTime(2017, 12, 12))
.thawAfter(
() ->
Assert.assertEquals(
"יום ראשון, 14 בינואר 2018 13:45",
DateUtilities.getRelativeDateTime(
getApplicationContext(),
new DateTime(2018, 1, 14, 13, 45, 1).getMillis(),
Locale.forLanguageTag("iw"),
FormatStyle.FULL)));
}
}

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

@ -0,0 +1,82 @@
package com.todoroo.andlib.utility;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
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;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.Locale;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.time.DateTime;
import org.threeten.bp.format.FormatStyle;
@RunWith(AndroidJUnit4.class)
public class RelativeDayTest {
private static final DateTime now = new DateTime(2013, 12, 31, 11, 9, 42, 357);
private static Locale defaultLocale;
@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), "December 24", "Dec 24");
}
@Test
public void testRelativeDayOneWeekNextYear() {
checkRelativeDay(new DateTime().plusDays(7), "January 7, 2014", "Jan 7, 2014");
}
private void checkRelativeDay(DateTime now, String full, String abbreviated) {
assertEquals(
full,
getRelativeDay(getApplicationContext(), now.getMillis(), Locale.US, FormatStyle.LONG));
assertEquals(
abbreviated,
getRelativeDay(getApplicationContext(), now.getMillis(), Locale.US, FormatStyle.MEDIUM));
}
}

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

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

Loading…
Cancel
Save