diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2203f43003e0e8214eb3ec5b8aa4c0d0d52ea647..58abbe15f0a74d9357a24e8507050881cc4283d4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,20 +1,12 @@ -image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:legacy" +image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest" stages: - build -variables: - GRADLE_VERSION: "gradle-5.4.1" - GIT_SUBMODULE_STRATEGY: "recursive" - before_script: - - git fetch + - export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 - export GRADLE_USER_HOME=$(pwd)/.gradle - - curl -sL https://services.gradle.org/distributions/${GRADLE_VERSION}-bin.zip -o /gradle.zip - - unzip /gradle.zip -d /gradle - - PATH="$PATH:/gradle/${GRADLE_VERSION}/bin" - - JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS} -Dfile.encoding=UTF8" - - gradle dependencies + - chmod +x ./gradlew cache: key: ${CI_PROJECT_ID} @@ -24,7 +16,7 @@ cache: build: stage: build script: - - gradle assemble + - ./gradlew assemble artifacts: paths: - - opentasks/build/outputs/ + - opentasks/build/outputs/apk/ diff --git a/build.gradle b/build.gradle index 6701d0896011ba7028edd93998c3ee0a75805553..9c5ba578b0eb702b4eee51d6a28184bc476388f1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,34 +1,4 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - google() - jcenter() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' - classpath("com.github.triplet.gradle:play-publisher:2.8.1") - } +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false } - -def gitVersion = { -> - def stdout = new ByteArrayOutputStream() - exec { - commandLine 'git', 'describe', '--tags', '--always', '--dirty' - standardOutput = stdout - } - return stdout.toString().trim() -} - -allprojects { - version gitVersion() - - repositories { - google() - jcenter() - maven { url "https://jitpack.io" } - maven { url 'https://gitlab.e.foundation/api/v4/groups/9/-/packages/maven'} - } -} - -apply from: "dependencies.gradle" diff --git a/dependencies.gradle b/dependencies.gradle deleted file mode 100644 index 003aa290432e2d2819a837738b1e426f6f6f38c8..0000000000000000000000000000000000000000 --- a/dependencies.gradle +++ /dev/null @@ -1,38 +0,0 @@ -def jems_version = '1.43' -def contentpal_version = '0.6' -def androidx_test_runner_version = '1.1.1' - -ext.deps = [ - // Support & Google libraries - support_appcompat : 'androidx.appcompat:appcompat:1.2.0', - support_annotations: 'androidx.annotation:annotation:1.1.0', - support_design : 'com.google.android.material:material:1.2.1', - android_dashclock : 'com.google.android.apps.dashclock:dashclock-api:2.0.0', - - // dmfs - jems : "org.dmfs:jems:$jems_version", - datetime : 'org.dmfs:rfc5545-datetime:0.2.4', - lib_recur : 'org.dmfs:lib-recur:0.12.2', - xml_magic : 'org.dmfs:android-xml-magic:0.1.1', - color_picker : 'com.github.dmfs:color-picker:1.3', - android_carrot : 'com.github.dmfs.androidcarrot:androidcarrot:13edc04', - bolts_color : 'com.github.dmfs.Bolts:color-bolts:0.1', - contentpal : "com.github.dmfs.contentpal:contentpal:$contentpal_version", - retention_magic : 'com.github.dmfs:retention-magic:1.3', - - // 3rd party - codeka_carrot : 'au.com.codeka:carrot:2.4.0', - - // Testing - junit : 'junit:junit:4.12', - hamcrest : 'org.hamcrest:hamcrest-library:1.3', - mockito : 'org.mockito:mockito-core:2.27.0', - robolectric : 'org.robolectric:robolectric:3.5.1', - support_test_runner: "androidx.test:runner:$androidx_test_runner_version", - support_test_rules : "androidx.test:rules:$androidx_test_runner_version", - - // dmfs testing - jems_testing : "org.dmfs:jems-testing:$jems_version", - contenttestpal : "com.github.dmfs.contentpal:contenttestpal:$contentpal_version", - contentpal_testing : "com.github.dmfs.contentpal:contentpal-testing:$contentpal_version" -] \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e32164be7ca8e4a04128d3233e6c7e7dc5153f28..38c803f5f7fbd11af77e6de24cd9931a5bbd44ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,3 @@ -COMPILE_SDK_VERSION=29 -MIN_SDK_VERSION=21 -TARGET_SDK_VERSION=29 -VERSION_OVERRIDE=0 +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.enableJetifier=true android.useAndroidX=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000000000000000000000000000000000..553a6362c0438be3272c365eb50c2b10ddc17b42 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,59 @@ +[versions] +android-carrot = "13edc04" +android-gradle-plugin = "8.13.1" +androidx-test-runner = "1.7.0" +annotation = "1.9.1" +appcompat = "1.7.1" +bolts-color = "0.1" +codeka-carrot = "2.4.5" +color-picker = "1.3" +contentpal = "0.6" +datetime = "0.2.4" +elib = "0.0.1-alpha11" +hamcrest = "3.0" +jems = "1.43" +junit = "4.13.2" +lib-recur = "0.12.2" +material = "1.13.0" +mockito = "5.20.0" +preference = "1.1.1" +recurpicker = "2.1.4" +retention-magic = "1.3" +robolectric = "4.16" +rxandroid = "2.1.1" +rxjava = "2.2.21" +xml-magic = "0.1.1" + +[libraries] +android-carrot = { module = "com.github.dmfs.androidcarrot:androidcarrot", version.ref = "android-carrot" } +android-dashclock = { module = "com.google.android.apps.dashclock:dashclock-api", version = "2.0.0" } +bolts-color = { module = "com.github.dmfs.Bolts:color-bolts", version.ref = "bolts-color" } +codeka-carrot = { module = "au.com.codeka:carrot", version.ref = "codeka-carrot" } +color-picker = { module = "com.github.dmfs:color-picker", version.ref = "color-picker" } +contentpal = { module = "com.github.dmfs.contentpal:contentpal", version.ref = "contentpal" } +contentpal-testing = { module = "com.github.dmfs.contentpal:contentpal-testing", version.ref = "contentpal" } +contenttestpal = { module = "com.github.dmfs.contentpal:contenttestpal", version.ref = "contentpal" } +datetime = { module = "org.dmfs:rfc5545-datetime", version.ref = "datetime" } +elib = { module = "foundation.e:elib", version.ref = "elib" } +hamcrest = { module = "org.hamcrest:hamcrest-library", version.ref = "hamcrest" } +jems = { module = "org.dmfs:jems", version.ref = "jems" } +jems-testing = { module = "org.dmfs:jems-testing", version.ref = "jems" } +junit = { module = "junit:junit", version.ref = "junit" } +lib-recur = { module = "org.dmfs:lib-recur", version.ref = "lib-recur" } +material = { module = "com.google.android.material:material", version.ref = "material" } +mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } +preference = { module = "androidx.preference:preference", version.ref = "preference" } +recurpicker = { module = "com.maltaisn:recurpicker", version.ref = "recurpicker" } +retention-magic = { module = "com.github.dmfs:retention-magic", version.ref = "retention-magic" } +robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroid" } +rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" } +support-annotations = { module = "androidx.annotation:annotation", version.ref = "annotation" } +support-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +support-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-runner" } +support-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } +xml-magic = { module = "org.dmfs:android-xml-magic", version.ref = "xml-magic" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } +android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ce73a4441b9aadd179b127052781f47455914c17..ed8581f154565a222f7c0b41f59080d44f05df3b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip diff --git a/keystore/platform.jks b/keystore/platform.jks new file mode 100644 index 0000000000000000000000000000000000000000..b778840542e79c048bcf570aa960243eeb9b9d53 Binary files /dev/null and b/keystore/platform.jks differ diff --git a/opentasks-contract/build.gradle b/opentasks-contract/build.gradle index 674316aaee8cb5a1f01a2f3884f446bce3ca5c5c..2556a61cacf4aeeaa965c4539fbc47b940eac070 100644 --- a/opentasks-contract/build.gradle +++ b/opentasks-contract/build.gradle @@ -1,11 +1,14 @@ -apply plugin: 'com.android.library' +plugins { + alias(libs.plugins.android.library) +} android { - compileSdkVersion COMPILE_SDK_VERSION.toInteger() + namespace = "org.dmfs.tasks.contract" + compileSdkVersion 36 defaultConfig { - minSdkVersion MIN_SDK_VERSION.toInteger() - targetSdkVersion TARGET_SDK_VERSION.toInteger() + minSdkVersion 21 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -17,8 +20,8 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } } diff --git a/opentasks-provider/build.gradle b/opentasks-provider/build.gradle index 21857c9507e889be01eef4e29721e56f311e3cf8..0ac44f88745014f94678c709a550812479a7a4ac 100644 --- a/opentasks-provider/build.gradle +++ b/opentasks-provider/build.gradle @@ -1,11 +1,14 @@ -apply plugin: 'com.android.library' +plugins { + alias(libs.plugins.android.library) +} android { - compileSdkVersion COMPILE_SDK_VERSION.toInteger() + namespace = "org.dmfs.tasks.provider" + compileSdkVersion 36 defaultConfig { - minSdkVersion MIN_SDK_VERSION.toInteger() - targetSdkVersion TARGET_SDK_VERSION.toInteger() + minSdkVersion 21 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -17,8 +20,8 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } packagingOptions { exclude 'META-INF/NOTICE' @@ -28,23 +31,23 @@ android { dependencies { implementation project(':opentasks-contract') - implementation deps.datetime - implementation deps.lib_recur - implementation deps.jems + implementation libs.datetime + implementation libs.lib.recur + implementation libs.jems - testImplementation deps.robolectric - testImplementation deps.junit - testImplementation deps.mockito - testImplementation deps.jems_testing - testImplementation deps.hamcrest + testImplementation libs.robolectric + testImplementation libs.junit + testImplementation libs.mockito + testImplementation libs.jems.testing + testImplementation libs.hamcrest androidTestImplementation project(':opentaskspal') - androidTestImplementation deps.contenttestpal - androidTestImplementation deps.support_annotations - androidTestImplementation deps.support_test_runner - androidTestImplementation deps.support_test_rules - androidTestImplementation deps.mockito - androidTestImplementation deps.jems_testing - androidTestImplementation deps.hamcrest - androidTestImplementation deps.contentpal_testing + androidTestImplementation libs.contenttestpal + androidTestImplementation libs.support.annotations + androidTestImplementation libs.support.test.runner + androidTestImplementation libs.support.test.rules + androidTestImplementation libs.mockito + androidTestImplementation libs.jems.testing + androidTestImplementation libs.hamcrest + androidTestImplementation libs.contentpal.testing } diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java index fef43ca94cbb5d82a6dc23dfba3223ed7cd1ac72..f81928c07dd2be9315e49ec4b2ca0e97eaf9dd7e 100644 --- a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java @@ -51,6 +51,9 @@ public class ZippedTest public void testAbsent() { Object dummyObject = new Object(); - assertThat(new Zipped<>(absent(), new ValueSingle<>(dummyObject), dummy(BiFunction.class)), hasValue(sameInstance(dummyObject))); + assertThat( + new Zipped(absent(), new ValueSingle<>(dummyObject), dummy(BiFunction.class)), + hasValue(sameInstance(dummyObject)) + ); } } \ No newline at end of file diff --git a/opentasks-theme/build.gradle b/opentasks-theme/build.gradle index 4021aacf989c2d6a792921c5bd9655edb25a7c27..d675aad7c72667cb0ee33d602ac4bbda0ae8a9d5 100644 --- a/opentasks-theme/build.gradle +++ b/opentasks-theme/build.gradle @@ -15,16 +15,17 @@ */ plugins { - id 'com.android.library' + alias(libs.plugins.android.library) } android { - compileSdkVersion COMPILE_SDK_VERSION.toInteger() - buildToolsVersion "29.0.3" + namespace = "org.dmfs.android.sync.opentasks_theme" + compileSdkVersion 36 + buildToolsVersion "35.0.0" defaultConfig { - minSdkVersion MIN_SDK_VERSION - targetSdkVersion TARGET_SDK_VERSION + minSdkVersion 21 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -37,14 +38,13 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } } dependencies { - - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.2.1' - implementation 'foundation.e:elib:0.0.1-alpha11' -} \ No newline at end of file + implementation libs.support.appcompat + implementation libs.material + implementation libs.elib +} diff --git a/opentasks/build.gradle b/opentasks/build.gradle index 220107000e82ed025b68e48d815c2c2d77812b02..147270aaf12a9f6ae3fc8f73057d29133cddcd73 100644 --- a/opentasks/build.gradle +++ b/opentasks/build.gradle @@ -1,56 +1,49 @@ -apply plugin: 'com.android.application' -if (project.hasProperty('PLAY_STORE_SERVICE_ACCOUNT_CREDENTIALS')) { - apply plugin: 'com.github.triplet.play' -} -// commit number is only relevant to the application project -def gitCommitNo = { ref -> - def stdout = new ByteArrayOutputStream() - try { - exec { - commandLine 'git', 'rev-list', '--count', ref - standardOutput = stdout - } - - return Integer.parseInt(stdout.toString().trim()) - } - catch (Exception e) { - return 0 - } +plugins { + alias(libs.plugins.android.application) } android { - compileSdkVersion COMPILE_SDK_VERSION.toInteger() + namespace = "org.dmfs.tasks" + compileSdkVersion 36 defaultConfig { applicationId "foundation.e.tasks" - minSdkVersion MIN_SDK_VERSION.toInteger() - targetSdkVersion TARGET_SDK_VERSION.toInteger() - // spread version code to allow inserting versions if necessary - versionCode gitCommitNo('refs/remotes/origin/master') * 99 + gitCommitNo('HEAD') + Integer.parseInt(VERSION_OVERRIDE) + minSdkVersion 21 + targetSdkVersion 36 + versionCode 7000 versionName '1.4.2' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } - if (project.hasProperty("DMFS_RELEASE_KEYSTORE")) { - signingConfigs { - release { - storeFile file(DMFS_RELEASE_KEYSTORE) - storePassword DMFS_RELEASE_KEYSTORE_PASSWORD - keyAlias DMFS_RELEASE_KEY_ALIAS - keyPassword DMFS_RELEASE_KEY_PASSWORD - } + + signingConfigs { + platformConfig { + storeFile file("$rootDir/keystore/platform.jks") + storePassword 'platform' + keyAlias 'platform' + keyPassword 'platform' } } + buildTypes { + debug { + minifyEnabled false + signingConfig signingConfigs.platformConfig + } + release { - if (project.hasProperty("DMFS_RELEASE_KEYSTORE")) { - signingConfig signingConfigs.release - } + signingConfig signingConfigs.platformConfig minifyEnabled true - proguardFiles 'proguard.cfg' + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard.cfg" + ) } } + buildFeatures { + buildConfig = true + } packagingOptions { exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' @@ -63,87 +56,46 @@ android { disable 'MissingTranslation' // TODO } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } } dependencies { implementation project(':opentasks-theme') implementation project(':opentasks-provider') - implementation deps.support_appcompat - implementation deps.support_design - implementation(deps.xml_magic) { + implementation libs.support.appcompat + implementation libs.material + implementation(libs.xml.magic) { // xmlpull is part of the runtime, so don't pull it in here exclude group: 'xmlpull', module: 'xmlpull' } - implementation deps.android_dashclock - implementation deps.color_picker - implementation(deps.codeka_carrot) { + implementation libs.android.dashclock + implementation libs.color.picker + implementation(libs.codeka.carrot) { exclude module: 'iterators' // TODO Remove when iterators have been removed from codeka:carrot } - implementation(deps.android_carrot) { + implementation(libs.android.carrot) { exclude module: 'carrot' exclude module: 'iterators' exclude module: 'jems' } - implementation deps.jems - implementation deps.datetime - implementation deps.bolts_color - implementation deps.retention_magic + implementation libs.jems + implementation libs.datetime + implementation libs.bolts.color + implementation libs.retention.magic - testImplementation deps.junit - testImplementation deps.robolectric - testImplementation deps.jems_testing + testImplementation libs.junit + testImplementation libs.robolectric + testImplementation libs.jems.testing - androidTestImplementation deps.support_test_runner - androidTestImplementation deps.support_test_rules + androidTestImplementation libs.support.test.runner + androidTestImplementation libs.support.test.rules implementation project(path: ':opentaskspal') - implementation 'foundation.e:elib:0.0.1-alpha11' - - implementation 'io.reactivex.rxjava2:rxjava:2.2.21' - implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - implementation 'androidx.preference:preference:1.1.1' - implementation 'com.maltaisn:recurpicker:2.1.4' -} + implementation libs.elib -if (project.hasProperty('PLAY_STORE_SERVICE_ACCOUNT_CREDENTIALS')) { - play { - serviceAccountCredentials = file(PLAY_STORE_SERVICE_ACCOUNT_CREDENTIALS) - // the track is determined automatically by the version number format - switch (version) { - case ~/^(\d+)(\.\d+)*(-\d+-[\w\d]+)?-dirty$/: - // work in progress goes to the internal track - track = "internal" - break - case ~/^(\d+)(\.\d+)*-\d+-[\w\d]+$/: - // untagged commits go to alpha - track = "alpha" - break - case ~/^(\d+)(\.\d+)*$/: - // tagged commits to go beta, from where they get promoted to releases - track = "beta" - break - default: - throw new IllegalArgumentException("Unrecognized version format") - } - } -} - -task postVersion { - doLast { - if (project.hasProperty('OPENTASKS_API_KEY')) { - // publish version number on api.opentasks.app - ((HttpURLConnection) new URL('https://opentasks-app.appspot.com/v1/app/latest_version/').openConnection()).with({ - requestMethod = 'POST' - doOutput = true - setRequestProperty('Content-Type', 'application/x-www-form-urlencoded') - setRequestProperty('Authorization', "Token token=\"${OPENTASKS_API_KEY}\"") - outputStream.withPrintWriter({ printWriter -> - printWriter.write("version_code=${project.android.defaultConfig.versionCode}&version_name=${project.android.defaultConfig.versionName}") - }) - content - }) - } - } + implementation libs.rxjava + implementation libs.rxandroid + implementation libs.preference + implementation libs.recurpicker } diff --git a/opentasks/proguard.cfg b/opentasks/proguard.cfg index bd574eba9f7e2dec285839ebc9dc2b1080fcefb3..a30497d1fd9a6d6772a32de2008f488e478ebe17 100644 --- a/opentasks/proguard.cfg +++ b/opentasks/proguard.cfg @@ -15,15 +15,15 @@ -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { native ; } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } diff --git a/opentasks/src/main/AndroidManifest.xml b/opentasks/src/main/AndroidManifest.xml index c2018b27db07e86ac68258c752d3146593989b00..9a037032b257c12bfda87f3907686be6bca92caa 100644 --- a/opentasks/src/main/AndroidManifest.xml +++ b/opentasks/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -16,6 +15,7 @@ + @@ -41,6 +42,7 @@ @@ -64,6 +66,7 @@ @@ -144,6 +147,7 @@ @@ -164,6 +169,7 @@ @@ -177,6 +183,7 @@ @@ -206,6 +213,7 @@ @@ -239,6 +247,7 @@ @@ -269,6 +278,7 @@ @@ -297,6 +307,7 @@ @@ -353,6 +364,7 @@ diff --git a/opentasks/src/main/java/org/dmfs/tasks/AboutFragment.java b/opentasks/src/main/java/org/dmfs/tasks/AboutFragment.java index f49a89dbded1a49843b6318b36f76d454817c097..d9cc3e84be394cff10d31c142a1caac8ccbd6451 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/AboutFragment.java +++ b/opentasks/src/main/java/org/dmfs/tasks/AboutFragment.java @@ -1,9 +1,10 @@ package org.dmfs.tasks; -import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.preference.PreferenceFragment; +import org.dmfs.tasks.BuildConfig; + import androidx.annotation.Nullable; diff --git a/opentasks/src/main/java/org/dmfs/tasks/AppSettingsActivity.java b/opentasks/src/main/java/org/dmfs/tasks/AppSettingsActivity.java index 182eaa995e3db2750e494bba53c79034bba68d8e..cbf1b49f73e5f1211bc65b4a1b350d1637a85edf 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/AppSettingsActivity.java +++ b/opentasks/src/main/java/org/dmfs/tasks/AppSettingsActivity.java @@ -100,7 +100,7 @@ public final class AppSettingsActivity extends BaseActivity implements Preferenc Resources.Theme theme = super.getTheme(); if (Build.VERSION.SDK_INT < 29) { - theme.applyStyle(R.style.OpenTasks_Theme_Default, true); + theme.applyStyle(org.dmfs.android.sync.opentasks_theme.R.style.OpenTasks_Theme_Default, true); } return theme; } diff --git a/opentasks/src/main/java/org/dmfs/tasks/InputTextDialogFragment.java b/opentasks/src/main/java/org/dmfs/tasks/InputTextDialogFragment.java index 82c8959a2f6c96b72600c07881252befb522d820..0201a1749551797cc92921db687084ee17981de3 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/InputTextDialogFragment.java +++ b/opentasks/src/main/java/org/dmfs/tasks/InputTextDialogFragment.java @@ -147,8 +147,7 @@ public class InputTextDialogFragment extends SupportDialogFragment implements On @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - final Context contextThemeWrapperLight = new ContextThemeWrapper(getActivity(), R.style.ThemeOverlay_AppCompat_Light); + final Context contextThemeWrapperLight = new ContextThemeWrapper(getActivity(), androidx.appcompat.R.style.ThemeOverlay_AppCompat_Light); LayoutInflater localInflater = inflater.cloneInContext(contextThemeWrapperLight); View view = localInflater.inflate(R.layout.fragment_input_text_dialog, container); diff --git a/opentasks/src/main/java/org/dmfs/tasks/QuickAddDialogFragment.java b/opentasks/src/main/java/org/dmfs/tasks/QuickAddDialogFragment.java index b31672119d7edce81a99b0024aaaf879b22c560f..2e751942ce616e7b687c98827bfe903f5f7056f3 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/QuickAddDialogFragment.java +++ b/opentasks/src/main/java/org/dmfs/tasks/QuickAddDialogFragment.java @@ -204,7 +204,7 @@ public class QuickAddDialogFragment extends SupportDialogFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final Context contextThemeWrapperDark = new ContextThemeWrapper(getActivity(), R.style.Base_Theme_AppCompat); + final Context contextThemeWrapperDark = new ContextThemeWrapper(getActivity(), androidx.appcompat.R.style.Base_Theme_AppCompat); View view = inflater.inflate(R.layout.fragment_quick_add_dialog, container); diff --git a/opentasks/src/main/java/org/dmfs/tasks/StaleListBroadcastReceiver.java b/opentasks/src/main/java/org/dmfs/tasks/StaleListBroadcastReceiver.java index 3134a4e17f29e926aac638a2df7e239b6cf60ddb..5b728fec98f57974ee2b89dd14e9ac8d08b2742f 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/StaleListBroadcastReceiver.java +++ b/opentasks/src/main/java/org/dmfs/tasks/StaleListBroadcastReceiver.java @@ -105,9 +105,9 @@ public final class StaleListBroadcastReceiver extends BroadcastReceiver .setContentIntent(PendingIntent.getActivity(context, 0, accountRequestIntent, PendingIntent.FLAG_UPDATE_CURRENT)) .addAction(new Notification.Action.Builder(null, "Grant", PendingIntent.getActivity(context, 0, accountRequestIntent, PendingIntent.FLAG_UPDATE_CURRENT)).build()) - .setColor(new AttributeColor(theme, R.attr.colorPrimary).argb()) + .setColor(new AttributeColor(theme, androidx.appcompat.R.attr.colorPrimary).argb()) .setColorized(true) - .setSmallIcon(R.drawable.ic_24_opentasks) + .setSmallIcon(org.dmfs.tasks.provider.R.drawable.ic_24_opentasks) .build()); } } diff --git a/opentasks/src/main/java/org/dmfs/tasks/TaskListActivity.java b/opentasks/src/main/java/org/dmfs/tasks/TaskListActivity.java index 28081814c1ba0d4940ff9d94923163d484bdf936..f28e0b0c18b518e8a07143e1e269556292fb734e 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/TaskListActivity.java +++ b/opentasks/src/main/java/org/dmfs/tasks/TaskListActivity.java @@ -520,27 +520,23 @@ public class TaskListActivity extends BaseActivity implements TaskListFragment.C private void updateTitle(int pageId) { - switch (pageId) - { - case R.id.task_group_by_list: - getSupportActionBar().setTitle(R.string.task_group_title_list); - break; - case R.id.task_group_by_start: - getSupportActionBar().setTitle(R.string.task_group_title_start); - break; - case R.id.task_group_by_due: - getSupportActionBar().setTitle(R.string.task_group_title_due); - break; - case R.id.task_group_by_priority: - getSupportActionBar().setTitle(R.string.task_group_title_priority); - break; - case R.id.task_group_by_progress: - getSupportActionBar().setTitle(R.string.task_group_title_progress); - break; - - default: - getSupportActionBar().setTitle(R.string.task_group_title_default); - break; + if (pageId == R.id.task_group_by_list) { + getSupportActionBar().setTitle(R.string.task_group_title_list); + } + else if (pageId == R.id.task_group_by_start) { + getSupportActionBar().setTitle(R.string.task_group_title_start); + } + else if (pageId == R.id.task_group_by_due) { + getSupportActionBar().setTitle(R.string.task_group_title_due); + } + else if (pageId == R.id.task_group_by_priority) { + getSupportActionBar().setTitle(R.string.task_group_title_priority); + } + else if (pageId == R.id.task_group_by_progress) { + getSupportActionBar().setTitle(R.string.task_group_title_progress); + } + else { + getSupportActionBar().setTitle(R.string.task_group_title_default); } } @@ -736,14 +732,14 @@ public class TaskListActivity extends BaseActivity implements TaskListFragment.C } }); SearchView searchView = (SearchView) mSearchItem.getActionView(); - EditText searchText = searchView.findViewById(R.id.search_src_text); - ImageView searchClose = searchView.findViewById(R.id.search_close_btn); + EditText searchText = searchView.findViewById(androidx.appcompat.R.id.search_src_text); + ImageView searchClose = searchView.findViewById(androidx.appcompat.R.id.search_close_btn); searchClose.setImageResource(R.drawable.ic_close); - ImageView searchIcon = searchView.findViewById(R.id.search_mag_icon); + ImageView searchIcon = searchView.findViewById(androidx.appcompat.R.id.search_mag_icon); searchIcon.setImageDrawable(null); ImageViewCompat.setImageTintList(searchIcon, ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_default_primary_text))); - searchView.findViewById(R.id.search_plate).setBackground(null); + searchView.findViewById(androidx.appcompat.R.id.search_plate).setBackground(null); searchText.setHintTextColor(getResources().getColor(R.color.dark_gray)); searchText.setTextColor(getResources().getColor(R.color.color_default_primary_text)); @@ -839,7 +835,7 @@ public class TaskListActivity extends BaseActivity implements TaskListFragment.C if (Build.VERSION.SDK_INT < 29) { theme.applyStyle( - R.style.OpenTasks_Theme_Default, + org.dmfs.android.sync.opentasks_theme.R.style.OpenTasks_Theme_Default, true); } return theme; diff --git a/opentasks/src/main/java/org/dmfs/tasks/actions/NotifyAction.java b/opentasks/src/main/java/org/dmfs/tasks/actions/NotifyAction.java index 072f075026d90aaf1072ed0f189e814681fd8cb6..e564837099195d4cff49766ceb79ed6cee74852c 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/actions/NotifyAction.java +++ b/opentasks/src/main/java/org/dmfs/tasks/actions/NotifyAction.java @@ -180,7 +180,9 @@ public final class NotifyAction implements TaskAction builder.setDefaults(new Conditional(mRepost, context).value()); } // TODO: for now we only use the primary app color, later we allow the user to select how to color notifications: default, list, priority - builder.setColor(new AttributeColor(new ContextThemeWrapper(context, R.style.OpenTasks_Theme_Default), R.attr.colorPrimary).argb()); + builder.setColor(new AttributeColor(new ContextThemeWrapper(context, + org.dmfs.android.sync.opentasks_theme.R.style.OpenTasks_Theme_Default), + androidx.appcompat.R.attr.colorPrimary).argb()); //builder.setColor(new EffectiveTaskColor(data).argb()); NotificationManagerCompat.from(context).notify("tasks", notificationId, builder.build()); } diff --git a/opentasks/src/main/java/org/dmfs/tasks/actions/PostUndoAction.java b/opentasks/src/main/java/org/dmfs/tasks/actions/PostUndoAction.java index e705a278102c280587a4a9b2e77e5bc1d8c7e37b..4c6d6a9fac3cba7c3e82bee7525823d5882d7aab 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/actions/PostUndoAction.java +++ b/opentasks/src/main/java/org/dmfs/tasks/actions/PostUndoAction.java @@ -70,7 +70,7 @@ public final class PostUndoAction implements TaskAction context, id, new Intent(context, ActionReceiver.class).setData(taskUri).setAction(ActionService.ACTION_UNDO_COMPLETE), - PendingIntent.FLAG_CANCEL_CURRENT)); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)); builder.setContent(undoView); // When the notification is cleared, we perform the destructive action @@ -78,10 +78,12 @@ public final class PostUndoAction implements TaskAction context, id, new Intent(context, ActionReceiver.class).setData(taskUri).setAction(ActionService.ACTION_FINISH_COMPLETE), - PendingIntent.FLAG_CANCEL_CURRENT)); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)); builder.setShowWhen(false); builder.setGroup(GROUP_UNDO); - builder.setColor(new AttributeColor(new ContextThemeWrapper(context, R.style.OpenTasks_Theme_Default), R.attr.colorPrimary).argb()); + builder.setColor(new AttributeColor(new ContextThemeWrapper(context, + org.dmfs.android.sync.opentasks_theme.R.style.OpenTasks_Theme_Default), + androidx.appcompat.R.attr.colorPrimary).argb()); NotificationManagerCompat.from(context).notify("tasks.undo", id, builder.build()); } diff --git a/opentasks/src/main/java/org/dmfs/tasks/groupings/BaseTaskViewDescriptor.java b/opentasks/src/main/java/org/dmfs/tasks/groupings/BaseTaskViewDescriptor.java index 3f7046dafa7a0a8fe3eba91ef639b7ff7ff31184..5f4a8cc745b9a7409a5face045c35a0fee78362b 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/groupings/BaseTaskViewDescriptor.java +++ b/opentasks/src/main/java/org/dmfs/tasks/groupings/BaseTaskViewDescriptor.java @@ -254,15 +254,18 @@ public abstract class BaseTaskViewDescriptor implements ViewDescriptor { if (priority < 5) { - prioLabel.setBackgroundColor(new AttributeColor(prioLabel.getContext(), R.attr.colorHighPriority).argb()); + prioLabel.setBackgroundColor(new AttributeColor(prioLabel.getContext(), + org.dmfs.android.sync.opentasks_theme.R.attr.colorHighPriority).argb()); } if (priority == 5) { - prioLabel.setBackgroundColor(new AttributeColor(prioLabel.getContext(), R.attr.colorMediumPriority).argb()); + prioLabel.setBackgroundColor(new AttributeColor(prioLabel.getContext(), + org.dmfs.android.sync.opentasks_theme.R.attr.colorMediumPriority).argb()); } if (priority > 5) { - prioLabel.setBackgroundColor(new AttributeColor(prioLabel.getContext(), R.attr.colorLowPriority).argb()); + prioLabel.setBackgroundColor(new AttributeColor(prioLabel.getContext(), + org.dmfs.android.sync.opentasks_theme.R.attr.colorLowPriority).argb()); } prioLabel.setVisibility(View.VISIBLE); } @@ -284,8 +287,8 @@ public abstract class BaseTaskViewDescriptor implements ViewDescriptor cardView.findViewById(R.id.color_label).setAlpha(isClosed ? 0.4f : 1f); cardView.setCardElevation(view.getResources().getDimensionPixelSize( isClosed ? - R.dimen.opentasks_tasklist_card_elevation_closed : - R.dimen.opentasks_tasklist_card_elevation)); + org.dmfs.android.sync.opentasks_theme.R.dimen.opentasks_tasklist_card_elevation_closed : + org.dmfs.android.sync.opentasks_theme.R.dimen.opentasks_tasklist_card_elevation)); ((TextView) cardView.findViewById(android.R.id.title)) .setTextColor(new AttributeColor(view.getContext(), isClosed ? diff --git a/opentasks/src/main/java/org/dmfs/tasks/notification/TaskNotificationService.java b/opentasks/src/main/java/org/dmfs/tasks/notification/TaskNotificationService.java index a00316ba0b8546377dc74374f4f03b67cd2488c0..5e7d89acb467b3ad0bd17bea1cb3f80cda70674d 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/notification/TaskNotificationService.java +++ b/opentasks/src/main/java/org/dmfs/tasks/notification/TaskNotificationService.java @@ -104,7 +104,7 @@ public class TaskNotificationService extends JobIntentService * Notifications of tasks which have been unpinned are removed. * Notifications of tasks which have changed otherwise are updated. */ - String authority = getString(R.string.opentasks_authority); + String authority = getString(org.dmfs.tasks.provider.R.string.opentasks_authority); Iterable currentNotifications = new org.dmfs.tasks.utils.Sorted<>( (o, o2) -> (int) (ContentUris.parseId(o.instance()) - ContentUris.parseId(o2.instance())), diff --git a/opentasks/src/main/java/org/dmfs/tasks/utils/BaseActivity.java b/opentasks/src/main/java/org/dmfs/tasks/utils/BaseActivity.java index ecabdbc78d48f9dd45dbb39a977bc2e14502859d..ab6f00ed328af1aba9f4827db1e2aa113b6d6f10 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/utils/BaseActivity.java +++ b/opentasks/src/main/java/org/dmfs/tasks/utils/BaseActivity.java @@ -18,15 +18,22 @@ package org.dmfs.tasks.utils; import android.Manifest; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; + import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import org.dmfs.android.retentionmagic.RetentionMagic; import org.dmfs.tasks.utils.permission.BasicAppPermissions; import org.dmfs.tasks.utils.permission.Permission; import org.dmfs.tasks.utils.permission.dialog.PermissionRequestDialogFragment; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * Base class for all Activities in the app. @@ -35,6 +42,8 @@ import org.dmfs.tasks.utils.permission.dialog.PermissionRequestDialogFragment; */ public abstract class BaseActivity extends AppCompatActivity { + private static final int REQUEST_PERMISSIONS = 2001; + private SharedPreferences mPrefs; private Permission mGetAccountsPermission; @@ -67,6 +76,7 @@ public abstract class BaseActivity extends AppCompatActivity { super.onResume(); requestMissingGetAccountsPermission(); + requestMissingPermissions(); } @@ -95,4 +105,25 @@ public abstract class BaseActivity extends AppCompatActivity } } + private void requestMissingPermissions() { + List permissions = new ArrayList<>(); + List allPermissions = Arrays.asList(Manifest.permission.READ_CONTACTS, + Manifest.permission.POST_NOTIFICATIONS, "foundation.e.permission.READ_TASKS", + "foundation.e.permission.WRITE_TASKS"); + + for (String permission : allPermissions) { + if (permission.equals(Manifest.permission.POST_NOTIFICATIONS) + && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + continue; + } + + if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { + permissions.add(permission); + } + } + + if (!permissions.isEmpty()) { + ActivityCompat.requestPermissions(this, permissions.toArray(new String[0]), REQUEST_PERMISSIONS); + } + } } diff --git a/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java b/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java index eca15c9d5844693da316fae14ecb2fe58c498897..9c7852cd83448a6abb37544fadf141fe4bab3d34 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java +++ b/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java @@ -271,7 +271,7 @@ public class DescriptionFieldView extends AbstractFieldView implements OnChecked ColorStateList colorStateList = new ColorStateList( new int[][] { new int[] { android.R.attr.state_focused }, new int[] { -android.R.attr.state_focused } }, - new int[] { new AttributeColor(getContext(), R.attr.colorPrimary).argb(), 0 }); + new int[] { new AttributeColor(getContext(), androidx.appcompat.R.attr.colorPrimary).argb(), 0 }); ViewCompat.setBackgroundTintList(text, colorStateList); text.setOnFocusChangeListener((v, hasFocus) -> { diff --git a/opentasks/src/main/res/layout/activity_settings.xml b/opentasks/src/main/res/layout/activity_settings.xml index ac7ce265596435848116c3a655686e6847c88ea1..5a41e1ca5c99d4c26d494071088093f8bc752ba6 100644 --- a/opentasks/src/main/res/layout/activity_settings.xml +++ b/opentasks/src/main/res/layout/activity_settings.xml @@ -2,6 +2,7 @@ android:layout_width="match_parent" android:orientation="vertical" android:background="@color/color_default_background" + android:fitsSystemWindows="true" android:layout_height="match_parent"> true true + + + true \ No newline at end of file diff --git a/opentasks/src/main/res/values/styles.xml b/opentasks/src/main/res/values/styles.xml index b081a23bb04797595061473bda2f7772215d2601..e58f14e0b1104c1cd8e150b686c8cab8c8f9b8e6 100644 --- a/opentasks/src/main/res/values/styles.xml +++ b/opentasks/src/main/res/values/styles.xml @@ -183,6 +183,9 @@ @color/e_secondary_text_color @color/accent @color/accent + + + true diff --git a/opentaskspal/build.gradle b/opentaskspal/build.gradle index 2996725761a131b318212f6cd4a5072337436c73..c9261579e585b44b35d9c5140fe3e9f32bf69c06 100644 --- a/opentaskspal/build.gradle +++ b/opentaskspal/build.gradle @@ -1,35 +1,38 @@ -apply plugin: 'com.android.library' +plugins { + alias(libs.plugins.android.library) +} android { - compileSdkVersion COMPILE_SDK_VERSION.toInteger() + namespace = "org.dmfs.opentaskspal" + compileSdkVersion 36 defaultConfig { - minSdkVersion MIN_SDK_VERSION.toInteger() - targetSdkVersion TARGET_SDK_VERSION.toInteger() + minSdkVersion 21 + targetSdkVersion 36 } packagingOptions { exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } } dependencies { api project(':opentasks-contract') - api deps.contentpal - api deps.datetime - api deps.lib_recur - api deps.support_annotations - api deps.bolts_color + api libs.contentpal + api libs.datetime + api libs.lib.recur + api libs.support.annotations + api libs.bolts.color - implementation deps.jems + implementation libs.jems - testImplementation deps.contentpal_testing - testImplementation deps.jems_testing - testImplementation deps.robolectric - testImplementation deps.junit - testImplementation deps.mockito -} \ No newline at end of file + testImplementation libs.contentpal.testing + testImplementation libs.jems.testing + testImplementation libs.robolectric + testImplementation libs.junit + testImplementation libs.mockito +} diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java index f266b6f23c37ee063292d9686dee8c41240a095c..a6982422f399843edf2df4c032d2cb38082dbeb7 100644 --- a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java +++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/EffectiveDueDateTest.java @@ -31,8 +31,8 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static org.dmfs.jems.mockito.doubles.TestDoubles.failingMock; import static org.dmfs.jems.optional.elementary.Absent.absent; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; diff --git a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java index bf67d42bc2f584070cca2cb902d9701934c4a6eb..2e240aeb72fcb452ef8ff1e2d5dda083a036c284 100644 --- a/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java +++ b/opentaskspal/src/test/java/org/dmfs/opentaskspal/readdata/TaskDateTimeTest.java @@ -30,8 +30,8 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.dmfs.jems.mockito.doubles.TestDoubles.failingMock; import static org.dmfs.optional.Absent.absent; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; diff --git a/settings.gradle b/settings.gradle index f5592faa59cdf033fed6f05c8d0eb088e7735291..207dd2027a6ca84607cf0adc718f98cb97a967ba 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,20 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url "https://jitpack.io" } + maven { url "https://gitlab.e.foundation/api/v4/groups/9/-/packages/maven" } + } +} + include ':opentasks-theme' include ':opentasks', ':opentasks-provider', ':opentasks-contract', ':opentaskspal'