diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml
index a17f5632f9ad..bc9cdb1b3b9e 100644
--- a/.github/workflows/analysis.yml
+++ b/.github/workflows/analysis.yml
@@ -33,7 +33,7 @@ jobs:
echo "pr=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
echo "repo=${{ github.event.pull_request.head.repo.full_name }}" >> "$GITHUB_OUTPUT"
fi
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: ${{ steps.get-vars.outputs.repo }}
ref: ${{ steps.get-vars.outputs.branch }}
diff --git a/.github/workflows/assembleFlavors.yml b/.github/workflows/assembleFlavors.yml
index 165c7f720a6f..a334359beae6 100644
--- a/.github/workflows/assembleFlavors.yml
+++ b/.github/workflows/assembleFlavors.yml
@@ -19,7 +19,7 @@ jobs:
matrix:
flavor: [ Generic, Gplay, Huawei ]
steps:
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 3151bfac84d8..ddb198952505 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -19,7 +19,7 @@ jobs:
matrix:
task: [ detekt, spotlessKotlinCheck ]
steps:
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 3ea950d1c13b..369b7acfc146 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -12,6 +12,10 @@ on:
permissions:
contents: read
+concurrency:
+ group: code-ql-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
jobs:
analyze:
name: Analyze
@@ -26,13 +30,13 @@ jobs:
language: [ 'java' ]
steps:
- name: Checkout repository
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set Swap Space
uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0
with:
swap-size-gb: 10
- name: Initialize CodeQL
- uses: github/codeql-action/init@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
+ uses: github/codeql-action/init@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
with:
languages: ${{ matrix.language }}
- name: Set up JDK 17
@@ -46,4 +50,4 @@ jobs:
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
./gradlew assembleDebug
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
+ uses: github/codeql-action/analyze@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
diff --git a/.github/workflows/command-rebase.yml b/.github/workflows/command-rebase.yml
index 8c215c1c4ca6..60876df35457 100644
--- a/.github/workflows/command-rebase.yml
+++ b/.github/workflows/command-rebase.yml
@@ -23,7 +23,7 @@ jobs:
steps:
- name: Add reaction on start
- uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
+ uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
@@ -31,7 +31,7 @@ jobs:
reaction-type: "+1"
- name: Checkout the latest code
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
token: ${{ secrets.COMMAND_BOT_PAT }}
@@ -42,7 +42,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
- name: Add reaction on failure
- uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
+ uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
if: failure()
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
diff --git a/.github/workflows/detectWrongSettings.yml b/.github/workflows/detectWrongSettings.yml
index 599e7d06b4c5..9d6dce59a20c 100644
--- a/.github/workflows/detectWrongSettings.yml
+++ b/.github/workflows/detectWrongSettings.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index 45a120cb4ed1..1ecd60deb21a 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -18,5 +18,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # v1.1.0
diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml
index 8f9090e6cf50..8a6f9e6cd7ad 100644
--- a/.github/workflows/qa.yml
+++ b/.github/workflows/qa.yml
@@ -19,7 +19,7 @@ jobs:
- name: Check if secrets are available
run: echo "::set-output name=ok::${{ secrets.KS_PASS != '' }}"
id: check-secrets
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
if: ${{ steps.check-secrets.outputs.ok == 'true' }}
- name: set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index f54c98fedb6d..bd873fbb8143 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -24,12 +24,12 @@ jobs:
steps:
- name: "Checkout code"
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
+ uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
@@ -37,6 +37,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
+ uses: github/codeql-action/upload-sarif@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
with:
sarif_file: results.sarif
diff --git a/.github/workflows/screenShotTest.yml b/.github/workflows/screenShotTest.yml
index 9b768312963c..e036d66e6ec5 100644
--- a/.github/workflows/screenShotTest.yml
+++ b/.github/workflows/screenShotTest.yml
@@ -22,7 +22,7 @@ jobs:
color: [ blue ]
api-level: [ 27 ]
steps:
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Gradle cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index f7b750515f89..12a5a6da2b64 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -18,7 +18,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebc537ffa5c9..5d6df215c09e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,30 @@
-## 3.24.1 (February 21, 2022)
+## 3.26.0 (September 16, 2023)
+
+- image editing
+- image details, with map
+- show other Nextcloud apps
+
+Minimum: NC 16 Server, Android 6.0 Marshmallow
+
+For a full list, please see https://github.com/nextcloud/android/milestone/84
+
+## 3.25.0 (June 13, 2023)
+
+- show Groupfolder
+- Tag in file listing
+
+Minimum: NC 16 Server, Android 6.0 Marshmallow
+
+For a full list, please see https://github.com/nextcloud/android/milestone/81
+
+## 3.24.1 (February 21, 2023)
- Fix crash in previous version when connecting to old server versions
Minimum: NC 16 Server, Android 6.0 Marshmallow
+For a full list, please see https://github.com/nextcloud/android/milestone/80
+
## 3.24.0 (February 13, 2023)
- Several performance optimizations by @starypatyk
diff --git a/README.md b/README.md
index 5bf764cecdf2..cdf43994ab1e 100644
--- a/README.md
+++ b/README.md
@@ -15,11 +15,17 @@ height="80">](https://f-droid.org/packages/com.nextcloud.client/)
## How to contribute :rocket:
-If you want to [contribute](https://nextcloud.com/contribute/) to Nextcloud, you are very welcome:
-
-* our forum at https://help.nextcloud.com
-* for translations of the app on [Transifex](https://app.transifex.com/nextcloud/nextcloud/android/)
-* opening issues and PRs (including a corresponding issue)
+If you want to [contribute](https://nextcloud.com/contribute/) to the Nextcloud Android client app, there are many ways to help whether or not you are a coder:
+
+* helping out other users on our forum at https://help.nextcloud.com
+* providing translations of the app on [Transifex](https://app.transifex.com/nextcloud/nextcloud/android/)
+* reporting problems / suggesting enhancements by [opening new issues](https://github.com/nextcloud/android/issues/new/choose)
+* implementing proposed bug fixes and enhancement ideas by submitting PRs (associated with a corresponding issue preferably)
+* reviewing [pull requests](https://github.com/nextcloud/android/pulls) and providing feedback on code, implementation, and functionality
+* installing and testing [pull request builds](https://github.com/nextcloud/android/pulls), [daily/dev builds](https://github.com/nextcloud/android#development-version-hammer), or [RCs/release candidate builds](https://github.com/nextcloud/android/releases)
+* enhancing Admin, User, or Developer [documentation](https://github.com/nextcloud/documentation/)
+* hitting hard on the latest stable release by testing fundamental features and evaluating the user experience
+* proactively getting familiar with [how to gather debug logs](https://github.com/nextcloud/android#getting-debug-info-via-logcat-mag) from your devices (so that you are prepared to provide a detailed report if you encounter a problem with the app in the future)
## Contribution Guidelines & License :scroll:
@@ -38,7 +44,7 @@ More information on how to contribute:
## Start contributing :hammer\_and\_wrench:
Make sure you read [SETUP.md](https://github.com/nextcloud/android/blob/master/SETUP.md) and [CONTRIBUTING.md](https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md) before you start working on this project. But basically: fork this repository and contribute back using pull requests to the master branch.
-Easy starting points are also reviewing [pull requests](https://github.com/nextcloud/android/pulls) and working on [starter issues](https://github.com/nextcloud/android/issues?q=is%3Aopen+is%3Aissue+label%3A%22starter+issue%22).
+Easy starting points are also reviewing [pull requests](https://github.com/nextcloud/android/pulls) and working on [starter issues](https://github.com/nextcloud/android/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
### Getting debug info via logcat :mag:
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepBoth.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepBoth.png
index 47172a3da27b..97e0219c3302 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepBoth.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepBoth.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepExisting.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepExisting.png
index 8ad1818f865e..57b78febd9c2 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepExisting.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepExisting.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepNew.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepNew.png
index e51fdd8144d0..bf4f56484ee1 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepNew.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepNew.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_screenshotTextFiles.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_screenshotTextFiles.png
index 336694046bc1..df72127e511a 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_screenshotTextFiles.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_screenshotTextFiles.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png
index 26c12663b702..f450422215b5 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testNewFolderDialog.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testNewFolderDialog.png
index 658eafbc80e7..d4dfd20689b3 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testNewFolderDialog.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testNewFolderDialog.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFileDialog.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFileDialog.png
index c4bd33b4abd2..e451319a42a4 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFileDialog.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFileDialog.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFilesDialog.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFilesDialog.png
index 6681374612c1..caa860dc2b97 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFilesDialog.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFilesDialog.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFolderDialog.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFolderDialog.png
index 1ef759d4f6dc..ff264659676c 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFolderDialog.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFolderDialog.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFoldersDialog.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFoldersDialog.png
index 6681374612c1..caa860dc2b97 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFoldersDialog.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFoldersDialog.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testStoragePermissionDialog.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testStoragePermissionDialog.png
index 065c11e16351..7aae97506053 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testStoragePermissionDialog.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testStoragePermissionDialog.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailActivitiesFragment.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailActivitiesFragment.png
index 01ad7143ffef..32a31a678acb 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailActivitiesFragment.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailActivitiesFragment.png differ
diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailDetailsFragment.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailDetailsFragment.png
index 01d6675377f2..e853f17b9131 100644
Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailDetailsFragment.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showFileDetailDetailsFragment.png differ
diff --git a/app/src/androidTest/java/com/owncloud/android/AbstractIT.java b/app/src/androidTest/java/com/owncloud/android/AbstractIT.java
index 9549951b3471..68bd9cc9d452 100644
--- a/app/src/androidTest/java/com/owncloud/android/AbstractIT.java
+++ b/app/src/androidTest/java/com/owncloud/android/AbstractIT.java
@@ -314,7 +314,7 @@ protected Activity getCurrentActivity() {
return currentActivity;
}
- protected void shortSleep() {
+ protected static void shortSleep() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
diff --git a/app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java b/app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java
index fd2e206e7f2c..94f54f69615e 100644
--- a/app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java
+++ b/app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java
@@ -135,10 +135,20 @@ public static void deleteAllFilesOnServer() {
.isSuccess());
}
- assertTrue(new RemoveFileRemoteOperation(remoteFile.getRemotePath())
- .execute(client)
- .isSuccess()
- );
+ boolean removeResult = false;
+ for (int i = 0; i < 5; i++) {
+ removeResult = new RemoveFileRemoteOperation(remoteFile.getRemotePath())
+ .execute(client)
+ .isSuccess();
+
+ if (removeResult) {
+ break;
+ }
+
+ shortSleep();
+ }
+
+ assertTrue(removeResult);
}
}
}
diff --git a/app/src/androidTest/java/com/owncloud/android/UploadIT.java b/app/src/androidTest/java/com/owncloud/android/UploadIT.java
index e93b328f0865..7b86de769169 100644
--- a/app/src/androidTest/java/com/owncloud/android/UploadIT.java
+++ b/app/src/androidTest/java/com/owncloud/android/UploadIT.java
@@ -41,7 +41,6 @@
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -106,29 +105,6 @@ public void before() throws IOException {
createDummyFiles();
}
- @After
- public void after() {
- RemoteOperationResult result = new RefreshFolderOperation(getStorageManager().getFileByPath("/"),
- System.currentTimeMillis() / 1000L,
- false,
- true,
- getStorageManager(),
- user,
- targetContext)
- .execute(client);
-
- // cleanup only if folder exists
- if (result.isSuccess() && getStorageManager().getFileByDecryptedRemotePath(FOLDER) != null) {
- new RemoveFileOperation(getStorageManager().getFileByDecryptedRemotePath(FOLDER),
- false,
- user,
- false,
- targetContext,
- getStorageManager())
- .execute(client);
- }
- }
-
@Test
public void testEmptyUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
@@ -529,8 +505,8 @@ public void testMetadata() throws IOException, AccountUtils.AccountNotFoundExcep
assertNotNull(ocFile);
assertEquals(remotePath, ocFile.getRemotePath());
- assertEquals(new ImageDimension(451f, 529f), ocFile.getImageDimension());
- assertEquals(new GeoLocation(49.99679166666667, 8.67198611111111), ocFile.getGeoLocation());
+ assertEquals(new ImageDimension(300f, 200f), ocFile.getImageDimension());
+ assertEquals(new GeoLocation(64, -46), ocFile.getGeoLocation());
}
private void verifyStoragePath(OCFile file) {
diff --git a/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt
index 67ba9cd40ff6..3bf0378c01bd 100644
--- a/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt
+++ b/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt
@@ -20,6 +20,7 @@
*/
package com.owncloud.android.ui
+import android.os.Build
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
@@ -65,7 +66,7 @@ class LoginIT : AbstractIT() {
* The CI/CD pipeline is encountering issues related to the Android version for this functionality.
* Therefore the test will only be executed on Android versions 10 and above.
*/
- @SdkSuppress(minSdkVersion = 29)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
fun login() {
val arguments = InstrumentationRegistry.getArguments()
val baseUrl = arguments.getString("TEST_SERVER_URL")!!
diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt
index 5f6883e1ab67..71213ff5928c 100644
--- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt
+++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt
@@ -73,14 +73,19 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
@Test
@ScreenshotTest
fun showFileDetailDetailsFragment() {
- val sut = testActivityRule.launchActivity(null)
- sut.addFragment(ImageDetailFragment.newInstance(oCFile, user))
+ val activity = testActivityRule.launchActivity(null)
+ val sut = ImageDetailFragment.newInstance(oCFile, user)
+ activity.addFragment(sut)
- waitForIdleSync()
- shortSleep()
shortSleep()
shortSleep()
- screenshot(sut)
+ waitForIdleSync()
+
+ activity.runOnUiThread {
+ sut.hideMap()
+ }
+
+ screenshot(activity)
}
@Test
@@ -182,6 +187,7 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
waitForIdleSync()
activity.runOnUiThread {
+ sut.fileDetailActivitiesFragment.disableLoadingActivities()
sut
.fileDetailActivitiesFragment
.setErrorContent(targetContext.resources.getString(R.string.file_detail_activity_error))
diff --git a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
index d776c042a9db..5039fb427441 100644
--- a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
+++ b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
@@ -92,7 +92,6 @@
import com.owncloud.android.ui.dialog.LoadingDialog;
import com.owncloud.android.ui.dialog.LocalStoragePathPickerDialogFragment;
import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
-import com.owncloud.android.ui.dialog.NoteDialogFragment;
import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
import com.owncloud.android.ui.dialog.RenamePublicShareDialogFragment;
@@ -404,9 +403,6 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract Migrations migrations();
- @ContributesAndroidInjector
- abstract NoteDialogFragment noteDialogFragment();
-
@ContributesAndroidInjector
abstract NotificationWork notificationWork();
diff --git a/app/src/main/java/com/nextcloud/client/jobs/FilesExportWork.kt b/app/src/main/java/com/nextcloud/client/jobs/FilesExportWork.kt
index 427b9f605d7b..d8a417513be0 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/FilesExportWork.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/FilesExportWork.kt
@@ -29,7 +29,6 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
-import android.graphics.BitmapFactory
import androidx.core.app.NotificationCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
@@ -69,9 +68,7 @@ class FilesExportWork(
val successfulExports = exportFiles(fileIDs)
- // show notification
showSuccessNotification(successfulExports)
-
return Result.success()
}
@@ -105,7 +102,13 @@ class FilesExportWork(
@Throws(IllegalStateException::class)
private fun exportFile(ocFile: OCFile) {
- FileExportUtils().exportFile(ocFile.fileName, ocFile.mimeType, contentResolver, ocFile, null)
+ FileExportUtils().exportFile(
+ ocFile.fileName,
+ ocFile.mimeType,
+ contentResolver,
+ ocFile,
+ null
+ )
}
private fun downloadFile(ocFile: OCFile) {
@@ -119,19 +122,16 @@ class FilesExportWork(
}
private fun showErrorNotification(successfulExports: Int) {
- if (successfulExports == 0) {
- showNotification(
- appContext.resources.getQuantityString(R.plurals.export_failed, successfulExports, successfulExports)
- )
+ val message = if (successfulExports == 0) {
+ appContext.resources.getQuantityString(R.plurals.export_failed, successfulExports, successfulExports)
} else {
- showNotification(
- appContext.resources.getQuantityString(
- R.plurals.export_partially_failed,
- successfulExports,
- successfulExports
- )
+ appContext.resources.getQuantityString(
+ R.plurals.export_partially_failed,
+ successfulExports,
+ successfulExports
)
}
+ showNotification(message)
}
private fun showSuccessNotification(successfulExports: Int) {
@@ -152,9 +152,7 @@ class FilesExportWork(
NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD
)
.setSmallIcon(R.drawable.notification_icon)
- .setLargeIcon(BitmapFactory.decodeResource(appContext.resources, R.drawable.notification_icon))
- .setSubText(user.accountName)
- .setContentText(message)
+ .setContentTitle(message)
.setAutoCancel(true)
viewThemeUtils.androidx.themeNotificationCompatBuilder(appContext, notificationBuilder)
@@ -166,7 +164,8 @@ class FilesExportWork(
appContext,
notificationId,
actionIntent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_CANCEL_CURRENT or
+ PendingIntent.FLAG_IMMUTABLE
)
notificationBuilder.addAction(
NotificationCompat.Action(
@@ -176,7 +175,8 @@ class FilesExportWork(
)
)
- val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ val notificationManager = appContext
+ .getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationId, notificationBuilder.build())
}
diff --git a/app/src/main/java/com/nextcloud/ui/ImageDetailFragment.kt b/app/src/main/java/com/nextcloud/ui/ImageDetailFragment.kt
index e858c6fa97d9..590e9e464fa7 100644
--- a/app/src/main/java/com/nextcloud/ui/ImageDetailFragment.kt
+++ b/app/src/main/java/com/nextcloud/ui/ImageDetailFragment.kt
@@ -30,6 +30,7 @@ import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.nextcloud.android.common.ui.theme.utils.ColorRole
@@ -260,6 +261,11 @@ class ImageDetailFragment : Fragment(), Injectable {
binding.imageLocationMapCopyright.text = binding.imageLocationMap.tileProvider.tileSource.copyrightNotice
}
+ @VisibleForTesting
+ fun hideMap() {
+ binding.imageLocationMap.visibility = View.GONE
+ }
+
@SuppressLint("SimpleDateFormat")
private fun gatherMetadata() {
val fileSize = DisplayUtils.bytesToHumanReadable(file.fileLength)
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.java b/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.java
index f2863b19755e..a3a7e1391281 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.java
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.java
@@ -25,6 +25,7 @@
import android.app.Dialog;
import android.os.Bundle;
+import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
@@ -59,7 +60,11 @@ public static AccountRemovalConfirmationDialog newInstance(User user) {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- user = getArguments().getParcelable(KEY_USER);
+
+ Bundle arguments = getArguments();
+ if (arguments != null) {
+ user = arguments.getParcelable(KEY_USER);
+ }
}
@Override
@@ -67,9 +72,18 @@ public void onStart() {
super.onStart();
AlertDialog alertDialog = (AlertDialog) getDialog();
-
- viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
+ if (alertDialog != null) {
+
+ MaterialButton positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveButton != null) {
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
+ }
+
+ MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ if (negativeButton != null) {
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
+ }
+ }
}
@NonNull
@@ -82,7 +96,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
.setPositiveButton(R.string.common_ok,
(dialogInterface, i) -> backgroundJobManager.startAccountRemovalJob(user.getAccountName(),
false))
- .setNeutralButton(R.string.common_cancel, null);
+ .setNegativeButton(R.string.common_cancel, null);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireActivity(), builder);
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java b/app/src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java
index b95a357e0fad..b62576fd822c 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java
@@ -35,6 +35,7 @@
import android.view.View;
import android.widget.Button;
+import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.collect.Sets;
import com.nextcloud.client.account.CurrentAccountProvider;
@@ -99,7 +100,7 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
private RichDocumentsTemplateAdapter adapter;
private OCFile parentFolder;
private OwnCloudClient client;
- private Button positiveButton;
+ private MaterialButton positiveButton;
private DialogFragment waitDialog;
public enum Type {
@@ -126,11 +127,18 @@ public void onStart() {
AlertDialog alertDialog = (AlertDialog) getDialog();
- positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
- viewThemeUtils.platform.colorTextButtons(positiveButton,
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
- positiveButton.setOnClickListener(this);
- positiveButton.setEnabled(false);
+ if (alertDialog != null) {
+ positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
+
+ MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ if (negativeButton != null) {
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
+ }
+
+ positiveButton.setOnClickListener(this);
+ positiveButton.setEnabled(false);
+ }
checkEnablingCreateButton();
}
@@ -205,12 +213,14 @@ public void afterTextChanged(Editable s) {
}
});
+ int titleTextId = getTitle(type);
+
// Build the dialog
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
builder.setView(view)
.setPositiveButton(R.string.create, null)
- .setNeutralButton(R.string.common_cancel, null)
- .setTitle(getTitle(type));
+ .setNegativeButton(R.string.common_cancel, null)
+ .setTitle(titleTextId);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(activity, builder);
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.kt
index a0e9aa514f28..b56513207ae2 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.kt
@@ -32,10 +32,10 @@ import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
-import android.widget.Button
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.GridLayoutManager
+import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.android.lib.resources.directediting.DirectEditingCreateFileRemoteOperation
import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainListOfTemplatesRemoteOperation
@@ -90,7 +90,7 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
private var adapter: TemplateAdapter? = null
private var parentFolder: OCFile? = null
private var title: String? = null
- private var positiveButton: Button? = null
+ private var positiveButton: MaterialButton? = null
private var creator: Creator? = null
enum class Type {
@@ -103,17 +103,18 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
override fun onStart() {
super.onStart()
val alertDialog = dialog as AlertDialog
- val button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
- viewThemeUtils.platform.colorTextButtons(
- button,
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
- )
- button.setOnClickListener(this)
- button.isEnabled = false
- button.isClickable = false
+ val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton)
+
+ val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton)
- positiveButton = button
+ positiveButton.setOnClickListener(this)
+ positiveButton.isEnabled = false
+ positiveButton.isClickable = false
+
+ this.positiveButton = positiveButton
checkEnablingCreateButton()
}
@@ -128,6 +129,7 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER)
creator = arguments.getParcelable(ARG_CREATOR)
+
title = arguments.getString(ARG_HEADLINE, getString(R.string.select_template))
title = when (savedInstanceState) {
null -> arguments.getString(ARG_HEADLINE)
@@ -175,7 +177,7 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
val builder = MaterialAlertDialogBuilder(activity)
builder.setView(view)
.setPositiveButton(R.string.create, null)
- .setNeutralButton(R.string.common_cancel, null)
+ .setNegativeButton(R.string.common_cancel, null)
.setTitle(title)
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.list.context, builder)
@@ -208,8 +210,8 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
}
fun setTemplateList(templateList: TemplateList?) {
- adapter!!.setTemplateList(templateList)
- adapter!!.notifyDataSetChanged()
+ adapter?.setTemplateList(templateList)
+ adapter?.notifyDataSetChanged()
}
override fun onClick(template: Template) {
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java b/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java
index a7333ffcab73..088e6f1aa127 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java
@@ -29,6 +29,7 @@
import android.widget.Button;
import android.widget.Toast;
+import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
@@ -70,7 +71,7 @@ public class ConflictsResolveDialog extends DialogFragment implements Injectable
public OnConflictDecisionMadeListener listener;
private User user;
private final List asyncTasks = new ArrayList<>();
- private Button positiveButton;
+ private MaterialButton positiveButton;
@Inject ViewThemeUtils viewThemeUtils;
@Inject SyncedFolderProvider syncedFolderProvider;
@@ -119,9 +120,11 @@ public void onStart() {
return;
}
- positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
- viewThemeUtils.platform.colorTextButtons(positiveButton,
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
+ positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
positiveButton.setEnabled(false);
}
@@ -175,7 +178,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
}
})
- .setNeutralButton(R.string.common_cancel, (dialog, which) -> {
+ .setNegativeButton(R.string.common_cancel, (dialog, which) -> {
if (listener != null) {
listener.conflictDecisionMade(Decision.CANCEL);
}
@@ -275,4 +278,5 @@ public void onStop() {
asyncTasks.clear();
}
+
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java b/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java
index 3703822aabed..c9a6d033767b 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java
@@ -31,6 +31,7 @@
import android.widget.Button;
import android.widget.TextView;
+import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.collect.Sets;
import com.nextcloud.client.di.Injectable;
@@ -71,7 +72,7 @@ public class CreateFolderDialogFragment
private OCFile mParentFolder;
- private Button positiveButton;
+ private MaterialButton positiveButton;
private EditBoxDialogBinding binding;
@@ -101,13 +102,11 @@ public void onStart() {
private void bindButton() {
Dialog dialog = getDialog();
- if (dialog instanceof AlertDialog) {
- AlertDialog alertDialog = (AlertDialog) dialog;
-
- positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-
- viewThemeUtils.platform.colorTextButtons(positiveButton,
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
+ if (dialog instanceof AlertDialog alertDialog) {
+ positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
}
}
@@ -186,17 +185,25 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
});
// Build the dialog
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
- builder.setView(view)
- .setPositiveButton(R.string.folder_confirm_create, this)
- .setNeutralButton(R.string.common_cancel, this)
- .setTitle(R.string.uploader_info_dirname);
+ MaterialAlertDialogBuilder builder = buildMaterialAlertDialog(view);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.userInputContainer.getContext(), builder);
return builder.create();
}
+ private MaterialAlertDialogBuilder buildMaterialAlertDialog(View view) {
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+
+ builder
+ .setView(view)
+ .setPositiveButton(R.string.folder_confirm_create, this)
+ .setNegativeButton(R.string.common_cancel, this)
+ .setTitle(R.string.uploader_info_dirname);
+
+ return builder;
+ }
+
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java b/app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
index f1c16c6b49cf..5d97c7c1c5a9 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
@@ -30,6 +30,7 @@
import android.text.format.DateUtils;
import android.widget.DatePicker;
+import com.google.android.material.button.MaterialButton;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.utils.theme.ViewThemeUtils;
@@ -81,12 +82,24 @@ public void setOnExpiryDateListener(OnExpiryDateListener onExpiryDateListener) {
public void onStart() {
super.onStart();
final Dialog currentDialog = getDialog();
+
if (currentDialog != null) {
final DatePickerDialog dialog = (DatePickerDialog) currentDialog;
- viewThemeUtils.platform.colorTextButtons(dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL),
- dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE),
- dialog.getButton(DatePickerDialog.BUTTON_POSITIVE));
+ MaterialButton positiveButton = (MaterialButton) dialog.getButton(DatePickerDialog.BUTTON_POSITIVE);
+ if (positiveButton != null) {
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
+ }
+
+ MaterialButton negativeButton = (MaterialButton) dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE);
+ if (negativeButton != null) {
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
+ }
+
+ MaterialButton neutralButton = (MaterialButton) dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL);
+ if (neutralButton != null) {
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(neutralButton);
+ }
}
}
@@ -118,7 +131,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
//show unset button only when date is already selected
if (chosenDateInMillis > 0) {
dialog.setButton(
- Dialog.BUTTON_NEUTRAL,
+ Dialog.BUTTON_NEGATIVE,
getText(R.string.share_via_link_unset_password),
(dialog1, which) -> {
if (onExpiryDateListener != null) {
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/NoteDialogFragment.java b/app/src/main/java/com/owncloud/android/ui/dialog/NoteDialogFragment.java
deleted file mode 100644
index 86ce2d6471fe..000000000000
--- a/app/src/main/java/com/owncloud/android/ui/dialog/NoteDialogFragment.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2018 Tobias Kaminsky
- * Copyright (C) 2018 Nextcloud GmbH.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.owncloud.android.ui.dialog;
-
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.nextcloud.client.di.Injectable;
-import com.owncloud.android.R;
-import com.owncloud.android.databinding.NoteDialogBinding;
-import com.owncloud.android.lib.resources.shares.OCShare;
-import com.owncloud.android.ui.activity.ComponentsGetter;
-import com.owncloud.android.utils.DisplayUtils;
-import com.owncloud.android.utils.KeyboardUtils;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-
-/**
- * Dialog to input a multiline note for a share
- */
-public class NoteDialogFragment extends DialogFragment implements DialogInterface.OnClickListener, Injectable {
-
- private static final String ARG_SHARE = "SHARE";
-
- @Inject ViewThemeUtils viewThemeUtils;
- @Inject KeyboardUtils keyboardUtils;
-
- private OCShare share;
- private NoteDialogBinding binding;
-
- public static NoteDialogFragment newInstance(OCShare share) {
- NoteDialogFragment frag = new NoteDialogFragment();
-
- Bundle args = new Bundle();
- args.putParcelable(ARG_SHARE, share);
- frag.setArguments(args);
-
- return frag;
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (getArguments() == null) {
- throw new IllegalArgumentException("Arguments may not be null");
- }
- share = getArguments().getParcelable(ARG_SHARE);
- }
-
- @Override
- public void onStart() {
- super.onStart();
-
- AlertDialog alertDialog = (AlertDialog) getDialog();
-
- viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
- }
-
- @Override
- public void onResume() {
- super.onResume();
- keyboardUtils.showKeyboardForEditText(requireDialog().getWindow(), binding.noteText);
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- // Inflate the layout for the dialog
- LayoutInflater inflater = requireActivity().getLayoutInflater();
- binding = NoteDialogBinding.inflate(inflater, null, false);
- View view = binding.getRoot();
-
- // Setup layout
- binding.noteText.setText(share.getNote());
- viewThemeUtils.material.colorTextInputLayout(binding.noteContainer);
-
- // Build the dialog
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(binding.noteContainer.getContext());
- builder.setView(view)
- .setPositiveButton(R.string.note_confirm, this)
- .setNeutralButton(R.string.common_cancel, this)
- .setTitle(R.string.send_note);
-
- viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.noteContainer.getContext(), builder);
-
- return builder.create();
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == AlertDialog.BUTTON_POSITIVE) {
- ComponentsGetter componentsGetter = (ComponentsGetter) getActivity();
-
- if (componentsGetter != null) {
- String note = "";
-
- if (binding.noteText.getText() != null) {
- note = binding.noteText.getText().toString().trim();
- }
-
- componentsGetter.getFileOperationsHelper().updateNoteToShare(share, note);
- } else {
- DisplayUtils.showSnackMessage(requireActivity(), R.string.note_could_not_sent);
- }
- }
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- binding = null;
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.java b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.java
index 64b832b34d51..087ff25035f8 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.java
@@ -23,6 +23,7 @@
import android.os.Bundle;
import android.view.ActionMode;
+import com.google.android.material.button.MaterialButton;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
@@ -96,15 +97,20 @@ public static RemoveFilesDialogFragment newInstance(ArrayList files) {
R.string.confirmation_remove_files_alert;
}
- int localRemoveButton = (containsFolder || containsDown) ? R.string.confirmation_remove_local : -1;
-
args.putInt(ARG_MESSAGE_RESOURCE_ID, messageStringId);
if (files.size() == SINGLE_SELECTION) {
- args.putStringArray(ARG_MESSAGE_ARGUMENTS, new String[]{files.get(0).getFileName()});
+ args.putStringArray(ARG_MESSAGE_ARGUMENTS, new String[] { files.get(0).getFileName() } );
}
+
args.putInt(ARG_POSITIVE_BTN_RES, R.string.file_delete);
- args.putInt(ARG_NEUTRAL_BTN_RES, R.string.file_keep);
- args.putInt(ARG_NEGATIVE_BTN_RES, localRemoveButton);
+
+ if (containsFolder || containsDown) {
+ args.putInt(ARG_NEGATIVE_BTN_RES, R.string.confirmation_remove_local);
+ args.putInt(ARG_NEUTRAL_BTN_RES, R.string.file_keep);
+ } else {
+ args.putInt(ARG_NEGATIVE_BTN_RES, R.string.file_keep);
+ }
+
args.putParcelableArrayList(ARG_TARGET_FILES, files);
frag.setArguments(args);
@@ -131,9 +137,16 @@ public void onStart() {
AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog != null) {
- viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
- alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE),
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
+ MaterialButton positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
+
+ MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
+
+ MaterialButton neutralButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
+ if (neutralButton != null) {
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(neutralButton);
+ }
}
}
@@ -141,10 +154,14 @@ public void onStart() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
- mTargetFiles = getArguments().getParcelableArrayList(ARG_TARGET_FILES);
+ Bundle arguments = getArguments();
- setOnConfirmationListener(this);
+ if (arguments == null) {
+ return dialog;
+ }
+ mTargetFiles = arguments.getParcelableArrayList(ARG_TARGET_FILES);
+ setOnConfirmationListener(this);
return dialog;
}
@@ -154,9 +171,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
*/
@Override
public void onConfirmation(String callerTag) {
- ComponentsGetter cg = (ComponentsGetter) getActivity();
- cg.getFileOperationsHelper().removeFiles(mTargetFiles, false, false);
- finishActionMode();
+ removeFiles(false);
}
/**
@@ -164,8 +179,14 @@ public void onConfirmation(String callerTag) {
*/
@Override
public void onCancel(String callerTag) {
+ removeFiles(true);
+ }
+
+ private void removeFiles(boolean onlyLocalCopy) {
ComponentsGetter cg = (ComponentsGetter) getActivity();
- cg.getFileOperationsHelper().removeFiles(mTargetFiles, true, false);
+ if (cg != null) {
+ cg.getFileOperationsHelper().removeFiles(mTargetFiles, onlyLocalCopy, false);
+ }
finishActionMode();
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/RenamePublicShareDialogFragment.java b/app/src/main/java/com/owncloud/android/ui/dialog/RenamePublicShareDialogFragment.java
index e043a8975acc..7973b93fafe0 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/RenamePublicShareDialogFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/RenamePublicShareDialogFragment.java
@@ -28,6 +28,7 @@
import android.view.LayoutInflater;
import android.view.View;
+import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
@@ -52,8 +53,6 @@ public class RenamePublicShareDialogFragment
private static final String ARG_PUBLIC_SHARE = "PUBLIC_SHARE";
- public static final String RENAME_PUBLIC_SHARE_FRAGMENT = "RENAME_PUBLIC_SHARE_FRAGMENT";
-
@Inject ViewThemeUtils viewThemeUtils;
@Inject KeyboardUtils keyboardUtils;
@@ -75,8 +74,10 @@ public void onStart() {
AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog != null) {
- viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
- alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
+ MaterialButton positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
}
}
@@ -104,7 +105,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(view.getContext());
builder.setView(view)
.setPositiveButton(R.string.file_rename, this)
- .setNeutralButton(R.string.common_cancel, this)
+ .setNegativeButton(R.string.common_cancel, this)
.setTitle(R.string.public_share_name);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.userInput.getContext(), builder);
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/SslValidatorDialog.java b/app/src/main/java/com/owncloud/android/ui/dialog/SslValidatorDialog.java
deleted file mode 100644
index 2373681946bd..000000000000
--- a/app/src/main/java/com/owncloud/android/ui/dialog/SslValidatorDialog.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/**
- * ownCloud Android client application
- *
- * @author David A. Velasco
- * Copyright (C) 2015 ownCloud Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package com.owncloud.android.ui.dialog;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.View;
-import android.view.Window;
-import android.widget.Button;
-
-import com.owncloud.android.R;
-import com.owncloud.android.databinding.SslValidatorLayoutBinding;
-import com.owncloud.android.lib.common.network.CertificateCombinedException;
-import com.owncloud.android.lib.common.network.NetworkUtils;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.utils.Log_OC;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.security.auth.x500.X500Principal;
-
-/**
- * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
- */
-public class SslValidatorDialog extends Dialog {
-
- private final static String TAG = SslValidatorDialog.class.getSimpleName();
-
- private OnSslValidatorListener mListener;
- private CertificateCombinedException mException;
- private SslValidatorLayoutBinding binding;
-
-
- /**
- * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should
- * be trusted.
- *
- * @param context Android context where the dialog will live.
- * @param result Result of a failed remote operation.
- * @param listener Object to notice when the server certificate was added to the local certificates store.
- * @return A new SslValidatorDialog instance. NULL if the operation can not be recovered
- * by setting the certificate as reliable.
- */
- public static SslValidatorDialog newInstance(Context context, RemoteOperationResult result, OnSslValidatorListener listener) {
- if (result != null && result.isSslRecoverableException()) {
- return new SslValidatorDialog(context, listener);
- } else {
- return null;
- }
- }
-
- /**
- * Private constructor.
- *
- * Instances have to be created through static {@link SslValidatorDialog#newInstance}.
- *
- * @param context Android context where the dialog will live
- * @param listener Object to notice when the server certificate was added to the local certificates store.
- */
- private SslValidatorDialog(Context context, OnSslValidatorListener listener) {
- super(context);
- mListener = listener;
- }
-
- /**
- * {@inheritDoc}
- */
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- binding = SslValidatorLayoutBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
-
- binding.ok.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- try {
- saveServerCert();
- dismiss();
- if (mListener != null) {
- mListener.onSavedCertificate();
- } else {
- Log_OC.d(TAG, "Nobody there to notify the certificate was saved");
- }
-
- } catch (GeneralSecurityException | IOException e) {
- dismiss();
- if (mListener != null) {
- mListener.onFailedSavingCertificate();
- }
- Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
-
- }
- }
- });
-
- binding.cancel.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- cancel();
- }
- });
-
- binding.detailsBtn.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- View detailsScroll = findViewById(R.id.details_scroll);
- if (detailsScroll.getVisibility() == View.VISIBLE) {
- detailsScroll.setVisibility(View.GONE);
- ((Button) v).setText(R.string.ssl_validator_btn_details_see);
- } else {
- detailsScroll.setVisibility(View.VISIBLE);
- ((Button) v).setText(R.string.ssl_validator_btn_details_hide);
- }
- }
- });
- }
-
-
- public void updateResult(RemoteOperationResult result) {
- if (result.isSslRecoverableException()) {
- mException = (CertificateCombinedException) result.getException();
-
- /// clean
- binding.reasonCertNotTrusted.setVisibility(View.GONE);
- binding.reasonCertExpired.setVisibility(View.GONE);
- binding.reasonCertNotYetValid.setVisibility(View.GONE);
- binding.reasonHostnameNotVerified.setVisibility(View.GONE);
- binding.detailsScroll.setVisibility(View.GONE);
-
- /// refresh
- if (mException.getCertPathValidatorException() != null) {
- binding.reasonCertNotTrusted.setVisibility(View.VISIBLE);
- }
-
- if (mException.getCertificateExpiredException() != null) {
- binding.reasonCertExpired.setVisibility(View.VISIBLE);
- }
-
- if (mException.getCertificateNotYetValidException() != null) {
- binding.reasonCertNotYetValid.setVisibility(View.VISIBLE);
- }
-
- if (mException.getSslPeerUnverifiedException() != null ) {
- binding.reasonHostnameNotVerified.setVisibility(View.VISIBLE);
- }
-
- showCertificateData(mException.getServerCertificate());
- }
- }
-
- private void showCertificateData(X509Certificate cert) {
-
- if (cert != null) {
- showSubject(cert.getSubjectX500Principal());
- showIssuer(cert.getIssuerX500Principal());
- showValidity(cert.getNotBefore(), cert.getNotAfter());
- showSignature(cert);
-
- } else {
- // this should not happen, TODO
- Log_OC.d("certNull", "This should not happen");
- }
- }
-
- private void showSignature(X509Certificate cert) {
- binding.valueSignature.setText(getHex(cert.getSignature()));
- binding.valueSignatureAlgorithm.setText(cert.getSigAlgName());
- }
-
- public String getHex(final byte [] raw) {
- if (raw == null) {
- return null;
- }
- final StringBuilder hex = new StringBuilder(2 * raw.length);
- for (final byte b : raw) {
- final int hiVal = (b & 0xF0) >> 4;
- final int loVal = b & 0x0F;
- hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7))));
- hex.append((char) ('0' + (loVal + (loVal / 10 * 7))));
- }
- return hex.toString();
- }
-
- @SuppressWarnings("deprecation")
- private void showValidity(Date notBefore, Date notAfter) {
- binding.valueValidityFrom.setText(notBefore.toLocaleString());
- binding.valueValidityTo.setText(notAfter.toLocaleString());
- }
-
- private void showSubject(X500Principal subject) {
- Map s = parsePrincipal(subject);
-
- if (s.get("CN") != null) {
- binding.valueSubjectCN.setText(s.get("CN"));
- binding.valueSubjectCN.setVisibility(View.VISIBLE);
- } else {
- binding.valueSubjectCN.setVisibility(View.GONE);
- }
- if (s.get("O") != null) {
- binding.valueSubjectO.setText(s.get("O"));
- binding.valueSubjectO.setVisibility(View.VISIBLE);
- } else {
- binding.valueSubjectO.setVisibility(View.GONE);
- }
- if (s.get("OU") != null) {
- binding.valueSubjectOU.setText(s.get("OU"));
- binding.valueSubjectOU.setVisibility(View.VISIBLE);
- } else {
- binding.valueSubjectOU.setVisibility(View.GONE);
- }
- if (s.get("C") != null) {
- binding.valueSubjectC.setText(s.get("C"));
- binding.valueSubjectC.setVisibility(View.VISIBLE);
- } else {
- binding.valueSubjectC.setVisibility(View.GONE);
- }
- if (s.get("ST") != null) {
- binding.valueSubjectST.setText(s.get("ST"));
- binding.valueSubjectST.setVisibility(View.VISIBLE);
- } else {
- binding.valueSubjectST.setVisibility(View.GONE);
- }
- if (s.get("L") != null) {
- binding.valueSubjectL.setText(s.get("L"));
- binding.valueSubjectL.setVisibility(View.VISIBLE);
- } else {
- binding.valueSubjectL.setVisibility(View.GONE);
- }
- }
-
- private void showIssuer(X500Principal issuer) {
- Map s = parsePrincipal(issuer);
-
- if (s.get("CN") != null) {
- binding.valueIssuerCN.setText(s.get("CN"));
- binding.valueIssuerCN.setVisibility(View.VISIBLE);
- } else {
- binding.valueIssuerCN.setVisibility(View.GONE);
- }
- if (s.get("O") != null) {
- binding.valueIssuerO.setText(s.get("O"));
- binding.valueIssuerO.setVisibility(View.VISIBLE);
- } else {
- binding.valueIssuerO.setVisibility(View.GONE);
- }
- if (s.get("OU") != null) {
- binding.valueIssuerOU.setText(s.get("OU"));
- binding.valueIssuerOU.setVisibility(View.VISIBLE);
- } else {
- binding.valueIssuerOU.setVisibility(View.GONE);
- }
- if (s.get("C") != null) {
- binding.valueIssuerC.setText(s.get("C"));
- binding.valueIssuerC.setVisibility(View.VISIBLE);
- } else {
- binding.valueIssuerC.setVisibility(View.GONE);
- }
- if (s.get("ST") != null) {
- binding.valueIssuerST.setText(s.get("ST"));
- binding.valueIssuerST.setVisibility(View.VISIBLE);
- } else {
- binding.valueIssuerST.setVisibility(View.GONE);
- }
- if (s.get("L") != null) {
- binding.valueIssuerL.setText(s.get("L"));
- binding.valueIssuerL.setVisibility(View.VISIBLE);
- } else {
- binding.valueIssuerL.setVisibility(View.GONE);
- }
- }
-
- private Map parsePrincipal(X500Principal principal) {
- Map result = new HashMap<>();
- String toParse = principal.getName();
- String[] pieces = toParse.split(",");
- String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
- for (String piece : pieces) {
- for (String token : tokens) {
- if (piece.startsWith(token + "=")) {
- result.put(token, piece.substring(token.length() + 1));
- }
- }
- }
- return result;
- }
-
- private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
- if (mException.getServerCertificate() != null) {
- // TODO make this asynchronously, it can take some time
- NetworkUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext());
- }
- }
-
- public interface OnSslValidatorListener {
- public void onSavedCertificate();
- public void onFailedSavingCertificate();
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt
index c3cddfcc30a1..d2b179e66628 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt
@@ -24,15 +24,14 @@ import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
-import android.view.View
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.client.di.Injectable
import com.owncloud.android.R
-import com.owncloud.android.databinding.StoragePermissionDialogBinding
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
@@ -43,10 +42,7 @@ import javax.inject.Inject
* Allows choosing "full access" (MANAGE_ALL_FILES) or "read-only media" (READ_EXTERNAL_STORAGE)
*/
@RequiresApi(Build.VERSION_CODES.R)
-class StoragePermissionDialogFragment :
- DialogFragment(), Injectable {
-
- private lateinit var binding: StoragePermissionDialogBinding
+class StoragePermissionDialogFragment : DialogFragment(), Injectable {
private var permissionRequired = false
@@ -64,51 +60,48 @@ class StoragePermissionDialogFragment :
super.onStart()
dialog?.let {
val alertDialog = it as AlertDialog
- viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE))
- }
- }
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- // Inflate the layout for the dialog
- val inflater = requireActivity().layoutInflater
- binding = StoragePermissionDialogBinding.inflate(inflater, null, false)
+ val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton)
- val view: View = binding.root
- val explanationResource = when {
- permissionRequired -> R.string.file_management_permission_text
- else -> R.string.file_management_permission_optional_text
- }
- binding.storagePermissionExplanation.text = getString(explanationResource, getString(R.string.app_name))
+ val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton)
- // Setup layout
- viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.btnFullAccess)
- binding.btnFullAccess.setOnClickListener {
- setResult(Result.FULL_ACCESS)
- dismiss()
- }
- viewThemeUtils.platform.colorTextButtons(binding.btnReadOnly)
- binding.btnReadOnly.setOnClickListener {
- setResult(Result.MEDIA_READ_ONLY)
- dismiss()
+ val neutralButton = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL) as MaterialButton
+ viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(neutralButton)
}
+ }
- // Build the dialog
- val titleResource = when {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val title = when {
permissionRequired -> R.string.file_management_permission
else -> R.string.file_management_permission_optional
}
+ val explanationResource = when {
+ permissionRequired -> R.string.file_management_permission_text
+ else -> R.string.file_management_permission_optional_text
+ }
+ val message = getString(explanationResource, getString(R.string.app_name))
- val builder = MaterialAlertDialogBuilder(binding.btnReadOnly.context)
- .setTitle(titleResource)
- .setView(view)
- .setNegativeButton(R.string.common_cancel) { _, _ ->
+ val dialogBuilder = MaterialAlertDialogBuilder(requireContext())
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(R.string.storage_permission_full_access) { _, _ ->
+ setResult(Result.FULL_ACCESS)
+ dismiss()
+ }
+ .setNegativeButton(R.string.storage_permission_media_read_only) { _, _ ->
+ setResult(Result.MEDIA_READ_ONLY)
+ dismiss()
+ }
+ .setNeutralButton(R.string.common_cancel) { _, _ ->
setResult(Result.CANCEL)
dismiss()
}
- viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.btnReadOnly.context, builder)
+ viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireContext(), dialogBuilder)
- return builder.create()
+ return dialogBuilder.create()
}
private fun setResult(result: Result) {
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
index 8991de8c5536..871a4624a13e 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
@@ -284,6 +284,10 @@ private void fetchAndSetData(int lastGiven) {
});
return;
}
+
+ if (!isLoadingActivities) {
+ return;
+ }
Thread t = new Thread(() -> {
try {
@@ -454,6 +458,11 @@ public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
return false;
}
+
+ @VisibleForTesting
+ public void disableLoadingActivities() {
+ isLoadingActivities = false;
+ }
private static class SubmitCommentTask extends AsyncTask {
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.java
deleted file mode 100644
index aad71fe6246f..000000000000
--- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.java
+++ /dev/null
@@ -1,691 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author TSI-mc
- * Copyright (C) 2017 Mario Danic
- * Copyright (C) 2017 Nextcloud GmbH.
- * Copyright (C) 2023 TSI-mc
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package com.owncloud.android.ui.fragment.contactsbackup;
-
-import android.Manifest;
-import android.app.DatePickerDialog;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.DatePicker;
-import android.widget.Toast;
-
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.jobs.BackgroundJobManager;
-import com.nextcloud.java.util.Optional;
-import com.owncloud.android.R;
-import com.owncloud.android.databinding.BackupFragmentBinding;
-import com.owncloud.android.datamodel.ArbitraryDataProvider;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.operations.RefreshFolderOperation;
-import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
-import com.owncloud.android.ui.activity.SettingsActivity;
-import com.owncloud.android.ui.fragment.FileFragment;
-import com.owncloud.android.utils.DisplayUtils;
-import com.owncloud.android.utils.MimeTypeUtil;
-import com.owncloud.android.utils.PermissionUtil;
-import com.owncloud.android.utils.theme.ThemeUtils;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.ActionBar;
-import androidx.fragment.app.Fragment;
-import third_parties.daveKoeller.AlphanumComparator;
-
-import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP;
-import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP;
-
-public class BackupFragment extends FileFragment implements DatePickerDialog.OnDateSetListener, Injectable {
- public static final String TAG = BackupFragment.class.getSimpleName();
- private static final String ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR";
- private static final String KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN";
- private static final String KEY_CALENDAR_DAY = "CALENDAR_DAY";
- private static final String KEY_CALENDAR_MONTH = "CALENDAR_MONTH";
- private static final String KEY_CALENDAR_YEAR = "CALENDAR_YEAR";
-
- public static final String PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED";
- public static final String PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED";
-
-
- private BackupFragmentBinding binding;
-
- @Inject BackgroundJobManager backgroundJobManager;
- @Inject ThemeUtils themeUtils;
-
- @Inject ArbitraryDataProvider arbitraryDataProvider;
- @Inject ViewThemeUtils viewThemeUtils;
-
- private Date selectedDate;
- private boolean calendarPickerOpen;
-
- private DatePickerDialog datePickerDialog;
-
- private CompoundButton.OnCheckedChangeListener dailyBackupCheckedChangeListener;
- private CompoundButton.OnCheckedChangeListener contactsCheckedListener;
- private CompoundButton.OnCheckedChangeListener calendarCheckedListener;
- private User user;
- private boolean showSidebar = true;
- //flag to check if calendar backup should be shown and backup should be done or not
- private boolean showCalendarBackup = true;
- public static BackupFragment create(boolean showSidebar) {
- BackupFragment fragment = new BackupFragment();
- Bundle bundle = new Bundle();
- bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar);
- fragment.setArguments(bundle);
- return fragment;
- }
-
- private boolean isCalendarBackupEnabled() {
- return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CALENDAR_BACKUP_ENABLED);
- }
-
- private void setCalendarBackupEnabled(final boolean enabled) {
- arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CALENDAR_BACKUP_ENABLED, enabled);
- }
-
- private boolean isContactsBackupEnabled() {
- return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CONTACTS_BACKUP_ENABLED);
- }
-
- private void setContactsBackupEnabled(final boolean enabled) {
- arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CONTACTS_BACKUP_ENABLED, enabled);
- }
-
- @Override
- public View onCreateView(@NonNull final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-
- // use grey as fallback for elements where custom theming is not available
- if (themeUtils.themingEnabled(getContext())) {
- getContext().getTheme().applyStyle(R.style.FallbackThemingTheme, true);
- }
-
- binding = BackupFragmentBinding.inflate(inflater, container, false);
- View view = binding.getRoot();
-
- setHasOptionsMenu(true);
-
- if (getArguments() != null) {
- showSidebar = getArguments().getBoolean(ARG_SHOW_SIDEBAR);
- }
-
- showCalendarBackup = requireContext().getResources().getBoolean(R.bool.show_calendar_backup);
-
- final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
- user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new);
-
- ActionBar actionBar = contactsPreferenceActivity != null ? contactsPreferenceActivity.getSupportActionBar() : null;
-
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- viewThemeUtils.files.themeActionBar(requireContext(), actionBar,
- showCalendarBackup ? R.string.backup_title : R.string.contact_backup_title);
- }
-
-
- viewThemeUtils.androidx.colorSwitchCompat(binding.contacts);
- viewThemeUtils.androidx.colorSwitchCompat(binding.calendar);
- viewThemeUtils.androidx.colorSwitchCompat(binding.dailyBackup);
- binding.dailyBackup.setChecked(arbitraryDataProvider.getBooleanValue(user,
- PREFERENCE_CONTACTS_AUTOMATIC_BACKUP));
-
- binding.contacts.setChecked(isContactsBackupEnabled() && checkContactBackupPermission());
- binding.calendar.setChecked(isCalendarBackupEnabled() && checkCalendarBackupPermission(getContext()));
-
- binding.calendar.setVisibility(showCalendarBackup ? View.VISIBLE : View.GONE);
-
- setupCheckListeners();
-
- setBackupNowButtonVisibility();
-
- binding.backupNow.setOnClickListener(v -> backupNow());
-
- binding.contactsDatepicker.setOnClickListener(v -> openCleanDate());
-
- // display last backup
- Long lastBackupTimestamp = arbitraryDataProvider.getLongValue(user, PREFERENCE_CONTACTS_LAST_BACKUP);
-
- if (lastBackupTimestamp == -1) {
- binding.lastBackupWithDate.setVisibility(View.GONE);
- } else {
- binding.lastBackupWithDate.setText(
- String.format(getString(R.string.last_backup),
- DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp)));
- }
-
- if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) {
- if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 &&
- savedInstanceState.getInt(KEY_CALENDAR_MONTH, -1) != -1 &&
- savedInstanceState.getInt(KEY_CALENDAR_DAY, -1) != -1) {
- selectedDate = new Date(savedInstanceState.getInt(KEY_CALENDAR_YEAR),
- savedInstanceState.getInt(KEY_CALENDAR_MONTH), savedInstanceState.getInt(KEY_CALENDAR_DAY));
- }
- calendarPickerOpen = true;
- }
-
- viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.backupNow);
- viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker);
-
- viewThemeUtils.platform.colorTextView(binding.dataToBackUpTitle);
- viewThemeUtils.platform.colorTextView(binding.backupSettingsTitle);
-
- return view;
- }
-
- private void setupCheckListeners() {
- dailyBackupCheckedChangeListener = (buttonView, isChecked) -> {
- if (checkAndAskForContactsReadPermission()) {
- setAutomaticBackup(isChecked);
- }
- };
- binding.dailyBackup.setOnCheckedChangeListener(dailyBackupCheckedChangeListener);
-
-
- contactsCheckedListener = (buttonView, isChecked) -> {
- if (isChecked) {
- if (checkAndAskForContactsReadPermission()) {
- setContactsBackupEnabled(true);
- }
- } else {
- setContactsBackupEnabled(false);
- }
- setBackupNowButtonVisibility();
- setAutomaticBackup(binding.dailyBackup.isChecked());
- };
- binding.contacts.setOnCheckedChangeListener(contactsCheckedListener);
-
- calendarCheckedListener = (buttonView, isChecked) -> {
- if (isChecked) {
- if (checkAndAskForCalendarReadPermission()) {
- setCalendarBackupEnabled(true);
- }
- } else {
- setCalendarBackupEnabled(false);
- }
- setBackupNowButtonVisibility();
- setAutomaticBackup(binding.dailyBackup.isChecked());
- };
- binding.calendar.setOnCheckedChangeListener(calendarCheckedListener);
- }
-
- private void setBackupNowButtonVisibility() {
- if (binding.contacts.isChecked() || binding.calendar.isChecked()) {
- binding.backupNow.setVisibility(View.VISIBLE);
- } else {
- binding.backupNow.setVisibility(View.INVISIBLE);
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (calendarPickerOpen) {
- if (selectedDate != null) {
- openDate(selectedDate);
- } else {
- openDate(null);
- }
- }
-
- final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
- if (contactsPreferenceActivity != null) {
- String backupFolderPath = getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
- refreshBackupFolder(backupFolderPath,
- contactsPreferenceActivity.getApplicationContext(),
- contactsPreferenceActivity.getStorageManager());
- }
- }
-
- private void refreshBackupFolder(final String backupFolderPath,
- final Context context,
- final FileDataStorageManager storageManager) {
- AsyncTask task = new AsyncTask() {
- @Override
- protected Boolean doInBackground(String... path) {
- OCFile folder = storageManager.getFileByPath(path[0]);
-
- if (folder != null) {
- RefreshFolderOperation operation = new RefreshFolderOperation(folder, System.currentTimeMillis(),
- false, false, storageManager, user, context);
-
- RemoteOperationResult result = operation.execute(user, context);
- return result.isSuccess();
- } else {
- return Boolean.FALSE;
- }
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- if (result && binding != null) {
- OCFile backupFolder = storageManager.getFileByPath(backupFolderPath);
-
- List backupFiles = storageManager
- .getFolderContent(backupFolder, false);
-
- Collections.sort(backupFiles, new AlphanumComparator<>());
-
- if (backupFiles == null || backupFiles.isEmpty()) {
- binding.contactsDatepicker.setVisibility(View.INVISIBLE);
- } else {
- binding.contactsDatepicker.setVisibility(View.VISIBLE);
- }
- }
- }
- };
-
- task.execute(backupFolderPath);
- }
-
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
-
- boolean retval;
- switch (item.getItemId()) {
- case android.R.id.home:
- if (showSidebar) {
- if (contactsPreferenceActivity.isDrawerOpen()) {
- contactsPreferenceActivity.closeDrawer();
- } else {
- contactsPreferenceActivity.openDrawer();
- }
- } else if (getActivity() != null) {
- getActivity().finish();
- } else {
- Intent settingsIntent = new Intent(getContext(), SettingsActivity.class);
- settingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(settingsIntent);
- }
- retval = true;
- break;
-
- default:
- retval = super.onOptionsItemSelected(item);
- break;
- }
- return retval;
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-
- if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) {
- for (int index = 0; index < permissions.length; index++) {
- if (Manifest.permission.READ_CONTACTS.equalsIgnoreCase(permissions[index])) {
- if (grantResults[index] >= 0) {
- // if approved, exit for loop
- setContactsBackupEnabled(true);
- break;
- }
-
- // if not accepted, disable again
- binding.contacts.setOnCheckedChangeListener(null);
- binding.contacts.setChecked(false);
- binding.contacts.setOnCheckedChangeListener(contactsCheckedListener);
- }
- }
- }
-
- if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) {
- boolean readGranted = false;
- boolean writeGranted = false;
- for (int index = 0; index < permissions.length; index++) {
- if (Manifest.permission.WRITE_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) {
- writeGranted = true;
- } else if (Manifest.permission.READ_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) {
- readGranted = true;
- }
- }
- if (!readGranted || !writeGranted) {
- // if not accepted, disable again
- binding.calendar.setOnCheckedChangeListener(null);
- binding.calendar.setChecked(false);
- binding.calendar.setOnCheckedChangeListener(calendarCheckedListener);
- } else {
- setCalendarBackupEnabled(true);
- }
- }
-
- setBackupNowButtonVisibility();
- setAutomaticBackup(binding.dailyBackup.isChecked());
- }
-
- public void backupNow() {
- if (isContactsBackupEnabled() && checkContactBackupPermission()) {
- startContactsBackupJob();
- }
-
- if (showCalendarBackup && isCalendarBackupEnabled() && checkCalendarBackupPermission(requireContext())) {
- startCalendarBackupJob();
- }
-
- DisplayUtils.showSnackMessage(requireView().findViewById(R.id.contacts_linear_layout),
- R.string.contacts_preferences_backup_scheduled);
- }
-
- private void startContactsBackupJob() {
- ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
- if (activity != null) {
- Optional optionalUser = activity.getUser();
- if (optionalUser.isPresent()) {
- backgroundJobManager.startImmediateContactsBackup(optionalUser.get());
- }
- }
- }
-
- private void startCalendarBackupJob() {
- ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
- if (activity != null) {
- Optional optionalUser = activity.getUser();
- if (optionalUser.isPresent()) {
- backgroundJobManager.startImmediateCalendarBackup(optionalUser.get());
- }
- }
- }
-
- private void setAutomaticBackup(final boolean enabled) {
-
- final ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
- if (activity == null) {
- return;
- }
- Optional optionalUser = activity.getUser();
- if (!optionalUser.isPresent()) {
- return;
- }
- User user = optionalUser.get();
- if (enabled) {
- if (isContactsBackupEnabled()) {
- Log_OC.d(TAG, "Scheduling contacts backup job");
- backgroundJobManager.schedulePeriodicContactsBackup(user);
- } else {
- Log_OC.d(TAG, "Cancelling contacts backup job");
- backgroundJobManager.cancelPeriodicContactsBackup(user);
- }
- if (isCalendarBackupEnabled()) {
- Log_OC.d(TAG, "Scheduling calendar backup job");
- backgroundJobManager.schedulePeriodicCalendarBackup(user);
- } else {
- Log_OC.d(TAG, "Cancelling calendar backup job");
- backgroundJobManager.cancelPeriodicCalendarBackup(user);
- }
- } else {
- Log_OC.d(TAG, "Cancelling all backup jobs");
- backgroundJobManager.cancelPeriodicContactsBackup(user);
- backgroundJobManager.cancelPeriodicCalendarBackup(user);
- }
-
- arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
- PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
- String.valueOf(enabled));
- }
-
- private boolean checkAndAskForContactsReadPermission() {
- final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
-
- // check permissions
- if (PermissionUtil.checkSelfPermission(contactsPreferenceActivity, Manifest.permission.READ_CONTACTS)) {
- return true;
- } else {
- // No explanation needed, request the permission.
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
- PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC);
- return false;
- }
- }
-
- private boolean checkAndAskForCalendarReadPermission() {
- final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
-
- // check permissions
- if (checkCalendarBackupPermission(contactsPreferenceActivity)) {
- return true;
- } else {
- // No explanation needed, request the permission.
- requestPermissions(new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR},
- PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC);
- return false;
- }
- }
-
- private boolean checkCalendarBackupPermission(final Context context) {
- return PermissionUtil.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) && PermissionUtil.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR);
- }
-
- private boolean checkContactBackupPermission() {
- return PermissionUtil.checkSelfPermission(getContext(), Manifest.permission.READ_CONTACTS);
- }
-
- public void openCleanDate() {
- if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) {
- openDate(null);
- }
- }
-
- public void openDate(@Nullable Date savedDate) {
- final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
-
- if (contactsPreferenceActivity == null) {
- Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show();
- return;
- }
-
- String contactsBackupFolderString =
- getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
- String calendarBackupFolderString =
- getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR;
-
- FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager();
-
- OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString);
- OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString);
-
- List backupFiles = storageManager.getFolderContent(contactsBackupFolder, false);
- backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false));
-
- Collections.sort(backupFiles, (o1, o2) -> {
- return Long.compare(o1.getModificationTimestamp(), o2.getModificationTimestamp());
- });
-
- Calendar cal = Calendar.getInstance();
- int year;
- int month;
- int day;
-
- if (savedDate == null) {
- year = cal.get(Calendar.YEAR);
- month = cal.get(Calendar.MONTH) + 1;
- day = cal.get(Calendar.DAY_OF_MONTH);
- } else {
- year = savedDate.getYear();
- month = savedDate.getMonth();
- day = savedDate.getDay();
- }
-
- if (backupFiles.size() > 0 && backupFiles.get(backupFiles.size() - 1) != null) {
- datePickerDialog = new DatePickerDialog(contactsPreferenceActivity, this, year, month, day);
- datePickerDialog.getDatePicker().setMaxDate(backupFiles.get(backupFiles.size() - 1)
- .getModificationTimestamp());
- datePickerDialog.getDatePicker().setMinDate(backupFiles.get(0).getModificationTimestamp());
-
- datePickerDialog.setOnDismissListener(dialog -> selectedDate = null);
-
- datePickerDialog.setTitle("");
- datePickerDialog.show();
-
- viewThemeUtils.platform.colorTextButtons(datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE),
- datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE));
-
- // set background to transparent
- datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE).setBackgroundColor(0x00000000);
- datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE).setBackgroundColor(0x00000000);
- } else {
- DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout),
- R.string.contacts_preferences_something_strange_happened);
- }
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (datePickerDialog != null) {
- datePickerDialog.dismiss();
- }
- }
-
- @Override
- public void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- if (datePickerDialog != null) {
- outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, datePickerDialog.isShowing());
-
- if (datePickerDialog.isShowing()) {
- outState.putInt(KEY_CALENDAR_DAY, datePickerDialog.getDatePicker().getDayOfMonth());
- outState.putInt(KEY_CALENDAR_MONTH, datePickerDialog.getDatePicker().getMonth());
- outState.putInt(KEY_CALENDAR_YEAR, datePickerDialog.getDatePicker().getYear());
- }
- }
- }
-
- @Override
- public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
- final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
-
- if (contactsPreferenceActivity == null) {
- Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show();
- return;
- }
-
- selectedDate = new Date(year, month, dayOfMonth);
-
- String contactsBackupFolderString =
- getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
- String calendarBackupFolderString =
- getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR;
-
- FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager();
-
- OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString);
- OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString);
-
- List backupFiles = storageManager.getFolderContent(contactsBackupFolder, false);
- backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false));
-
- // find file with modification with date and time between 00:00 and 23:59
- // if more than one file exists, take oldest
- Calendar date = Calendar.getInstance();
- date.set(year, month, dayOfMonth);
-
- // start
- date.set(Calendar.HOUR, 0);
- date.set(Calendar.MINUTE, 0);
- date.set(Calendar.SECOND, 1);
- date.set(Calendar.MILLISECOND, 0);
- date.set(Calendar.AM_PM, Calendar.AM);
- long start = date.getTimeInMillis();
-
- // end
- date.set(Calendar.HOUR, 23);
- date.set(Calendar.MINUTE, 59);
- date.set(Calendar.SECOND, 59);
- long end = date.getTimeInMillis();
-
- OCFile contactsBackupToRestore = null;
- List calendarBackupsToRestore = new ArrayList<>();
-
- for (OCFile file : backupFiles) {
- if (start < file.getModificationTimestamp() && end > file.getModificationTimestamp()) {
- // contact
- if (MimeTypeUtil.isVCard(file)) {
- if (contactsBackupToRestore == null) {
- contactsBackupToRestore = file;
- } else if (contactsBackupToRestore.getModificationTimestamp() < file.getModificationTimestamp()) {
- contactsBackupToRestore = file;
- }
- }
-
- // calendars
- if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) {
- calendarBackupsToRestore.add(file);
- }
- }
- }
-
- List backupToRestore = new ArrayList<>();
-
- if (contactsBackupToRestore != null) {
- backupToRestore.add(contactsBackupToRestore);
- }
-
- backupToRestore.addAll(calendarBackupsToRestore);
-
-
- if (backupToRestore.isEmpty()) {
- DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout),
- R.string.contacts_preferences_no_file_found);
- } else {
- final User user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new);
- OCFile[] files = new OCFile[backupToRestore.size()];
- Fragment contactListFragment = BackupListFragment.newInstance(backupToRestore.toArray(files), user);
-
- contactsPreferenceActivity.getSupportFragmentManager().
- beginTransaction()
- .replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG)
- .addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST)
- .commit();
- }
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt
new file mode 100644
index 000000000000..e216bacaed7d
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt
@@ -0,0 +1,726 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author TSI-mc
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud GmbH.
+ * Copyright (C) 2023 TSI-mc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.owncloud.android.ui.fragment.contactsbackup
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.app.DatePickerDialog
+import android.app.DatePickerDialog.OnDateSetListener
+import android.content.Context
+import android.content.Intent
+import android.os.AsyncTask
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CompoundButton
+import android.widget.DatePicker
+import android.widget.Toast
+import com.nextcloud.client.account.User
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.jobs.BackgroundJobManager
+import com.owncloud.android.R
+import com.owncloud.android.databinding.BackupFragmentBinding
+import com.owncloud.android.datamodel.ArbitraryDataProvider
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.operations.RefreshFolderOperation
+import com.owncloud.android.ui.activity.ContactsPreferenceActivity
+import com.owncloud.android.ui.activity.SettingsActivity
+import com.owncloud.android.ui.fragment.FileFragment
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.MimeTypeUtil
+import com.owncloud.android.utils.PermissionUtil
+import com.owncloud.android.utils.PermissionUtil.checkSelfPermission
+import com.owncloud.android.utils.theme.ThemeUtils
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import third_parties.daveKoeller.AlphanumComparator
+import java.util.Calendar
+import java.util.Collections
+import java.util.Date
+import javax.inject.Inject
+
+@Suppress("TooManyFunctions")
+class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
+ private lateinit var binding: BackupFragmentBinding
+
+ @JvmField
+ @Inject
+ var backgroundJobManager: BackgroundJobManager? = null
+
+ @JvmField
+ @Inject
+ var themeUtils: ThemeUtils? = null
+
+ @JvmField
+ @Inject
+ var arbitraryDataProvider: ArbitraryDataProvider? = null
+
+ @JvmField
+ @Inject
+ var viewThemeUtils: ViewThemeUtils? = null
+
+ private var selectedDate: Date? = null
+ private var calendarPickerOpen = false
+ private var datePickerDialog: DatePickerDialog? = null
+ private var contactsCheckedListener: CompoundButton.OnCheckedChangeListener? = null
+ private var calendarCheckedListener: CompoundButton.OnCheckedChangeListener? = null
+ private var user: User? = null
+ private var showSidebar = true
+
+ // flag to check if calendar backup should be shown and backup should be done or not
+ private var showCalendarBackup = true
+ private var isCalendarBackupEnabled: Boolean
+ get() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CALENDAR_BACKUP_ENABLED) } ?: false
+ private set(enabled) {
+ arbitraryDataProvider!!.storeOrUpdateKeyValue(
+ user!!.accountName,
+ PREFERENCE_CALENDAR_BACKUP_ENABLED,
+ enabled
+ )
+ }
+
+ private var isContactsBackupEnabled: Boolean
+ get() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CONTACTS_BACKUP_ENABLED) } ?: false
+ private set(enabled) {
+ arbitraryDataProvider!!.storeOrUpdateKeyValue(
+ user!!.accountName,
+ PREFERENCE_CONTACTS_BACKUP_ENABLED,
+ enabled
+ )
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ // use grey as fallback for elements where custom theming is not available
+ if (themeUtils?.themingEnabled(context) == true) {
+ requireContext().theme.applyStyle(R.style.FallbackThemingTheme, true)
+ }
+
+ binding = BackupFragmentBinding.inflate(inflater, container, false)
+ val view: View = binding.root
+
+ setHasOptionsMenu(true)
+
+ if (arguments != null) {
+ showSidebar = requireArguments().getBoolean(ARG_SHOW_SIDEBAR)
+ }
+
+ showCalendarBackup = requireContext().resources.getBoolean(R.bool.show_calendar_backup)
+
+ val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
+ user = contactsPreferenceActivity?.user?.orElseThrow { RuntimeException() }
+
+ setupSwitches(user)
+
+ setupCheckListeners()
+ setBackupNowButtonVisibility()
+
+ setOnClickListeners()
+
+ contactsPreferenceActivity?.let {
+ displayLastBackup(it)
+ applyUserColorToActionBar(it)
+ }
+
+ setupDates(savedInstanceState)
+ applyUserColor()
+
+ return view
+ }
+
+ private fun setupSwitches(user: User?) {
+ user?.let {
+ binding.dailyBackup.isChecked = arbitraryDataProvider?.getBooleanValue(
+ it,
+ ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP
+ ) ?: false
+ }
+
+ binding.contacts.isChecked = isContactsBackupEnabled && checkContactBackupPermission()
+ binding.calendar.isChecked = isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())
+ binding.calendar.visibility = if (showCalendarBackup) View.VISIBLE else View.GONE
+ }
+
+ private fun setupCheckListeners() {
+ binding.dailyBackup.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
+ if (checkAndAskForContactsReadPermission()) {
+ setAutomaticBackup(isChecked)
+ }
+ }
+
+ initContactsCheckedListener()
+ binding.contacts.setOnCheckedChangeListener(contactsCheckedListener)
+
+ initCalendarCheckedListener()
+ binding.calendar.setOnCheckedChangeListener(calendarCheckedListener)
+ }
+
+ private fun initContactsCheckedListener() {
+ contactsCheckedListener =
+ CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
+ if (isChecked) {
+ if (checkAndAskForContactsReadPermission()) {
+ isContactsBackupEnabled = true
+ }
+ } else {
+ isContactsBackupEnabled = false
+ }
+ setBackupNowButtonVisibility()
+ setAutomaticBackup(binding.dailyBackup.isChecked)
+ }
+ }
+
+ private fun initCalendarCheckedListener() {
+ calendarCheckedListener =
+ CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
+ if (isChecked) {
+ if (checkAndAskForCalendarReadPermission()) {
+ isCalendarBackupEnabled = true
+ }
+ } else {
+ isCalendarBackupEnabled = false
+ }
+ setBackupNowButtonVisibility()
+ setAutomaticBackup(binding.dailyBackup.isChecked)
+ }
+ }
+
+ private fun setBackupNowButtonVisibility() {
+ binding.backupNow.visibility =
+ if (binding.contacts.isChecked || binding.calendar.isChecked) View.VISIBLE else View.INVISIBLE
+ }
+
+ private fun setOnClickListeners() {
+ binding.backupNow.setOnClickListener { backupNow() }
+ binding.contactsDatepicker.setOnClickListener { openCleanDate() }
+ }
+
+ private fun displayLastBackup(contactsPreferenceActivity: ContactsPreferenceActivity) {
+ val lastBackupTimestamp = user?.let {
+ arbitraryDataProvider?.getLongValue(
+ it,
+ ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP
+ )
+ } ?: -1L
+
+ if (lastBackupTimestamp == -1L) {
+ binding.lastBackupWithDate.visibility = View.GONE
+ } else {
+ binding.lastBackupWithDate.text = String.format(
+ getString(R.string.last_backup),
+ DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp)
+ )
+ }
+ }
+
+ private fun applyUserColorToActionBar(contactsPreferenceActivity: ContactsPreferenceActivity) {
+ val actionBar = contactsPreferenceActivity.supportActionBar
+
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true)
+ viewThemeUtils?.files?.themeActionBar(
+ requireContext(),
+ actionBar,
+ if (showCalendarBackup) R.string.backup_title else R.string.contact_backup_title
+ )
+ }
+ }
+
+ private fun setupDates(savedInstanceState: Bundle?) {
+ if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) {
+ if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 && savedInstanceState.getInt(
+ KEY_CALENDAR_MONTH,
+ -1
+ ) != -1 && savedInstanceState.getInt(
+ KEY_CALENDAR_DAY, -1
+ ) != -1
+ ) {
+ val cal = Calendar.getInstance()
+ cal[Calendar.YEAR] = savedInstanceState.getInt(KEY_CALENDAR_YEAR)
+ cal[Calendar.MONTH] = savedInstanceState.getInt(KEY_CALENDAR_MONTH)
+ cal[Calendar.DAY_OF_MONTH] = savedInstanceState.getInt(KEY_CALENDAR_DAY)
+ selectedDate = cal.time
+ }
+ calendarPickerOpen = true
+ }
+ }
+
+ private fun applyUserColor() {
+ viewThemeUtils?.androidx?.colorSwitchCompat(binding.contacts)
+ viewThemeUtils?.androidx?.colorSwitchCompat(binding.calendar)
+ viewThemeUtils?.androidx?.colorSwitchCompat(binding.dailyBackup)
+
+ viewThemeUtils?.material?.colorMaterialButtonPrimaryFilled(binding.backupNow)
+ viewThemeUtils?.material?.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker)
+
+ viewThemeUtils?.platform?.colorTextView(binding.dataToBackUpTitle)
+ viewThemeUtils?.platform?.colorTextView(binding.backupSettingsTitle)
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ if (calendarPickerOpen) {
+ if (selectedDate != null) {
+ openDate(selectedDate)
+ } else {
+ openDate(null)
+ }
+ }
+
+ val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
+ if (contactsPreferenceActivity != null) {
+ val backupFolderPath = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
+ refreshBackupFolder(
+ backupFolderPath,
+ contactsPreferenceActivity.applicationContext,
+ contactsPreferenceActivity.storageManager
+ )
+ }
+ }
+
+ private fun refreshBackupFolder(
+ backupFolderPath: String,
+ context: Context,
+ storageManager: FileDataStorageManager
+ ) {
+ val task: AsyncTask =
+ @SuppressLint("StaticFieldLeak")
+ object : AsyncTask() {
+ @Deprecated("Deprecated in Java")
+ override fun doInBackground(vararg path: String): Boolean {
+ val folder = storageManager.getFileByPath(path[0])
+ return if (folder != null) {
+ val operation = RefreshFolderOperation(
+ folder,
+ System.currentTimeMillis(),
+ false,
+ false,
+ storageManager,
+ user,
+ context
+ )
+ val result = operation.execute(user, context)
+ result.isSuccess
+ } else {
+ java.lang.Boolean.FALSE
+ }
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun onPostExecute(result: Boolean) {
+ if (result) {
+ val backupFolder = storageManager.getFileByPath(backupFolderPath)
+ val backupFiles = storageManager
+ .getFolderContent(backupFolder, false)
+ Collections.sort(backupFiles, AlphanumComparator())
+ if (backupFiles == null || backupFiles.isEmpty()) {
+ binding.contactsDatepicker.visibility = View.INVISIBLE
+ } else {
+ binding.contactsDatepicker.visibility = View.VISIBLE
+ }
+ }
+ }
+ }
+
+ task.execute(backupFolderPath)
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
+
+ val retval: Boolean
+ when (item.itemId) {
+ android.R.id.home -> {
+ if (showSidebar) {
+ if (contactsPreferenceActivity!!.isDrawerOpen) {
+ contactsPreferenceActivity.closeDrawer()
+ } else {
+ contactsPreferenceActivity.openDrawer()
+ }
+ } else if (activity != null) {
+ requireActivity().finish()
+ } else {
+ val settingsIntent = Intent(context, SettingsActivity::class.java)
+ settingsIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ startActivity(settingsIntent)
+ }
+ retval = true
+ }
+
+ else -> retval = super.onOptionsItemSelected(item)
+ }
+ return retval
+ }
+
+ @Deprecated("Deprecated in Java")
+ @Suppress("NestedBlockDepth")
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) {
+ for (index in permissions.indices) {
+ if (Manifest.permission.READ_CONTACTS.equals(permissions[index], ignoreCase = true)) {
+ if (grantResults[index] >= 0) {
+ // if approved, exit for loop
+ isContactsBackupEnabled = true
+ break
+ }
+
+ // if not accepted, disable again
+ binding.contacts.setOnCheckedChangeListener(null)
+ binding.contacts.isChecked = false
+ binding.contacts.setOnCheckedChangeListener(contactsCheckedListener)
+ }
+ }
+ }
+ if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) {
+ var readGranted = false
+ var writeGranted = false
+ for (index in permissions.indices) {
+ if (Manifest.permission.WRITE_CALENDAR.equals(
+ permissions[index],
+ ignoreCase = true
+ ) && grantResults[index] >= 0
+ ) {
+ writeGranted = true
+ } else if (Manifest.permission.READ_CALENDAR.equals(
+ permissions[index],
+ ignoreCase = true
+ ) && grantResults[index] >= 0
+ ) {
+ readGranted = true
+ }
+ }
+ if (!readGranted || !writeGranted) {
+ // if not accepted, disable again
+ binding.calendar.setOnCheckedChangeListener(null)
+ binding.calendar.isChecked = false
+ binding.calendar.setOnCheckedChangeListener(calendarCheckedListener)
+ } else {
+ isCalendarBackupEnabled = true
+ }
+ }
+ setBackupNowButtonVisibility()
+ setAutomaticBackup(binding.dailyBackup.isChecked)
+ }
+
+ private fun backupNow() {
+ if (isContactsBackupEnabled && checkContactBackupPermission()) {
+ startContactsBackupJob()
+ }
+ if (showCalendarBackup && isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())) {
+ startCalendarBackupJob()
+ }
+ DisplayUtils.showSnackMessage(
+ requireView().findViewById(R.id.contacts_linear_layout),
+ R.string.contacts_preferences_backup_scheduled
+ )
+ }
+
+ private fun startContactsBackupJob() {
+ val activity = activity as ContactsPreferenceActivity?
+ if (activity != null) {
+ val optionalUser = activity.user
+ if (optionalUser.isPresent) {
+ backgroundJobManager!!.startImmediateContactsBackup(optionalUser.get())
+ }
+ }
+ }
+
+ private fun startCalendarBackupJob() {
+ val activity = activity as ContactsPreferenceActivity?
+ if (activity != null) {
+ val optionalUser = activity.user
+ if (optionalUser.isPresent) {
+ backgroundJobManager!!.startImmediateCalendarBackup(optionalUser.get())
+ }
+ }
+ }
+
+ private fun setAutomaticBackup(enabled: Boolean) {
+ val activity = activity as ContactsPreferenceActivity? ?: return
+ val optionalUser = activity.user
+ if (!optionalUser.isPresent) {
+ return
+ }
+ val user = optionalUser.get()
+ if (enabled) {
+ if (isContactsBackupEnabled) {
+ Log_OC.d(TAG, "Scheduling contacts backup job")
+ backgroundJobManager?.schedulePeriodicContactsBackup(user)
+ } else {
+ Log_OC.d(TAG, "Cancelling contacts backup job")
+ backgroundJobManager?.cancelPeriodicContactsBackup(user)
+ }
+ if (isCalendarBackupEnabled) {
+ Log_OC.d(TAG, "Scheduling calendar backup job")
+ backgroundJobManager?.schedulePeriodicCalendarBackup(user)
+ } else {
+ Log_OC.d(TAG, "Cancelling calendar backup job")
+ backgroundJobManager?.cancelPeriodicCalendarBackup(user)
+ }
+ } else {
+ Log_OC.d(TAG, "Cancelling all backup jobs")
+ backgroundJobManager?.cancelPeriodicContactsBackup(user)
+ backgroundJobManager?.cancelPeriodicCalendarBackup(user)
+ }
+ arbitraryDataProvider?.storeOrUpdateKeyValue(
+ user.accountName,
+ ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
+ enabled.toString()
+ )
+ }
+
+ private fun checkAndAskForContactsReadPermission(): Boolean {
+ val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
+
+ // check permissions
+ return if (checkSelfPermission(contactsPreferenceActivity!!, Manifest.permission.READ_CONTACTS)) {
+ true
+ } else {
+ // No explanation needed, request the permission.
+ requestPermissions(
+ arrayOf(Manifest.permission.READ_CONTACTS),
+ PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC
+ )
+ false
+ }
+ }
+
+ private fun checkAndAskForCalendarReadPermission(): Boolean {
+ val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
+
+ // check permissions
+ return if (contactsPreferenceActivity?.let { checkCalendarBackupPermission(it) } == true) {
+ true
+ } else {
+ // No explanation needed, request the permission.
+ requestPermissions(
+ arrayOf(
+ Manifest.permission.READ_CALENDAR,
+ Manifest.permission.WRITE_CALENDAR
+ ),
+ PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC
+ )
+ false
+ }
+ }
+
+ private fun checkCalendarBackupPermission(context: Context): Boolean {
+ return checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) && checkSelfPermission(
+ context, Manifest.permission.WRITE_CALENDAR
+ )
+ }
+
+ private fun checkContactBackupPermission(): Boolean {
+ return checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS)
+ }
+
+ private fun openCleanDate() {
+ if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) {
+ openDate(null)
+ }
+ }
+
+ private fun openDate(savedDate: Date?) {
+ val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
+ if (contactsPreferenceActivity == null) {
+ Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show()
+ return
+ }
+
+ val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
+ val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
+ val storageManager = contactsPreferenceActivity.storageManager
+ val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
+ val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
+
+ val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
+ backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
+ backupFiles.sortWith { o1: OCFile?, o2: OCFile? ->
+ if (o1 != null && o2 != null) {
+ o1.modificationTimestamp.compareTo(o2.modificationTimestamp)
+ } else {
+ -1
+ }
+ }
+
+ val cal = Calendar.getInstance()
+ val year: Int
+ val month: Int
+ val day: Int
+ if (savedDate == null) {
+ year = cal[Calendar.YEAR]
+ month = cal[Calendar.MONTH] + 1
+ day = cal[Calendar.DAY_OF_MONTH]
+ } else {
+ year = savedDate.year
+ month = savedDate.month
+ day = savedDate.day
+ }
+ if (backupFiles.size > 0 && backupFiles[backupFiles.size - 1] != null) {
+ datePickerDialog = DatePickerDialog(contactsPreferenceActivity, this, year, month, day)
+ datePickerDialog?.datePicker?.maxDate = backupFiles[backupFiles.size - 1]!!
+ .modificationTimestamp
+ datePickerDialog?.datePicker?.minDate = backupFiles[0]!!.modificationTimestamp
+ datePickerDialog?.setOnDismissListener { selectedDate = null }
+ datePickerDialog?.setTitle("")
+ datePickerDialog?.show()
+
+ viewThemeUtils?.platform?.colorTextButtons(
+ datePickerDialog!!.getButton(DatePickerDialog.BUTTON_NEGATIVE),
+ datePickerDialog!!.getButton(DatePickerDialog.BUTTON_POSITIVE)
+ )
+
+ // set background to transparent
+ datePickerDialog?.getButton(DatePickerDialog.BUTTON_NEGATIVE)?.setBackgroundColor(0x00000000)
+ datePickerDialog?.getButton(DatePickerDialog.BUTTON_POSITIVE)?.setBackgroundColor(0x00000000)
+ } else {
+ DisplayUtils.showSnackMessage(
+ requireView().findViewById(R.id.contacts_linear_layout),
+ R.string.contacts_preferences_something_strange_happened
+ )
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ datePickerDialog?.dismiss()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+
+ datePickerDialog?.let {
+ outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, it.isShowing)
+
+ if (it.isShowing) {
+ outState.putInt(KEY_CALENDAR_DAY, it.datePicker.dayOfMonth)
+ outState.putInt(KEY_CALENDAR_MONTH, it.datePicker.month)
+ outState.putInt(KEY_CALENDAR_YEAR, it.datePicker.year)
+ }
+ }
+ }
+
+ @Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "ComplexMethod", "LongMethod", "MagicNumber")
+ override fun onDateSet(view: DatePicker, year: Int, month: Int, dayOfMonth: Int) {
+ val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
+ if (contactsPreferenceActivity == null) {
+ Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show()
+ return
+ }
+
+ selectedDate = Date(year, month, dayOfMonth)
+ val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
+ val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
+ val storageManager = contactsPreferenceActivity.storageManager
+ val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
+ val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
+ val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
+ backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
+
+ // find file with modification with date and time between 00:00 and 23:59
+ // if more than one file exists, take oldest
+ val date = Calendar.getInstance()
+ date[year, month] = dayOfMonth
+
+ // start
+ date[Calendar.HOUR] = 0
+ date[Calendar.MINUTE] = 0
+ date[Calendar.SECOND] = 1
+ date[Calendar.MILLISECOND] = 0
+ date[Calendar.AM_PM] = Calendar.AM
+ val start = date.timeInMillis
+
+ // end
+ date[Calendar.HOUR] = 23
+ date[Calendar.MINUTE] = 59
+ date[Calendar.SECOND] = 59
+ val end = date.timeInMillis
+ var contactsBackupToRestore: OCFile? = null
+ val calendarBackupsToRestore: MutableList = ArrayList()
+ for (file in backupFiles) {
+ if (start < file.modificationTimestamp && end > file.modificationTimestamp) {
+ // contact
+ if (MimeTypeUtil.isVCard(file)) {
+ if (contactsBackupToRestore == null) {
+ contactsBackupToRestore = file
+ } else if (contactsBackupToRestore.modificationTimestamp < file.modificationTimestamp) {
+ contactsBackupToRestore = file
+ }
+ }
+
+ // calendars
+ if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) {
+ calendarBackupsToRestore.add(file)
+ }
+ }
+ }
+ val backupToRestore: MutableList = ArrayList()
+ if (contactsBackupToRestore != null) {
+ backupToRestore.add(contactsBackupToRestore)
+ }
+ backupToRestore.addAll(calendarBackupsToRestore)
+ if (backupToRestore.isEmpty()) {
+ DisplayUtils.showSnackMessage(
+ requireView().findViewById(R.id.contacts_linear_layout),
+ R.string.contacts_preferences_no_file_found
+ )
+ } else {
+ val user = contactsPreferenceActivity.user.orElseThrow { RuntimeException() }
+ val files: Array = arrayOfNulls(backupToRestore.size)
+
+ val contactListFragment = BackupListFragment.newInstance(files, user)
+
+ contactsPreferenceActivity.supportFragmentManager.beginTransaction()
+ .replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG)
+ .addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST)
+ .commit()
+ }
+ }
+
+ companion object {
+ val TAG = BackupFragment::class.java.simpleName
+ private const val ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR"
+ private const val KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN"
+ private const val KEY_CALENDAR_DAY = "CALENDAR_DAY"
+ private const val KEY_CALENDAR_MONTH = "CALENDAR_MONTH"
+ private const val KEY_CALENDAR_YEAR = "CALENDAR_YEAR"
+ const val PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED"
+ const val PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED"
+
+ @JvmStatic
+ fun create(showSidebar: Boolean): BackupFragment {
+ val fragment = BackupFragment()
+ val bundle = Bundle()
+ bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar)
+ fragment.arguments = bundle
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/res/layout/backup_fragment.xml b/app/src/main/res/layout/backup_fragment.xml
index d5b873b8878f..a8467ef4c1d5 100644
--- a/app/src/main/res/layout/backup_fragment.xml
+++ b/app/src/main/res/layout/backup_fragment.xml
@@ -17,8 +17,8 @@
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see .
-->
-
-
-
-
+ android:textSize="@dimen/two_line_primary_text_size" />
-
-
-
-
+ android:gravity="end"
+ android:orientation="horizontal">
+ android:theme="@style/Widget.Material3.Button.IconButton.Filled" />
diff --git a/app/src/main/res/layout/ssl_validator_layout.xml b/app/src/main/res/layout/ssl_validator_layout.xml
deleted file mode 100644
index 9b494d9d0468..000000000000
--- a/app/src/main/res/layout/ssl_validator_layout.xml
+++ /dev/null
@@ -1,443 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/storage_permission_dialog.xml b/app/src/main/res/layout/storage_permission_dialog.xml
deleted file mode 100644
index 236f0d3bed5b..000000000000
--- a/app/src/main/res/layout/storage_permission_dialog.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 6c9b9e9e037f..29cd9bfd1a82 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -307,7 +307,7 @@
Urruneko bide-izena
Transferitu
Deskargatu
- Kargatu
+ Igo
Gehitu edo igo
Huts egin du fitxategia deskarga-kudeatzailera pasatzean
Huts egin du fitxategia inprimatzean
@@ -342,7 +342,7 @@
Baimenak behar dira
Biltegiratze-baimenak
%1$shobeto dabil biltegia atzitzeko baimenekin. Fitxategi guztietarako sarbide osoa aukera dezakezu, edo argazki eta bideoak \"irakurtzeko soilik\" baimena eman.
- %1$s-k fitxategiak kudeatzeko baimenak behar ditu fitxategiak kargatzeko. Fitxategi guztietarako sarbide osoa aukera dezakezu, edo \"irakurtzeko soilik\" baimena argazki eta bideoentzat.
+ %1$s-k fitxategiak kudeatzeko baimenak behar ditu fitxategiak igotzeko. Fitxategi guztietarako sarbide osoa aukera dezakezu, edo \"irakurtzeko soilik\" baimena argazki eta bideoentzat.
Helburua egiaztatzen...
Garbitzen…
Datu-biltegiratze karpeta eguneratzen
@@ -424,7 +424,7 @@
%s mm
%s s
%1$skarpetan
- Existitzen diren fitxategiak ere kargatu
+ Existitzen diren fitxategiak ere igo
Igo bakarrik gailua kargatzean
/InstantUpload
URL baliogabea
@@ -433,7 +433,7 @@
Azken babeskopia: %1$s
Esteka
Esteka-izena
- Onartu kargatzea eta edizioa
+ Onartu igotzea eta edizioa
Edizioa
Fitxategia jaregitea (igotzeko soilik)
Ikustea soilik
@@ -522,7 +522,7 @@
Erakutsi zerbitzariak bidalitako push jakinarazpenak: Aipamenak iruzkinetan, urruneko partekatze berrien harrera, administratzaileak argitaratutako argitalpenak etab.
Push jakinarazpenak
Erakutsi igoeraren egoera
- Kargak
+ Igotzeak
Jakinarazpen ikonoa
Jakinarazpenik ez
Begiratu beranduago, mesedez.
@@ -556,13 +556,13 @@
2012/05/18 12:23 PM
gelditu
txandakatu
- Energia aurrezteko kontrola desgaituz gero, fitxategiak bateria baxu dagoenean kargatu litezke!
+ Energia aurrezteko kontrola desgaituz gero, fitxategiak bateria baxu dagoenean igo litezke!
ezabatua
jatorrizko karpetan mantenduko da
aplikazioaren karpetara mugitu da
Zer egin fitxategia dagoeneko existitzen bada?
Galdetu beti
- Saltatu karga
+ Saltatu igotzea
Gainidatzi urruneko bertsioa
Aldatu izena bertsio berriari
Zer egin fitxategia dagoeneko existitzen bada?
@@ -610,7 +610,7 @@
Erakutsi ezkutuko fitxategiak
Eskuratu iturburu-kodea
Datu-biltegiratze karpeta
- Kudeatu karpeten kargatze automatikoa
+ Kudeatu karpeten igotze automatikoa
Karpeta lokala
Urruneko karpeta
Gaia
@@ -799,7 +799,7 @@
Sinkronizazioak huts egin du, hasi saioa berriz
Fitxategien edukiak dagoeneko sinkronizaturik
%1$s karpetaren sinkronizazioa ezin izan da osatu
- 1.3.16 bertsiotik aurrera, gailu honetatik kargatzen diren fitxategiak %1$s karpeta lokalera kopiatzen dira, fitxategi bat hainbat konturekin sinkronizatzen denean datuak gal ez daitezen.\n\nAldaketa honen ondorioz, aplikazio honen aurreko bertsioekin kargatutako fitxategi guztiak %2$s karpetara kopiatu dira. Hala ere, kontua sinkronizatzean gertatutako akats batek eragiketa hori osatzea eragotzi du. Fitxategiak dauden bezala utz ditzakezu eta %3$s(r)ako esteka ezabatu, edo fitxategia(k) %1$s karpetara eraman eta %4$s(r)ako esteka mantendu.\n\nBehean zerrendatuta daude fitxategi lokala(k) eta estekatutako %5$s(e)ko urruneko fitxategia(k).
+ 1.3.16 bertsiotik aurrera, gailu honetatik kargatzen diren fitxategiak %1$s karpeta lokalera kopiatzen dira, fitxategi bat hainbat konturekin sinkronizatzen denean datuak gal ez daitezen.\n\nAldaketa honen ondorioz, aplikazio honen aurreko bertsioekin igotako fitxategi guztiak %2$s karpetara kopiatu dira. Hala ere, kontua sinkronizatzean gertatutako akats batek eragiketa hori osatzea eragotzi du. Fitxategiak dauden bezala utz ditzakezu eta %3$s(r)ako esteka ezabatu, edo fitxategia(k) %1$s karpetara eraman eta %4$s(r)ako esteka mantendu.\n\nBehean zerrendatuta daude fitxategi lokala(k) eta estekatutako %5$s(e)ko urruneko fitxategia(k).
Fitxategi lokal batzuk ahaztu dira
Fitxategiaren bertsio berriena ekartzen.
Aukeratu zer sinkronizatu
@@ -810,7 +810,7 @@
Fitxategiak
Ezarpenak botoia
Konfiguratu karpetak
- Berehalako karga erabat aldatu da. Konfiguratu berriro zure karga automatikoa menu nagusitik.\n\nGozatu karga automatiko berri eta hedatuaz.
+ Berehalako igotzeak guztiz aldatu dira. Konfiguratu berriro zure igotze automatikoa menu nagusitik.\n\nGozatu igotze automatiko berri eta hedatuaz.
Ez da multimedia karpetarik aurkitu
%1$s-(r)entzat
Mota
@@ -856,7 +856,7 @@
Kode-zati testu-fitxategia(.txt)
Sartu igoko den fitxategi-izena eta fitxategi mota
Igo fitxategiak
- Kargatu elementuaren ekintza botoia
+ Elementua igotzeko ekintza botoia
Ezabatu
Ezin dira fitxategiak igo
Edukiak igo edo auto-igotzea aktiba ezazu.
@@ -875,7 +875,7 @@
%1$s ez dago baimendua jasotako fitxategia irakurtzeko
Fitxategia ezin izan da aldi baterako karpetan kopiatu. Saiatu berriro bidaltzen.
Kargatzeko hautatutako fitxategia ez da aurkitu. Mesedez, egiaztatu fitxategia existitzen dela.
- Fitxategi hau ezin da kargatu
+ Fitxategi hau ezin da igo
Ez dago fitxategirik kargatzeko
Karpetaren izena
Aukeratu karga-karpeta
diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml
index 6681a4f4aac4..358e5a9d7b5e 100644
--- a/app/src/main/res/values-ja-rJP/strings.xml
+++ b/app/src/main/res/values-ja-rJP/strings.xml
@@ -30,9 +30,11 @@
アクティビティ
別のリンクを作成
新規公開共有リンクを追加
+ 新しいセキュアなファイルドロップを追加
%1$s に追加
高度な設定
再共有を許可
+ ダッシュボードから一つのウィジェットを表示
%s の中を検索
関連付けられたアカウントが見つかりません!
アクセスに失敗しました: %1$s
@@ -85,11 +87,14 @@
カレンダー
証明書の読み込みに問題がありました。
開発バージョンの変更履歴
+ 後で確認するか、再読込をしてください
チェックボックス
ローカルフォルダーを選択…
+ 場所を選択
リモートフォルダを選択…
テンプレートを選択してファイル名を入力してください。
保存するファイルを選択
+ ウィジェットを選択
通知の削除に失敗
メッセージを消去
ステータスメッセージの有効期限
@@ -149,6 +154,7 @@
ローカルファイル
両方のバージョンを選択した場合、ローカルファイルはファイル名に数字が追加されます。
サーバーファイル
+ 連絡先のバックアップ
連絡先リストのユーザーアイコン
許可がありません、インポートできません。
連絡先
@@ -193,13 +199,20 @@
重複をチェックしませんでした
このスマートフォンでは、ダイジェストアルゴリズムが利用できません。
ダイレクトリンクからのログインに失敗しました!
+ %1$s で %2$s へログインする
無効
閉じる
通知を閉じる
取り込み中
+ 複数の画像
+ PDFファイル
+ エクスポートする種類を選択
+ PDFの生成に失敗しました
+ PDFを生成中...
完了
消去しない
ローカルファイルが作成できません
+ ローカルファイルに対して無効なファイル名
最新の開発バージョンをダウンロード
%1$sをダウンロードできませんでした
ダウンロード失敗、要 再ログイン
@@ -229,10 +242,14 @@
%2$s 中%1$s が使われています。
%1$s使用中
自動アップロード
+ E2E暗号化が未設定
+ インターネット接続なしには不可能です
さらに表示
ノート
トーク
+ もっと Nextcloud アプリを見る
Nextcloud ノート
+ Nextcloud Talk
暗号化設定
暗号化を設定する
復号化中…
@@ -262,7 +279,9 @@
問題を報告しますか? (GitHubのアカウントが必要です)
ファイルの取得中にエラーが発生しました
テンプレートの取得中にエラーが発生しました
+ 暗号化設定ダイアログの表示エラー
カメラ起動エラー
+ 文書スキャンの開始エラー
アカウント
ジョブの名前
進捗状況
@@ -293,6 +312,7 @@
UIの更新に失敗しました
お気に入りに追加
お気に入り
+ ファイル名が既に存在します
削除
ファイルのアクティビティ取得エラー
詳細のロードに失敗しました
@@ -358,6 +378,7 @@
ファイル名
あなたのデータをセキュアなままコントロールしましょう
セキュアなコラボレーションとファイル交換
+ 使いやすいWebメールやカレンダーや & 連絡先
画面共有やオンラインミーティングやウェブ会議
フォルダーはすでに存在します
作成
@@ -383,6 +404,18 @@
パスワード
サーバーが利用できません
自分のサーバーをホストする
+ ダッシュボードウィジェットのアイコン
+ 編集された
+ 左右反転
+ 上下反転
+ 反時計回りに回す
+ 時計回りに回す
+ 画像の編集が不可能です
+ ファイルの詳細
+ ISO %s
+ %s MP
+ %s mm
+ %s 秒
フォルダー%1$sの中で
既存のファイルもアップロード
充電中のみアップロード
@@ -403,12 +436,14 @@
ローカルファイルシステムにファイルが見つかりません
%1$s/%2$s
これ以上フォルダーがありません。
+ フォルダーの配置
有効期限: %1$s
ファイルをロック
%1$sによりロック
%1$sアプリによりロック
%1$s アンドロイドアプリログ
ログを送信するためのアプリが見つかりません。メールクライアントをインストールしてください。
+ %1$sとしてログインしました
ログイン
%1$sをWeb画面でブラウザーで開くときのURL
ログを消去
@@ -459,6 +494,8 @@
リンクを処理するアプリがありません
カレンダーがありません
メールアドレスの利用可能なアプリはありません
+ アイテムがありません
+ マップを処理するアプリケーションがありません
利用できるアカウントは1つだけです
PDFを処理するアプリケーションがありません
選択されたファイルの送信で利用可能なアプリがありません
@@ -497,11 +534,13 @@
パスコードを削除しました
パスコードを保存しました
パスコードが正しくありません
+ パスワード保護されたPDFファイルを開くことは出来ません。外部ビューワを使ってください
ズームするにはページをタップ
許可
拒否
ファイルをダウンロードとアップロードする追加の権限が必要です。
画像を設定するアプリが見つかりませんでした
+ ホームスクリーンにピン留めする
389 KB
placeholder.txt
12:23:45
@@ -531,6 +570,7 @@
もっと見る
カレンダーと連絡先を毎日バックアップ
連絡先のデイリーバックアップ
+ End-to-end 暗号化を設定中!
E2Eニーモニック
ニーモニックを表示するには、デバイスクレデンシャルを有効にしてください。
メディアのスキャン結果通知を表示する
@@ -540,7 +580,9 @@
インプリント
元のファイルの扱い…
元のファイルになります…
+ 日付を基にしたサブフォルダーに保存
サブフォルダーを利用
+ このクライアントに End-to-End 暗号化を追加
ライセンス
アプリパスコード
デバイスの資格情報が有効です
@@ -551,6 +593,7 @@
パスコード
アカウント管理
友達にすすめる
+ end-to-end 暗号化を設定
隠しファイルを表示
ソースコードを入手
データ保存フォルダー
@@ -574,6 +617,7 @@
デバイスで %1$s をお試しください
あなたのデバイスで %1$s を使用してください。\nダウンロードはこちらです: %2$s
%1$s または %2$s
+ コンテンツを更新
再読み込み
(リモート)
ファイルが見つかりません!
@@ -594,6 +638,7 @@
ファイルを取得中...
ドキュメントのロードに失敗しました。
QRコードを用いてログイン
+ ページをスキャン
あなたのデータを保護
自己ホスト型の生産性
閲覧と共有
@@ -608,6 +653,7 @@
DAVx5で同期
検索結果の取得中にエラーが発生しました
すべて選択
+ メディア用のフォルダーの設定
テンプレートを選択してください
テンプレートを選択する
送信
@@ -643,6 +689,7 @@
パスワード保護
編集可能
ファイルを転送
+ セキュアなファイルドロップ
閲覧のみ
共有権限
%1$s (リモート)
@@ -662,6 +709,8 @@
リンク経由で共有
%1$sと共有中
共有を追加できませんでした
+ 写真を表示
+ 動画を表示
他のサービスでサインアップ
%1$s があなたのNextcloudアカウント %2$s にアクセスできるようにしますか?
ソート
@@ -716,9 +765,13 @@
内部ストリーミングは不可能
代わりにメディアをダウンロードするか、外部アプリを使用してください。
ストリクトモード:HTTP接続が許可されていません!
+ 念/月/日
+ 年/月
年
\"%1$s\" があなたに共有されました
%1$s は \"%2$s\" をあなたと共有しました
+ 写真のみ
+ 動画のみ
提案
競合が見つかりました
フォルダー %1$s はもう存在しません
@@ -752,6 +805,7 @@
サムネイル
既存ファイルのサムネイル
新規ファイルのサムネイル
+ ローディング中 期待した時間より長くかかっています
今日
ゴミ箱
削除されたファイルはありません
@@ -866,6 +920,7 @@
スキップ
%1$sの新機能
あなたのステータスは?
+ ウィジェットは %1$s 25 以上でのみ利用可能です
利用できません
メールを送信
データ保存フォルダーが存在しません!
@@ -888,6 +943,18 @@
- 重複する応募が%d件見つかりました。
+
+ - エクスポートされた %d ファイル
+
+
+ - %dファイルのエクスポートに失敗しました
+
+
+ - エラーのため、%d ファイルはエクスポートされ、他のファイルはスキップされました
+
+
+ - %dファイルがエクスポートされます。詳細は通知を確認してください。
+
- %1$d フォルダ
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 2cb934c28dc6..d1bec04cd0f9 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -420,7 +420,7 @@
마지막 백업: %1$s
링크
링크 이름
- 업로드 및 편집 허용
+ 업로드와 수정 허용
글 수정
파일 보관소 (업로드만 허용)
읽기 전용
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index c9d548abe2db..1c51ee591b09 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -125,7 +125,7 @@
Mudar para a conta
Sim
Teste a versão de desenvolvimento
- Inclui todas as futuras e mais recentes funcionalidades. Falhas/erros podem ocorrer, se e quando tal acontecer, por favor, reporte as suas descobertas.
+ Isto inclui todas as futuras e mais recentes funcionalidades. Podem ocorrer falhas/erros, se e quando tal acontecer, por favor, reporte as suas descobertas.
fórum
Ajude outros em
Reveja, emende e escreva o código, consulte %1$s para detalhes.
@@ -137,12 +137,12 @@
Obter versão candidata a lançamento a partir da aplicação F-Droid
Obter versão candidata a lançamento a partir da loja Google Play
Versão candidata a lançamento
- A versão candidata a lançamento (RC) é um snapshot da próxima versão e é espectável que seja estável. Através do teste da sua configuração individual pode ajudar-nos a assegurá-lo. Para testar, inscreva-se na loja Play ou verifique a secção \"Version\" do F-Droid.
+ A versão candidata de lançamento (RC) é um \'\'snapshot\'\' da próxima versão e é expetável que seja estável. Se testar a sua configuração individual pode ajudar-nos a assegurá-lo. Para testar, inscreva-se na loja Play ou consulte a secção \"Version\" do F-Droid.
Encontrou um erro? Ocorrências estranhas?
Ajude-nos, testando
Reportar um problema no Github
Configure
- Remove localmente a criptografia
+ Remove encriptação local
Deseja realmente apagar %1$s?
Quer realmente remover os itens seleccionados?
Deseja realmente apagar %1$s e o seu conteúdo?
@@ -152,7 +152,7 @@
Ficheiro em conflito %1$s
Ficheiro local
Se selecionou ambas as versões, o ficheiro local terá um número acrescentado ao seu nome.
- Arquivo do servidor
+ Ficheiro do servidor
Cópia de segurança dos contactos
Ícone de utilizador de lista de contactos
Sem permissão concedida, nada para importar.
@@ -190,7 +190,7 @@
Apagar entradas
Eliminar hiperligação
Desseleccionado tudo
- Nome do arquivo de destino
+ Nome do ficheiro de destino
Nova versão disponível
Nenhuma informação disponível.
Nenhuma nova versão disponível
@@ -202,12 +202,16 @@
Desativar
Dispensar
Dispensar notificação
- Apresenta a sua frase-chave de 12 palavras
+ Exibe a sua frase-chave de 12 palavras
Não incomodar
Múltiplas imagens
+ Ficheiro PDF
+ Escolher tipo de exportação
+ Geração de PDF falhou
+ A gerar PDF...
Concluído
Não limpar
- Não é possível criar ficheiro local
+ Não é possível criar o ficheiro local
Transferir a última versão de desenvolvimento
Não foi possível transferir %1$s
A transferência falhou, inicie novamente a sessão
@@ -238,7 +242,11 @@
%1$s utilizado
Carregamento automático
Mais
+ Notas
Falar
+ Mais Aplicações Nextcloud
+ Nextcloud Notes
+ Nextcloud Talk
Definir como encriptado
Definir encriptação
Decryption…
@@ -255,15 +263,19 @@
Definir encriptação
Não foi possível guardar as chaves, por favor, tente novamente.
Erro ao encriptar. Palavra-passe errada?
+ Inserir nome do ficheiro de destino
Por favor, introduza um nome para o ficheiro
Não foi possível copiar %1$s para a pasta local %2$s
Erro crítico: impossível concluir a operação
Erro ao escolher a data
Erro ao comentar ficheiro
%1$s crachou
+ Erro ao criar o ficheiro com o modelo
+ Erro ao mostrar as ações do ficheiro
Relatório
Erro ao obter o ficheiro
Erro ao obter modelos
+ Erro ao mostrar a janela da configuração de encriptação!
Erro ao iniciar câmara
Contas
Nome do Trabalho
@@ -289,11 +301,12 @@
Enviar
Adicionar ou enviar
Falhou a passagem do ficheiro ao gestor de transferências
- Falhou a impressão do ficheiro
+ Não foi possível imprimir o ficheiro
Falha ao iniciar o editor
Falha ao atualizar a IU
Adicionar aos favoritos
Favorito
+ O nome do ficheiro já existe
Apagar
Erro ao obter as atividades para o ficheiro
Falha ao carregar detalhes
@@ -307,10 +320,10 @@
Sem resultados nesta pasta
Sem resultados
Não está aqui nada. Pode adicionar uma pasta.
- Ficheiros e pastas que foram descarregados aparecem aqui.
+ Os ficheiros e as pastas que foram transferidos serão mostrados aqui.
Não foi encontrado nenhum arquivo modificado nos últimos 7 dias
Poderá estar numa pasta diferente?
- Ficheiros e pastas que partilhou aparecem aqui.
+ Os ficheiros e as pastas que partilhou serão mostrados aqui.
Ainda sem partilhas
Nenhum resultado encontrado para a sua consulta
pasta
@@ -318,6 +331,7 @@
Nenhuma aplicação para usar este tipo de ficheiro.
há segundos
Permissões necessárias
+ Permissões de armazenamento
%1$s precisa de permissões de gestão de ficheiros para carregar ficheiros. Pode escolher acesso total a todos os ficheiros ou acesso só de leitura a fotografias e vídeos.
Verificando destino…
Limpando…
@@ -383,11 +397,11 @@
Servidor não disponível
Hospede o seu próprio servidor
Ícone para lista vazia
- Ícone do widget do painel de controlo
- Ícone do widget da entrada
+ Ícone do \'\'widget\'\' do painel de controlo
+ Ícone do \'\'widget\'\' da entrada
editado
- Virar horizontalmente
- Virar verticalmente
+ Inverter horizontalmente
+ Inverter verticalmente
Rodar no sentido anti-horário
Rodar no sentido horário
Não é possível editar a imagem.
@@ -404,7 +418,7 @@
/Envio Instantâneo
URL inválido
Invisível
- Nome não pode ficar em branco
+ O nome não pode ficar em branco
Última cópia de segurança: %1$s
Hiperligação
Nome da hiperligação
@@ -418,9 +432,14 @@
Ficheiros não encontrados no sistema de ficheiros local
%1$s/%2$s
Não existem mais pastas.
+ Localizar pasta
+ Expira: %1$s
Bloquear ficheiro
+ Bloqueado por %1$s
+ Bloqueado por aplicação %1$s
%1$s registos de aplicação Android
Não foi encontrada nenhuma aplicação para o envio de registos. Por favor, Instale um cliente de correio eletrónico.
+ Autenticado como %1$s
Iniciar Sessão
A hiperligação para a sua interface da Web %1$s quando a abre no seu navegador.
Eliminar registos
@@ -467,10 +486,14 @@
vídeo
Nova notificação
Uma nova versão foi criada
+ Sem ações para este utilizador
Nenhuma aplicação disponível para lidar com links
O calendário não existe
+ Sem itens
Só é permitida uma conta.
Nenhuma aplicação disponível para lidar com PDF
+ Nenhuma aplicação disponível para enviar os ficheiros selecionados
+ Por favor, selecione pelo menos uma permissão para partilhar.
Enviar
Não foi possível enviar a nota
Ícone de nota
@@ -509,6 +532,8 @@
Negar
Permissões adicionais são necessárias para enviar e transferir ficheiros.
Nenhuma aplicação encontrada para definir a imagem
+ Afixar no ecrã Início
+ Abrir %1$s
389 KB
placeholder.txt
12:23:45
@@ -548,7 +573,7 @@
Informação
O ficheiro original será…
O ficheiro original será…
- Armazenar em subpastas com base na data
+ Guardar nas subpastas com base na data
Usar Subpastas
Opções de subpasta
Licença
@@ -561,6 +586,7 @@
Código
Gerir contas
Recomendar a um amigo
+ Remover encriptação localmente
Configurar a encriptação ponta-a-ponta
Mostrar alternador de aplicações
Sugestões de aplicações Nextcloud no cabeçalho da navegação
@@ -639,6 +665,7 @@
Defina a mensagem de estado
Durante a configuração da encriptação ponta-a-ponta, receberá uma mnemónica aleatória de 12 palavras, de que necessitará para abrir os seus ficheiros noutros dispositivos. Ela só será guardada neste dispositivo e pode ser mostrada novamente neste ecrã. Por favor, anote-a num local seguro!
Partilhar
+ Hiperligação de Partilhar e Copiar
Partilha
%1$s
Expira %1$s
@@ -679,6 +706,8 @@
Partilhado via hiperligação
Partilhado consigo por %1$s
Adição de destinatário da partilha falhou
+ Mostrar fotografias
+ Mostrar vídeos
Registar com fornecedor
Permitir que %1$s aceda à sua conta Nextcloud %2$s?
Ordenar por
@@ -731,9 +760,13 @@
Transmita com…
Transmissão interna não é possível
Em vez disso, por favor, transfira mediateca ou utilize uma aplicação externa.
+ Ano/Mês/Dia
+ Ano/Mês
+ Ano
\"%1$s\" foi partilhado consigo
%1$s partilhou \"%2$s\" consigo
- Fotos & videos
+ Apenas fotografias
+ Fotografias e vídeos
Apenas vídeos
Sugerir
Encontrados conflitos
@@ -784,12 +817,12 @@ Aproveite o novo e melhorado envio automático.
Encriptação não definida
Remover dos favoritos
Ocorreu um erro enquanto tentava remover a partilha deste ficheiro ou pasta.
- Impossível eliminar a partilha. Verifique se o ficheiro existe.
+ Não é possível remover a partilha. Por favor, verifique se o ficheiro existe.
para cancelar a partilha deste ficheiro
Cancelamento da partilha falhou
Acesso através de domínio não confiável. Por favor, verifique a documentação para mais informações.
- Erro ao tentar modificar a partilha.
- Impossível modificar. Verifique se o ficheiro existe.
+ Ocorreu um erro enquanto tentava atualizar a partilha.
+ Não é possível atualizar. Por favor, verifique se o ficheiro existe.
para atualizar esta partilha
Actualização da partilha falhou
Não é possível criar ficheiro local
@@ -828,7 +861,7 @@ Aproveite o novo e melhorado envio automático.
Escolha uma pasta para envio
Não foi possível enviar %1$s
O envio falhou, inicie novamente a sessão
- Exite um conflito com o ficheiro enviado
+ Conflito de envio do ficheiro
Escolha qual das versões manter %1$s
Envio falhou
Opção de envio:
@@ -900,6 +933,26 @@ Aproveite o novo e melhorado envio automático.
- Falha ao copiar %1$d ficheiros da pasta %2$s para
- Falha ao copiar %1$d ficheiros da pasta %2$s para
+
+ - Processada %d entrada.
+ - Processadas %dentradas.
+ - Processadas %d entradas.
+
+
+ - Encontrada %d entrada duplicada.
+ - Encontradas %d entradas duplicadas.
+ - Encontradas %d entradas duplicadas.
+
+
+ - Exportado %d ficheiro
+ - Exportados %d ficheiros
+ - Exportados %d ficheiros
+
+
+ - Falhou exportação de %d ficheiro
+ - Falhou exportação de %d ficheiros
+ - Falhou exportação de %d ficheiros
+
- %1$d pasta
- %1$d pastas
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 17ae890a4b19..c592f9757130 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -653,6 +653,7 @@
Đặt mật khẩu
Mật khẩu được bảo vệ
Có thể chỉnh sửa
+ Thả file
Chỉ xem
Quyền kho
%1$s (từ xa)
diff --git a/app/src/main/res/values/dims.xml b/app/src/main/res/values/dims.xml
index 3805116abcbb..6b3fe89a1e9c 100644
--- a/app/src/main/res/values/dims.xml
+++ b/app/src/main/res/values/dims.xml
@@ -79,7 +79,6 @@
20dp
12sp
20dp
- 180dp
60dp
12sp
12dp
@@ -134,6 +133,8 @@
16sp
18sp
24dp
+ 160dp
+
4
12dp
50dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6b758fa380d7..00883583ec32 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -799,8 +799,6 @@
Deny
Allow
Note to recipient
- Send
- Send note to recipient
Could not send note
Note
No app available to handle links
diff --git a/scripts/analysis/analysis-wrapper.sh b/scripts/analysis/analysis-wrapper.sh
index 8770f1d2401b..81e04ec2e159 100755
--- a/scripts/analysis/analysis-wrapper.sh
+++ b/scripts/analysis/analysis-wrapper.sh
@@ -128,7 +128,7 @@ else
# check for NotNull
if [[ $(grep org.jetbrains.annotations app/src/main/* -irl | wc -l) -gt 0 ]] ; then
- notNull="org.jetbrains.annotations.NotNull is used. Please use androidx.annotation.NonNull instead.
"
+ notNull="org.jetbrains.annotations.* is used. Please use androidx.annotation.* instead.
"
fi
bodyContent="$codacyResult $lintResult $spotbugsResult $checkLibraryMessage $lintMessage $spotbugsMessage $gplayLimitation $notNull"
diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt
index 31824795b1a7..2c0dfa2b0d8a 100644
--- a/scripts/analysis/lint-results.txt
+++ b/scripts/analysis/lint-results.txt
@@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
- Lint Report: 80 warnings
+ Lint Report: 79 warnings