Selaa lähdekoodia

Merge master

Signed-off-by: alperozturk <alper_ozturk@proton.me>
alperozturk 1 vuosi sitten
vanhempi
commit
e3d0ce39b4
100 muutettua tiedostoa jossa 2020 lisäystä ja 2167 poistoa
  1. 1 1
      .devcontainer/devcontainer.json
  2. 4 2
      .drone.yml
  3. 1 1
      .github/workflows/assembleFlavors.yml
  4. 1 1
      .github/workflows/check.yml
  5. 2 2
      .github/workflows/codeql.yml
  6. 3 3
      .github/workflows/command-rebase.yml
  7. 1 1
      .github/workflows/detectWrongSettings.yml
  8. 1 1
      .github/workflows/qa.yml
  9. 2 2
      .github/workflows/scorecard.yml
  10. 1 1
      .github/workflows/screenShotTest.yml
  11. 1 1
      .github/workflows/unit-tests.yml
  12. 22 1
      CHANGELOG.md
  13. 1 1
      CONTRIBUTING.md
  14. 1 1
      SETUP.md
  15. BIN
      app/screenshots/gplay/debug/com.nextcloud.client.SyncedFoldersActivityIT_testSyncedFolderDialog.png
  16. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepBoth.png
  17. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepExisting.png
  18. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepNew.png
  19. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_screenshotTextFiles.png
  20. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png
  21. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.FolderPickerActivityIT_open.png
  22. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithOneAction.png
  23. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithThreeAction.png
  24. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithThreeActionRTL.png
  25. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithTwoAction.png
  26. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testEnforcedPasswordDialog.png
  27. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testFileActionsBottomSheet.png
  28. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testLoadingDialog.png
  29. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testOptionalPasswordDialog.png
  30. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFileDialog.png
  31. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFilesDialog.png
  32. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFolderDialog.png
  33. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFoldersDialog.png
  34. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialogDifferentTypes_Screenshot.png
  35. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialog_Screenshot.png
  36. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendShareDialogTest_showDialog.png
  37. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SetupEncryptionDialogFragmentIT_error.png
  38. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SetupEncryptionDialogFragmentIT_showMnemonic.png
  39. 10 5
      app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt
  40. 19 0
      app/src/androidTest/java/com/owncloud/android/AbstractIT.java
  41. 9 5
      app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java
  42. 1 1
      app/src/androidTest/java/com/owncloud/android/ui/activity/FolderPickerActivityIT.java
  43. 34 1
      app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java
  44. 1 0
      app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.java
  45. 1 1
      app/src/androidTest/java/com/owncloud/android/ui/trashbin/TrashbinLocalRepository.kt
  46. 0 73
      app/src/main/java/com/nextcloud/client/di/ActivityInjector.java
  47. 62 0
      app/src/main/java/com/nextcloud/client/di/ActivityInjector.kt
  48. 4 0
      app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
  49. 15 19
      app/src/main/java/com/nextcloud/client/di/FragmentInjector.kt
  50. 13 17
      app/src/main/java/com/nextcloud/client/integrations/deck/DeckApi.kt
  51. 0 96
      app/src/main/java/com/nextcloud/client/integrations/deck/DeckApiImpl.java
  52. 85 0
      app/src/main/java/com/nextcloud/client/integrations/deck/DeckApiImpl.kt
  53. 19 19
      app/src/main/java/com/nextcloud/client/jobs/FilesExportWork.kt
  54. 9 2
      app/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt
  55. 1 1
      app/src/main/java/com/nextcloud/client/jobs/NotificationWork.kt
  56. 0 232
      app/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.java
  57. 279 0
      app/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.kt
  58. 0 159
      app/src/main/java/com/nextcloud/client/onboarding/WhatsNewActivity.java
  59. 169 0
      app/src/main/java/com/nextcloud/client/onboarding/WhatsNewActivity.kt
  60. 10 21
      app/src/main/java/com/nextcloud/ui/SquareLoaderImageView.kt
  61. 2 4
      app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt
  62. 8 4
      app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt
  63. 44 0
      app/src/main/java/com/nextcloud/utils/extensions/BundleExtensions.kt
  64. 39 31
      app/src/main/java/com/owncloud/android/MainApp.java
  65. 4 0
      app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
  66. 0 40
      app/src/main/java/com/owncloud/android/authentication/DeepLinkLoginActivity.java
  67. 37 0
      app/src/main/java/com/owncloud/android/authentication/DeepLinkLoginActivity.kt
  68. 19 17
      app/src/main/java/com/owncloud/android/datamodel/MediaFolder.kt
  69. 18 27
      app/src/main/java/com/owncloud/android/datamodel/MediaFolderType.kt
  70. 0 51
      app/src/main/java/com/owncloud/android/datamodel/MediaFoldersModel.java
  71. 24 0
      app/src/main/java/com/owncloud/android/datamodel/MediaFoldersModel.kt
  72. 7 6
      app/src/main/java/com/owncloud/android/datamodel/OCFile.java
  73. 1 1
      app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
  74. 3 11
      app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
  75. 7 0
      app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
  76. 9 57
      app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  77. 54 45
      app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
  78. 1 1
      app/src/main/java/com/owncloud/android/ui/activity/PassCodeActivity.kt
  79. 10 2
      app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
  80. 1 3
      app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
  81. 19 19
      app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java
  82. 0 89
      app/src/main/java/com/owncloud/android/ui/adapter/StoragePathAdapter.java
  83. 80 0
      app/src/main/java/com/owncloud/android/ui/adapter/StoragePathAdapter.kt
  84. 1 1
      app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java
  85. 1 2
      app/src/main/java/com/owncloud/android/ui/components/PassCodeEditText.kt
  86. 0 105
      app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.java
  87. 105 0
      app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.kt
  88. 16 14
      app/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.kt
  89. 0 173
      app/src/main/java/com/owncloud/android/ui/dialog/ConfirmationDialogFragment.java
  90. 161 0
      app/src/main/java/com/owncloud/android/ui/dialog/ConfirmationDialogFragment.kt
  91. 9 5
      app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java
  92. 0 230
      app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java
  93. 203 0
      app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt
  94. 0 196
      app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
  95. 183 0
      app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.kt
  96. 0 105
      app/src/main/java/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java
  97. 92 0
      app/src/main/java/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.kt
  98. 0 88
      app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java
  99. 79 0
      app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.kt
  100. 0 169
      app/src/main/java/com/owncloud/android/ui/dialog/LocalStoragePathPickerDialogFragment.java

+ 1 - 1
.devcontainer/devcontainer.json

@@ -1,4 +1,4 @@
 {
 	"name": "NextcloudAndroid",
-	"dockerFile": "Dockerfile",
+	"dockerFile": "Dockerfile"
 }

+ 4 - 2
.drone.yml

@@ -32,7 +32,7 @@ services:
     image: ghcr.io/nextcloud/continuous-integration-shallow-server:latest # also change in updateScreenshots.sh
     environment:
       EVAL: true
-      SERVER_VERSION: 'stable25'
+      SERVER_VERSION: 'stable27'
     commands:
       - BRANCH="$SERVER_VERSION" /usr/local/bin/initnc.sh
       - echo 127.0.0.1 server >> /etc/hosts
@@ -171,4 +171,6 @@ name: GIT_TOKEN
 data: XIoa9IYq+xQ+N5iln8dlpWv0jV6ROr7HuE24ioUr4uQ8m8SjyH0yognWYLYLqnbTKrFWlFZiEMQTH/sZiWjRFvV1iL0=
 ---
 kind: signature
-hmac: aec1cabcb7b4f98f1be1d5b7a052a629ce3193e19a3692dd52ada48b6e72a1c9
+hmac: b78dcc477ff74ccbd7877df011090783847f8b5215a994be6597408bd735b524
+
+...

+ 1 - 1
.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:

+ 1 - 1
.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:

+ 2 - 2
.github/workflows/codeql.yml

@@ -32,7 +32,7 @@ jobs:
         with:
           swap-size-gb: 10
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
+        uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
         with:
           languages: ${{ matrix.language }}
       - name: Set up JDK 17
@@ -46,4 +46,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@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5

+ 3 - 3
.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 }}

+ 1 - 1
.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:

+ 1 - 1
.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

+ 2 - 2
.github/workflows/scorecard.yml

@@ -29,7 +29,7 @@ jobs:
           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@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
         with:
           sarif_file: results.sarif

+ 1 - 1
.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

+ 1 - 1
.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:

+ 22 - 1
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

+ 1 - 1
CONTRIBUTING.md

@@ -297,7 +297,7 @@ We use [shot](https://github.com/Karumi/Shot) for taking screenshots and compare
      Screenshot.snapActivity(activity).record();
     ```
 
-    - best practise is to first create test with emulator too see behaviour and then create screenshots
+    - best practice is to first create test with emulator too see behaviour and then create screenshots
 
 ## File naming
 

+ 1 - 1
SETUP.md

@@ -51,7 +51,7 @@ To set up the project in Android Studio follow the next steps:
 
 * Open Android Studio and select 'Import Project (Eclipse ADT, Gradle, etc)'. Browse through your file system to the folder 'android' where the project is located. Android Studio will then create the '.iml' files it needs. If you ever close the project but the files are still there, you just select 'Open Project…'. The file chooser will show an Android face as the folder icon, which you can select to reopen the project.
 * Android Studio will try to build the project directly after importing it. To build it manually, follow the menu path 'Build'/'Make Project', or just click the 'Play' button in the toolbar to build and run it in a mobile device or an emulator. The resulting APK file will be saved in the 'build/outputs/apk/' subdirectory in the project folder.
-* Setup Android Studio editor configurtation for the project: ```Settings``` → ```Editor``` → ```Code Style``` → ```Scheme: Project``` and ```Enable EditorConfig support```
+* Setup Android Studio editor configuration for the project: ```Settings``` → ```Editor``` → ```Code Style``` → ```Scheme: Project``` and ```Enable EditorConfig support```
 
 
 ### 3. Working in a terminal with Gradle:

BIN
app/screenshots/gplay/debug/com.nextcloud.client.SyncedFoldersActivityIT_testSyncedFolderDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepBoth.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepExisting.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_keepNew.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ConflictsResolveActivityIT_screenshotTextFiles.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.FolderPickerActivityIT_open.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithOneAction.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithThreeAction.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithThreeActionRTL.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testConfirmationDialogWithTwoAction.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testEnforcedPasswordDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testFileActionsBottomSheet.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testLoadingDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testOptionalPasswordDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFileDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFilesDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFolderDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testRemoveFoldersDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialogDifferentTypes_Screenshot.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialog_Screenshot.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendShareDialogTest_showDialog.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SetupEncryptionDialogFragmentIT_error.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SetupEncryptionDialogFragmentIT_showMnemonic.png


+ 10 - 5
app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt

@@ -25,6 +25,8 @@ import android.app.Activity
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.Espresso.onView
 import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
+import androidx.test.espresso.action.ViewActions.scrollTo
 import androidx.test.espresso.assertion.ViewAssertions.matches
 import androidx.test.espresso.contrib.DrawerActions
 import androidx.test.espresso.contrib.NavigationViewActions
@@ -238,12 +240,15 @@ class FileDisplayActivityIT : AbstractOnServerIT() {
         onView(withId(R.id.sort_button)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
 
         // browse into folder
-        onView(withId(R.id.list_root)).perform(
-            RecyclerViewActions.actionOnItemAtPosition<OCFileListItemViewHolder>(
-                0,
-                click()
+        onView(withId(R.id.list_root))
+            .perform(scrollTo())
+            .perform(closeSoftKeyboard())
+            .perform(
+                RecyclerViewActions.actionOnItemAtPosition<OCFileListItemViewHolder>(
+                    0,
+                    click()
+                )
             )
-        )
         shortSleep()
         checkToolbarTitle(topFolder)
         // sort button should now be visible

+ 19 - 0
app/src/androidTest/java/com/owncloud/android/AbstractIT.java

@@ -7,6 +7,8 @@ import android.accounts.OperationCanceledException;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -58,6 +60,7 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collection;
+import java.util.Locale;
 import java.util.Objects;
 
 import androidx.annotation.NonNull;
@@ -403,6 +406,22 @@ public abstract class AbstractIT {
         assertTrue(result.getLogMessage(), result.isSuccess());
     }
 
+    protected void enableRTL() {
+        Locale locale = new Locale("ar");
+        Resources resources = InstrumentationRegistry.getInstrumentation().getTargetContext().getResources();
+        Configuration config = resources.getConfiguration();
+        config.setLocale(locale);
+        resources.updateConfiguration(config, null);
+    }
+
+    protected void resetLocale() {
+        Locale locale = new Locale("en");
+        Resources resources = InstrumentationRegistry.getInstrumentation().getTargetContext().getResources();
+        Configuration config = resources.getConfiguration();
+        config.setLocale(locale);
+        resources.updateConfiguration(config, null);
+    }
+
     protected void screenshot(View view) {
         screenshot(view, "");
     }

+ 9 - 5
app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java

@@ -128,11 +128,15 @@ public abstract class AbstractOnServerIT extends AbstractIT {
 
             if (!remoteFile.getRemotePath().equals("/")) {
                 if (remoteFile.isEncrypted()) {
-                    assertTrue(new ToggleEncryptionRemoteOperation(remoteFile.getLocalId(),
-                                                                   remoteFile.getRemotePath(),
-                                                                   false)
-                                   .execute(client)
-                                   .isSuccess());
+                    ToggleEncryptionRemoteOperation operation = new ToggleEncryptionRemoteOperation(remoteFile.getLocalId(),
+                                                                                                    remoteFile.getRemotePath(),
+                                                                                                    false);
+
+                    boolean operationResult = operation
+                        .execute(client)
+                        .isSuccess();
+
+                    assertTrue(operationResult);
                 }
 
                 boolean removeResult = false;

+ 1 - 1
app/src/androidTest/java/com/owncloud/android/ui/activity/FolderPickerActivityIT.java

@@ -130,7 +130,7 @@ public class FolderPickerActivityIT extends AbstractIT {
         sut.setFile(origin);
 
         sut.runOnUiThread(() -> {
-            sut.findViewById(R.id.folder_picker_btn_choose).requestFocus();
+            sut.findViewById(R.id.folder_picker_btn_copy).requestFocus();
         });
         waitForIdleSync();
         screenshot(sut);

+ 34 - 1
app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java

@@ -92,6 +92,7 @@ import androidx.activity.result.contract.ActivityResultContract;
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
 import androidx.test.espresso.intent.rule.IntentsTestRule;
+import androidx.test.rule.GrantPermissionRule;
 import kotlin.Unit;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -107,7 +108,7 @@ public class DialogFragmentIT extends AbstractIT {
         Intent intent = new Intent(targetContext, FileDisplayActivity.class);
         return activityRule.launchActivity(intent);
     }
-
+    
 
     @After
     public void quitLooperIfNeeded() {
@@ -134,6 +135,38 @@ public class DialogFragmentIT extends AbstractIT {
         showDialog(dialog);
     }
 
+    @Test
+    @ScreenshotTest
+    public void testConfirmationDialogWithOneAction() {
+        ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[]{}, R.string.filedetails_sync_file, R.string.common_ok, -1, -1);
+        showDialog(dialog);
+    }
+
+    @Test
+    @ScreenshotTest
+    public void testConfirmationDialogWithTwoAction() {
+        ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[]{}, R.string.filedetails_sync_file, R.string.common_ok, R.string.common_cancel, -1);
+        showDialog(dialog);
+    }
+
+    @Test
+    @ScreenshotTest
+    public void testConfirmationDialogWithThreeAction() {
+        ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[]{}, R.string.filedetails_sync_file, R.string.common_ok, R.string.common_cancel, R.string.common_confirm);
+        showDialog(dialog);
+    }
+
+    @Test
+    @ScreenshotTest
+    public void testConfirmationDialogWithThreeActionRTL() {
+        enableRTL();
+
+        ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_list_empty_text_auto_upload, new String[] { }, -1, R.string.common_ok, R.string.common_cancel, R.string.common_confirm);
+        showDialog(dialog);
+
+        resetLocale();
+    }
+
     @Test
     @ScreenshotTest
     public void testRemoveFileDialog() {

+ 1 - 0
app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.java

@@ -45,6 +45,7 @@ public class SyncFileNotEnoughSpaceDialogFragmentTest extends AbstractIT {
         FileDisplayActivity test = activityRule.launchActivity(null);
         OCFile ocFile = new OCFile("/Document/");
         ocFile.setFileLength(5000000);
+        ocFile.setFolder();
 
         SyncFileNotEnoughSpaceDialogFragment dialog = SyncFileNotEnoughSpaceDialogFragment.newInstance(ocFile, 1000);
         dialog.show(test.getListOfFilesFragment().getFragmentManager(), "1");

+ 1 - 1
app/src/androidTest/java/com/owncloud/android/ui/trashbin/TrashbinLocalRepository.kt

@@ -26,7 +26,7 @@ import com.owncloud.android.R
 import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile
 import com.owncloud.android.ui.trashbin.TrashbinRepository.LoadFolderCallback
 
-class TrashbinLocalRepository(val testCase: TrashbinActivityIT.TestCase) : TrashbinRepository {
+class TrashbinLocalRepository(private val testCase: TrashbinActivityIT.TestCase) : TrashbinRepository {
     override fun emptyTrashbin(callback: TrashbinRepository.OperationCallback?) {
         TODO("Not yet implemented")
     }

+ 0 - 73
app/src/main/java/com/nextcloud/client/di/ActivityInjector.java

@@ -1,73 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Chris Narkiewicz
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.client.di;
-
-import android.app.Activity;
-import android.app.Application;
-import android.os.Bundle;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import dagger.android.AndroidInjection;
-
-public class ActivityInjector implements Application.ActivityLifecycleCallbacks {
-
-    @Override
-    public final void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-        if (activity instanceof Injectable) {
-            AndroidInjection.inject(activity);
-        }
-
-        if (activity instanceof FragmentActivity) {
-            final FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
-            fm.registerFragmentLifecycleCallbacks(new FragmentInjector(), true);
-        }
-    }
-
-    @Override
-    public final void onActivityStarted(Activity activity) {
-        // not needed
-    }
-
-    @Override
-    public final void onActivityResumed(Activity activity) {
-        // not needed
-    }
-
-    @Override
-    public final void onActivityPaused(Activity activity) {
-        // not needed
-    }
-
-    @Override
-    public final void onActivityStopped(Activity activity) {
-        // not needed
-    }
-
-    @Override
-    public final void onActivitySaveInstanceState(Activity activity, Bundle outState) {
-        // not needed
-    }
-
-    @Override
-    public final void onActivityDestroyed(Activity activity) {
-        // not needed
-    }
-}

+ 62 - 0
app/src/main/java/com/nextcloud/client/di/ActivityInjector.kt

@@ -0,0 +1,62 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.client.di
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import androidx.fragment.app.FragmentActivity
+import dagger.android.AndroidInjection
+
+class ActivityInjector : ActivityLifecycleCallbacks {
+    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+        if (activity is Injectable) {
+            AndroidInjection.inject(activity)
+        }
+        if (activity is FragmentActivity) {
+            val fm = activity.supportFragmentManager
+            fm.registerFragmentLifecycleCallbacks(FragmentInjector(), true)
+        }
+    }
+
+    override fun onActivityStarted(activity: Activity) {
+        // unused atm
+    }
+
+    override fun onActivityResumed(activity: Activity) {
+        // unused atm
+    }
+
+    override fun onActivityPaused(activity: Activity) {
+        // unused atm
+    }
+
+    override fun onActivityStopped(activity: Activity) {
+        // unused atm
+    }
+
+    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+        // unused atm
+    }
+
+    override fun onActivityDestroyed(activity: Activity) {
+        // unused atm
+    }
+}

+ 4 - 0
app/src/main/java/com/nextcloud/client/di/ComponentsModule.java

@@ -95,6 +95,7 @@ import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
 import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
 import com.owncloud.android.ui.dialog.RenamePublicShareDialogFragment;
+import com.owncloud.android.ui.dialog.SendFilesDialog;
 import com.owncloud.android.ui.dialog.SendShareDialog;
 import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment;
 import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
@@ -460,6 +461,9 @@ abstract class ComponentsModule {
     @ContributesAndroidInjector
     abstract FileActionsBottomSheet fileActionsBottomSheet();
 
+    @ContributesAndroidInjector
+    abstract SendFilesDialog sendFilesDialog();
+
     @ContributesAndroidInjector
     abstract DocumentScanActivity documentScanActivity();
 

+ 15 - 19
app/src/main/java/com/nextcloud/client/di/FragmentInjector.java → app/src/main/java/com/nextcloud/client/di/FragmentInjector.kt

@@ -17,30 +17,26 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
+package com.nextcloud.client.di
 
-package com.nextcloud.client.di;
+import android.content.Context
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import dagger.android.support.AndroidSupportInjection
 
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import dagger.android.support.AndroidSupportInjection;
-
-class FragmentInjector extends FragmentManager.FragmentLifecycleCallbacks {
-    @Override
-    public void onFragmentPreAttached(
-        @NonNull FragmentManager fragmentManager,
-        @NonNull Fragment fragment,
-        @NonNull Context context
+internal class FragmentInjector : FragmentManager.FragmentLifecycleCallbacks() {
+    override fun onFragmentPreAttached(
+        fragmentManager: FragmentManager,
+        fragment: Fragment,
+        context: Context
     ) {
-        super.onFragmentPreAttached(fragmentManager, fragment, context);
-        if (fragment instanceof Injectable) {
+        super.onFragmentPreAttached(fragmentManager, fragment, context)
+        if (fragment is Injectable) {
             try {
-                AndroidSupportInjection.inject(fragment);
-            } catch (IllegalArgumentException directCause) {
+                AndroidSupportInjection.inject(fragment)
+            } catch (directCause: IllegalArgumentException) {
                 // this provides a cause description that is a bit more friendly for developers
-                throw new InjectorNotFoundException(fragment, directCause);
+                throw InjectorNotFoundException(fragment, directCause)
             }
         }
     }

+ 13 - 17
app/src/main/java/com/nextcloud/client/integrations/deck/DeckApi.java → app/src/main/java/com/nextcloud/client/integrations/deck/DeckApi.kt

@@ -17,23 +17,18 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
+package com.nextcloud.client.integrations.deck
 
-package com.nextcloud.client.integrations.deck;
-
-import android.app.PendingIntent;
-
-import com.nextcloud.client.account.User;
-import com.nextcloud.java.util.Optional;
-import com.owncloud.android.lib.resources.notifications.models.Notification;
-
-import androidx.annotation.NonNull;
+import android.app.PendingIntent
+import com.nextcloud.client.account.User
+import com.nextcloud.java.util.Optional
+import com.owncloud.android.lib.resources.notifications.models.Notification
 
 /**
- * This API is for an integration with the <a href="https://github.com/stefan-niedermann/nextcloud-deck">Nextcloud
- * Deck</a> app for android.
+ * This API is for an integration with the [Nextcloud
+ * Deck](https://github.com/stefan-niedermann/nextcloud-deck) app for android.
  */
-public interface DeckApi {
-
+interface DeckApi {
     /**
      * Creates a PendingIntent that can be used in a NotificationBuilder to open the notification link in Deck app
      *
@@ -41,9 +36,10 @@ public interface DeckApi {
      * @param user         The user that is affected by the notification
      * @return If notification can be consumed by Deck, a PendingIntent opening notification link in Deck app; empty
      * value otherwise
-     * @see <a href="https://apps.nextcloud.com/apps/deck">Deck Server App</a>
+     * @see [Deck Server App](https://apps.nextcloud.com/apps/deck)
      */
-    @NonNull
-    Optional<PendingIntent> createForwardToDeckActionIntent(@NonNull final Notification notification,
-                                                            @NonNull final User user);
+    fun createForwardToDeckActionIntent(
+        notification: Notification,
+        user: User
+    ): Optional<PendingIntent?>
 }

+ 0 - 96
app/src/main/java/com/nextcloud/client/integrations/deck/DeckApiImpl.java

@@ -1,96 +0,0 @@
-/*
- * Nextcloud application
- *
- * @author Stefan Niedermann
- * Copyright (C) 2020 Stefan Niedermann <info@niedermann.it>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.client.integrations.deck;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-
-import com.nextcloud.client.account.User;
-import com.nextcloud.java.util.Optional;
-import com.owncloud.android.lib.resources.notifications.models.Notification;
-
-import androidx.annotation.NonNull;
-
-public class DeckApiImpl implements DeckApi {
-
-    static final String APP_NAME = "deck";
-    static final String[] DECK_APP_PACKAGES = new String[] {
-        "it.niedermann.nextcloud.deck",
-        "it.niedermann.nextcloud.deck.play",
-        "it.niedermann.nextcloud.deck.dev"
-    };
-    static final String DECK_ACTIVITY_TO_START = "it.niedermann.nextcloud.deck.ui.PushNotificationActivity";
-
-    private static final String EXTRA_ACCOUNT = "account";
-    private static final String EXTRA_LINK = "link";
-    private static final String EXTRA_OBJECT_ID = "objectId";
-    private static final String EXTRA_SUBJECT = "subject";
-    private static final String EXTRA_SUBJECT_RICH = "subjectRich";
-    private static final String EXTRA_MESSAGE = "message";
-    private static final String EXTRA_MESSAGE_RICH = "messageRich";
-    private static final String EXTRA_USER = "user";
-    private static final String EXTRA_NID = "nid";
-
-    private final Context context;
-    private final PackageManager packageManager;
-
-    public DeckApiImpl(@NonNull Context context, @NonNull PackageManager packageManager) {
-        this.context = context;
-        this.packageManager = packageManager;
-    }
-
-    @NonNull
-    @Override
-    public Optional<PendingIntent> createForwardToDeckActionIntent(@NonNull Notification notification, @NonNull User user) {
-        if (APP_NAME.equalsIgnoreCase(notification.app)) {
-            final Intent intent = new Intent();
-            for (String appPackage : DECK_APP_PACKAGES) {
-                intent.setClassName(appPackage, DECK_ACTIVITY_TO_START);
-                if (packageManager.resolveActivity(intent, 0) != null) {
-                    return Optional.of(createPendingIntent(intent, notification, user));
-                }
-            }
-        }
-        return Optional.empty();
-    }
-
-    private PendingIntent createPendingIntent(@NonNull Intent intent, @NonNull Notification notification, @NonNull User user) {
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return PendingIntent.getActivity(context, notification.getNotificationId(),
-                                         putExtrasToIntent(intent, notification, user),
-                                         PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
-    }
-
-    private Intent putExtrasToIntent(@NonNull Intent intent, @NonNull Notification notification, @NonNull User user) {
-        return intent
-            .putExtra(EXTRA_ACCOUNT, user.getAccountName())
-            .putExtra(EXTRA_LINK, notification.getLink())
-            .putExtra(EXTRA_OBJECT_ID, notification.getObjectId())
-            .putExtra(EXTRA_SUBJECT, notification.getSubject())
-            .putExtra(EXTRA_SUBJECT_RICH, notification.getSubjectRich())
-            .putExtra(EXTRA_MESSAGE, notification.getMessage())
-            .putExtra(EXTRA_MESSAGE_RICH, notification.getMessageRich())
-            .putExtra(EXTRA_USER, notification.getUser())
-            .putExtra(EXTRA_NID, notification.getNotificationId());
-    }
-}

+ 85 - 0
app/src/main/java/com/nextcloud/client/integrations/deck/DeckApiImpl.kt

@@ -0,0 +1,85 @@
+/*
+ * Nextcloud application
+ *
+ * @author Stefan Niedermann
+ * Copyright (C) 2020 Stefan Niedermann <info@niedermann.it>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.client.integrations.deck
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import com.nextcloud.client.account.User
+import com.nextcloud.java.util.Optional
+import com.owncloud.android.lib.resources.notifications.models.Notification
+
+class DeckApiImpl(private val context: Context, private val packageManager: PackageManager) : DeckApi {
+    override fun createForwardToDeckActionIntent(notification: Notification, user: User): Optional<PendingIntent?> {
+        if (APP_NAME.equals(notification.app, ignoreCase = true)) {
+            val intent = Intent()
+            for (appPackage in DECK_APP_PACKAGES) {
+                intent.setClassName(appPackage, DECK_ACTIVITY_TO_START)
+                if (packageManager.resolveActivity(intent, 0) != null) {
+                    return Optional.of(createPendingIntent(intent, notification, user))
+                }
+            }
+        }
+        return Optional.empty()
+    }
+
+    private fun createPendingIntent(intent: Intent, notification: Notification, user: User): PendingIntent {
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        return PendingIntent.getActivity(
+            context,
+            notification.getNotificationId(),
+            putExtrasToIntent(intent, notification, user),
+            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+        )
+    }
+
+    private fun putExtrasToIntent(intent: Intent, notification: Notification, user: User): Intent {
+        return intent
+            .putExtra(EXTRA_ACCOUNT, user.accountName)
+            .putExtra(EXTRA_LINK, notification.getLink())
+            .putExtra(EXTRA_OBJECT_ID, notification.getObjectId())
+            .putExtra(EXTRA_SUBJECT, notification.getSubject())
+            .putExtra(EXTRA_SUBJECT_RICH, notification.getSubjectRich())
+            .putExtra(EXTRA_MESSAGE, notification.getMessage())
+            .putExtra(EXTRA_MESSAGE_RICH, notification.getMessageRich())
+            .putExtra(EXTRA_USER, notification.getUser())
+            .putExtra(EXTRA_NID, notification.getNotificationId())
+    }
+
+    companion object {
+        const val APP_NAME = "deck"
+        val DECK_APP_PACKAGES = arrayOf(
+            "it.niedermann.nextcloud.deck",
+            "it.niedermann.nextcloud.deck.play",
+            "it.niedermann.nextcloud.deck.dev"
+        )
+        const val DECK_ACTIVITY_TO_START = "it.niedermann.nextcloud.deck.ui.PushNotificationActivity"
+        private const val EXTRA_ACCOUNT = "account"
+        private const val EXTRA_LINK = "link"
+        private const val EXTRA_OBJECT_ID = "objectId"
+        private const val EXTRA_SUBJECT = "subject"
+        private const val EXTRA_SUBJECT_RICH = "subjectRich"
+        private const val EXTRA_MESSAGE = "message"
+        private const val EXTRA_MESSAGE_RICH = "messageRich"
+        private const val EXTRA_USER = "user"
+        private const val EXTRA_NID = "nid"
+    }
+}

+ 19 - 19
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())
     }
 

+ 9 - 2
app/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt

@@ -105,12 +105,19 @@ class MediaFoldersDetectionWork constructor(
         )
         val imageMediaFolderPaths: MutableList<String> = ArrayList()
         val videoMediaFolderPaths: MutableList<String> = ArrayList()
+
         for (imageMediaFolder in imageMediaFolders) {
-            imageMediaFolderPaths.add(imageMediaFolder.absolutePath)
+            imageMediaFolder.absolutePath?.let {
+                imageMediaFolderPaths.add(it)
+            }
         }
+
         for (videoMediaFolder in videoMediaFolders) {
-            imageMediaFolderPaths.add(videoMediaFolder.absolutePath)
+            videoMediaFolder.absolutePath?.let {
+                imageMediaFolderPaths.add(it)
+            }
         }
+
         val arbitraryDataString = arbitraryDataProvider.getValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS)
         if (!TextUtils.isEmpty(arbitraryDataString)) {
             mediaFoldersModel = gson.fromJson(arbitraryDataString, MediaFoldersModel::class.java)

+ 1 - 1
app/src/main/java/com/nextcloud/client/jobs/NotificationWork.kt

@@ -142,7 +142,7 @@ class NotificationWork constructor(
 
         val deckActionOverrideIntent = deckApi.createForwardToDeckActionIntent(notification, user)
 
-        val pendingIntent: PendingIntent
+        val pendingIntent: PendingIntent?
         if (deckActionOverrideIntent.isPresent) {
             pendingIntent = deckActionOverrideIntent.get()
         } else {

+ 0 - 232
app/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.java

@@ -1,232 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Bartosz Przybylski
- * @author Chris Narkiewicz
- * Copyright (C) 2015 Bartosz Przybylski
- * Copyright (C) 2015 ownCloud Inc.
- * Copyright (C) 2016 Nextcloud.
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.client.onboarding;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import com.nextcloud.android.common.ui.theme.utils.ColorRole;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.appinfo.AppInfo;
-import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.preferences.AppPreferences;
-import com.owncloud.android.BuildConfig;
-import com.owncloud.android.R;
-import com.owncloud.android.authentication.AuthenticatorActivity;
-import com.owncloud.android.databinding.FirstRunActivityBinding;
-import com.owncloud.android.features.FeatureItem;
-import com.owncloud.android.ui.activity.BaseActivity;
-import com.owncloud.android.ui.activity.FileDisplayActivity;
-import com.owncloud.android.ui.adapter.FeaturesViewAdapter;
-import com.owncloud.android.utils.DisplayUtils;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import javax.inject.Inject;
-
-import androidx.viewpager.widget.ViewPager;
-
-/**
- * Activity displaying general feature after a fresh install.
- */
-public class FirstRunActivity extends BaseActivity implements ViewPager.OnPageChangeListener, Injectable {
-
-    public static final String EXTRA_ALLOW_CLOSE = "ALLOW_CLOSE";
-    public static final String EXTRA_EXIT = "EXIT";
-    public static final int FIRST_RUN_RESULT_CODE = 199;
-
-    @Inject UserAccountManager userAccountManager;
-    @Inject AppPreferences preferences;
-    @Inject AppInfo appInfo;
-    @Inject OnboardingService onboarding;
-
-    @Inject ViewThemeUtils.Factory viewThemeUtilsFactory;
-
-    private FirstRunActivityBinding binding;
-    private ViewThemeUtils defaultViewThemeUtils;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        enableAccountHandling = false;
-
-        super.onCreate(savedInstanceState);
-        defaultViewThemeUtils = viewThemeUtilsFactory.withPrimaryAsBackground();
-        defaultViewThemeUtils.platform.themeStatusBar(this, ColorRole.PRIMARY);
-        this.binding = FirstRunActivityBinding.inflate(getLayoutInflater());
-        setContentView(binding.getRoot());
-
-        boolean isProviderOrOwnInstallationVisible = getResources().getBoolean(R.bool.show_provider_or_own_installation);
-
-        setSlideshowSize(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
-
-
-        defaultViewThemeUtils.material.colorMaterialButtonFilledOnPrimary(binding.login);
-        binding.login.setOnClickListener(v -> {
-            if (getIntent().getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
-                Intent authenticatorActivityIntent = new Intent(this, AuthenticatorActivity.class);
-                authenticatorActivityIntent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, false);
-                startActivityForResult(authenticatorActivityIntent, FIRST_RUN_RESULT_CODE);
-            } else {
-                finish();
-            }
-        });
-
-
-        defaultViewThemeUtils.material.colorMaterialButtonOutlinedOnPrimary(binding.signup);
-        binding.signup.setVisibility(isProviderOrOwnInstallationVisible ? View.VISIBLE : View.GONE);
-        binding.signup.setOnClickListener(v -> {
-            Intent authenticatorActivityIntent = new Intent(this, AuthenticatorActivity.class);
-            authenticatorActivityIntent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, true);
-
-            if (getIntent().getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
-                startActivityForResult(authenticatorActivityIntent, FIRST_RUN_RESULT_CODE);
-            } else {
-                authenticatorActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                startActivity(authenticatorActivityIntent);
-            }
-        });
-
-        defaultViewThemeUtils.platform.colorTextView(binding.hostOwnServer, ColorRole.ON_PRIMARY);
-        binding.hostOwnServer.setVisibility(isProviderOrOwnInstallationVisible ? View.VISIBLE : View.GONE);
-
-        if (isProviderOrOwnInstallationVisible) {
-            binding.hostOwnServer.setOnClickListener(v -> DisplayUtils.startLinkIntent(this, R.string.url_server_install));
-        }
-
-
-        // Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
-        if (onboarding.isFirstRun()) {
-            userAccountManager.removeAllAccounts();
-        }
-
-        FeaturesViewAdapter featuresViewAdapter = new FeaturesViewAdapter(getSupportFragmentManager(), getFirstRun());
-        binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.getCount());
-        binding.contentPanel.setAdapter(featuresViewAdapter);
-
-        binding.contentPanel.addOnPageChangeListener(this);
-    }
-
-    private void setSlideshowSize(boolean isLandscape) {
-        boolean isProviderOrOwnInstallationVisible = getResources().getBoolean(R.bool.show_provider_or_own_installation);
-
-        LinearLayout.LayoutParams layoutParams;
-
-        binding.buttonLayout.setOrientation(isLandscape ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
-
-        if (isProviderOrOwnInstallationVisible) {
-            layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        } else {
-            layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DisplayUtils.convertDpToPixel(isLandscape ? 100f : 150f, this));
-        }
-
-        binding.bottomLayout.setLayoutParams(layoutParams);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        setSlideshowSize(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
-    }
-
-    @Override
-    public void onBackPressed() {
-        onFinish();
-
-        if (getIntent().getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
-            super.onBackPressed();
-        } else {
-            Intent intent = new Intent(getApplicationContext(), AuthenticatorActivity.class);
-            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            intent.putExtra(EXTRA_EXIT, true);
-            startActivity(intent);
-            finish();
-        }
-    }
-
-    private void onFinish() {
-        preferences.setLastSeenVersionCode(BuildConfig.VERSION_CODE);
-    }
-
-    @Override
-    protected void onStop() {
-        onFinish();
-
-        super.onStop();
-    }
-
-    @Override
-    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        // unused but to be implemented due to abstract parent
-    }
-
-    @Override
-    public void onPageSelected(int position) {
-        binding.progressIndicator.animateToStep(position + 1);
-    }
-
-    @Override
-    public void onPageScrollStateChanged(int state) {
-        // unused but to be implemented due to abstract parent
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (FIRST_RUN_RESULT_CODE == requestCode && RESULT_OK == resultCode) {
-
-            String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
-            Account account = userAccountManager.getAccountByName(accountName);
-
-
-            if (account == null) {
-                DisplayUtils.showSnackMessage(this, R.string.account_creation_failed);
-                return;
-            }
-
-            userAccountManager.setCurrentOwnCloudAccount(account.name);
-
-            Intent i = new Intent(this, FileDisplayActivity.class);
-            i.setAction(FileDisplayActivity.RESTART);
-            i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            startActivity(i);
-
-            finish();
-        }
-    }
-
-
-    public static FeatureItem[] getFirstRun() {
-        return new FeatureItem[]{
-            new FeatureItem(R.drawable.logo, R.string.first_run_1_text, R.string.empty, true, false),
-            new FeatureItem(R.drawable.first_run_files, R.string.first_run_2_text, R.string.empty, true, false),
-            new FeatureItem(R.drawable.first_run_groupware, R.string.first_run_3_text, R.string.empty, true, false),
-            new FeatureItem(R.drawable.first_run_talk, R.string.first_run_4_text, R.string.empty, true, false)};
-    }
-}

+ 279 - 0
app/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.kt

@@ -0,0 +1,279 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Bartosz Przybylski
+ * @author Chris Narkiewicz
+ * Copyright (C) 2015 Bartosz Przybylski
+ * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2016 Nextcloud.
+ * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.client.onboarding
+
+import android.accounts.AccountManager
+import android.content.Intent
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.viewpager.widget.ViewPager
+import com.nextcloud.android.common.ui.theme.utils.ColorRole
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.appinfo.AppInfo
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.BuildConfig
+import com.owncloud.android.R
+import com.owncloud.android.authentication.AuthenticatorActivity
+import com.owncloud.android.databinding.FirstRunActivityBinding
+import com.owncloud.android.features.FeatureItem
+import com.owncloud.android.ui.activity.BaseActivity
+import com.owncloud.android.ui.activity.FileDisplayActivity
+import com.owncloud.android.ui.adapter.FeaturesViewAdapter
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+/**
+ * Activity displaying general feature after a fresh install.
+ */
+class FirstRunActivity : BaseActivity(), ViewPager.OnPageChangeListener, Injectable {
+
+    @JvmField
+    @Inject
+    var userAccountManager: UserAccountManager? = null
+
+    @JvmField
+    @Inject
+    var preferences: AppPreferences? = null
+
+    @JvmField
+    @Inject
+    var appInfo: AppInfo? = null
+
+    @JvmField
+    @Inject
+    var onboarding: OnboardingService? = null
+
+    @JvmField
+    @Inject
+    var viewThemeUtilsFactory: ViewThemeUtils.Factory? = null
+
+    private var activityResult: ActivityResultLauncher<Intent>? = null
+
+    private lateinit var binding: FirstRunActivityBinding
+    private var defaultViewThemeUtils: ViewThemeUtils? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        enableAccountHandling = false
+
+        super.onCreate(savedInstanceState)
+
+        applyDefaultTheme()
+
+        binding = FirstRunActivityBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+
+        val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
+        setSlideshowSize(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
+
+        registerActivityResult()
+        setupLoginButton()
+        setupSignupButton(isProviderOrOwnInstallationVisible)
+        setupHostOwnServerTextView(isProviderOrOwnInstallationVisible)
+        deleteAccountAtFirstLaunch()
+        setupFeaturesViewAdapter()
+        handleOnBackPressed()
+    }
+
+    private fun applyDefaultTheme() {
+        defaultViewThemeUtils = viewThemeUtilsFactory?.withPrimaryAsBackground()
+        defaultViewThemeUtils?.platform?.themeStatusBar(this, ColorRole.PRIMARY)
+    }
+
+    private fun registerActivityResult() {
+        activityResult =
+            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
+                if (RESULT_OK == result.resultCode) {
+                    val data = result.data
+                    val accountName = data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
+                    val account = userAccountManager?.getAccountByName(accountName)
+                    if (account == null) {
+                        DisplayUtils.showSnackMessage(this, R.string.account_creation_failed)
+                        return@registerForActivityResult
+                    }
+
+                    userAccountManager?.setCurrentOwnCloudAccount(account.name)
+
+                    val i = Intent(this, FileDisplayActivity::class.java)
+                    i.action = FileDisplayActivity.RESTART
+                    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                    startActivity(i)
+                    finish()
+                }
+            }
+    }
+
+    private fun setupLoginButton() {
+        defaultViewThemeUtils?.material?.colorMaterialButtonFilledOnPrimary(binding.login)
+        binding.login.setOnClickListener {
+            if (intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
+                val authenticatorActivityIntent = getAuthenticatorActivityIntent(false)
+                activityResult?.launch(authenticatorActivityIntent)
+            } else {
+                finish()
+            }
+        }
+    }
+
+    private fun setupSignupButton(isProviderOrOwnInstallationVisible: Boolean) {
+        defaultViewThemeUtils?.material?.colorMaterialButtonOutlinedOnPrimary(binding.signup)
+        binding.signup.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
+        binding.signup.setOnClickListener {
+            val authenticatorActivityIntent = getAuthenticatorActivityIntent(true)
+
+            if (intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)) {
+                activityResult?.launch(authenticatorActivityIntent)
+            } else {
+                authenticatorActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                startActivity(authenticatorActivityIntent)
+            }
+        }
+    }
+
+    private fun getAuthenticatorActivityIntent(extraUseProviderAsWebLogin: Boolean): Intent {
+        val intent = Intent(this, AuthenticatorActivity::class.java)
+        intent.putExtra(AuthenticatorActivity.EXTRA_USE_PROVIDER_AS_WEBLOGIN, extraUseProviderAsWebLogin)
+        return intent
+    }
+
+    private fun setupHostOwnServerTextView(isProviderOrOwnInstallationVisible: Boolean) {
+        defaultViewThemeUtils?.platform?.colorTextView(binding.hostOwnServer, ColorRole.ON_PRIMARY)
+        binding.hostOwnServer.visibility = if (isProviderOrOwnInstallationVisible) View.VISIBLE else View.GONE
+        if (isProviderOrOwnInstallationVisible) {
+            binding.hostOwnServer.setOnClickListener {
+                DisplayUtils.startLinkIntent(
+                    this,
+                    R.string.url_server_install
+                )
+            }
+        }
+    }
+
+    // Sometimes, accounts are not deleted when you uninstall the application so we'll do it now
+    private fun deleteAccountAtFirstLaunch() {
+        if (onboarding?.isFirstRun == true) {
+            userAccountManager?.removeAllAccounts()
+        }
+    }
+
+    @Suppress("SpreadOperator")
+    private fun setupFeaturesViewAdapter() {
+        val featuresViewAdapter = FeaturesViewAdapter(supportFragmentManager, *firstRun)
+        binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.count)
+        binding.contentPanel.adapter = featuresViewAdapter
+        binding.contentPanel.addOnPageChangeListener(this)
+    }
+
+    private fun handleOnBackPressed() {
+        onBackPressedDispatcher.addCallback(
+            this,
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    val isFromAddAccount = intent.getBooleanExtra(EXTRA_ALLOW_CLOSE, false)
+
+                    val destination: Intent = if (isFromAddAccount) {
+                        Intent(applicationContext, FileDisplayActivity::class.java)
+                    } else {
+                        Intent(applicationContext, AuthenticatorActivity::class.java)
+                    }
+
+                    if (!isFromAddAccount) {
+                        destination.putExtra(EXTRA_EXIT, true)
+                    }
+
+                    destination.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+                    startActivity(destination)
+                    finish()
+                }
+            }
+        )
+    }
+
+    private fun setSlideshowSize(isLandscape: Boolean) {
+        val isProviderOrOwnInstallationVisible = resources.getBoolean(R.bool.show_provider_or_own_installation)
+        binding.buttonLayout.orientation = if (isLandscape) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
+
+        val layoutParams: LinearLayout.LayoutParams = if (isProviderOrOwnInstallationVisible) {
+            LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            )
+        } else {
+            @Suppress("MagicNumber")
+            LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                DisplayUtils.convertDpToPixel(if (isLandscape) 100f else 150f, this)
+            )
+        }
+
+        binding.bottomLayout.layoutParams = layoutParams
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        setSlideshowSize(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
+    }
+
+    private fun onFinish() {
+        preferences?.lastSeenVersionCode = BuildConfig.VERSION_CODE
+    }
+
+    override fun onStop() {
+        onFinish()
+        super.onStop()
+    }
+
+    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
+        // unused but to be implemented due to abstract parent
+    }
+
+    override fun onPageSelected(position: Int) {
+        binding.progressIndicator.animateToStep(position + 1)
+    }
+
+    override fun onPageScrollStateChanged(state: Int) {
+        // unused but to be implemented due to abstract parent
+    }
+
+    companion object {
+        const val EXTRA_ALLOW_CLOSE = "ALLOW_CLOSE"
+        const val EXTRA_EXIT = "EXIT"
+
+        val firstRun: Array<FeatureItem>
+            get() = arrayOf(
+                FeatureItem(R.drawable.logo, R.string.first_run_1_text, R.string.empty, true, false),
+                FeatureItem(R.drawable.first_run_files, R.string.first_run_2_text, R.string.empty, true, false),
+                FeatureItem(R.drawable.first_run_groupware, R.string.first_run_3_text, R.string.empty, true, false),
+                FeatureItem(R.drawable.first_run_talk, R.string.first_run_4_text, R.string.empty, true, false)
+            )
+    }
+}

+ 0 - 159
app/src/main/java/com/nextcloud/client/onboarding/WhatsNewActivity.java

@@ -1,159 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Bartosz Przybylski
- * @author Chris Narkiewicz
- * Copyright (C) 2015 Bartosz Przybylski
- * Copyright (C) 2015 ownCloud Inc.
- * Copyright (C) 2016 Nextcloud.
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
- */
-package com.nextcloud.client.onboarding;
-
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.TextView;
-
-import com.google.android.material.button.MaterialButton;
-import com.nextcloud.android.common.ui.theme.utils.ColorRole;
-import com.nextcloud.client.appinfo.AppInfo;
-import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.preferences.AppPreferences;
-import com.owncloud.android.BuildConfig;
-import com.owncloud.android.R;
-import com.owncloud.android.databinding.WhatsNewActivityBinding;
-import com.owncloud.android.ui.adapter.FeaturesViewAdapter;
-import com.owncloud.android.ui.adapter.FeaturesWebViewAdapter;
-import com.owncloud.android.ui.whatsnew.ProgressIndicator;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import javax.inject.Inject;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.viewpager.widget.ViewPager;
-
-/**
- * Activity displaying new features after an update.
- */
-public class WhatsNewActivity extends FragmentActivity implements ViewPager.OnPageChangeListener, Injectable {
-
-    @Inject AppPreferences preferences;
-    @Inject AppInfo appInfo;
-    @Inject OnboardingService onboarding;
-    @Inject ViewThemeUtils.Factory viewThemeUtilsFactory;
-    private ViewThemeUtils viewThemeUtils;
-    
-    private WhatsNewActivityBinding binding;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        binding = WhatsNewActivityBinding.inflate(getLayoutInflater());
-        setContentView(binding.getRoot());
-
-        viewThemeUtils = viewThemeUtilsFactory.withPrimaryAsBackground();
-        viewThemeUtils.platform.themeStatusBar(this, ColorRole.PRIMARY);
-
-        
-        String[] urls = getResources().getStringArray(R.array.whatsnew_urls);
-
-        boolean showWebView = urls.length > 0;
-
-        if (showWebView) {
-            FeaturesWebViewAdapter featuresWebViewAdapter = new FeaturesWebViewAdapter(getSupportFragmentManager(),
-                                                                                       urls);
-            binding.progressIndicator.setNumberOfSteps(featuresWebViewAdapter.getCount());
-            binding.contentPanel.setAdapter(featuresWebViewAdapter);
-        } else {
-            FeaturesViewAdapter featuresViewAdapter = new FeaturesViewAdapter(getSupportFragmentManager(),
-                                                                              onboarding.getWhatsNew());
-            binding.progressIndicator.setNumberOfSteps(featuresViewAdapter.getCount());
-            binding.contentPanel.setAdapter(featuresViewAdapter);
-        }
-
-        binding.contentPanel.addOnPageChangeListener(this);
-
-        viewThemeUtils.platform.colorImageView(binding.forward, ColorRole.ON_PRIMARY);
-
-        binding.forward.setOnClickListener(view -> {
-            if (binding.progressIndicator.hasNextStep()) {
-                binding.contentPanel.setCurrentItem(binding.contentPanel.getCurrentItem() + 1, true);
-                binding.progressIndicator.animateToStep(binding.contentPanel.getCurrentItem() + 1);
-            } else {
-                onFinish();
-                finish();
-            }
-            updateNextButtonIfNeeded();
-        });
-
-        binding.forward.setBackground(null);
-
-        viewThemeUtils.platform.colorTextView(binding.skip, ColorRole.ON_PRIMARY);
-        binding.skip.setOnClickListener(view -> {
-            onFinish();
-            finish();
-        });
-
-        viewThemeUtils.platform.colorTextView(binding.welcomeText, ColorRole.ON_PRIMARY);
-
-        if (showWebView) {
-            binding.welcomeText.setText(R.string.app_name);
-        } else {
-            binding.welcomeText.setText(String.format(getString(R.string.whats_new_title), appInfo.getVersionName()));
-        }
-
-        updateNextButtonIfNeeded();
-    }
-
-    @Override
-    public void onBackPressed() {
-        onFinish();
-        super.onBackPressed();
-    }
-
-    private void updateNextButtonIfNeeded() {
-        if (!binding.progressIndicator.hasNextStep()) {
-            binding.forward.setImageResource(R.drawable.ic_ok);
-            binding.skip.setVisibility(View.INVISIBLE);
-        } else {
-            binding.forward.setImageResource(R.drawable.arrow_right);
-            binding.skip.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void onFinish() {
-        preferences.setLastSeenVersionCode(BuildConfig.VERSION_CODE);
-    }
-
-    @Override
-    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        // unused but to be implemented due to abstract parent
-    }
-
-    @Override
-    public void onPageSelected(int position) {
-        binding.progressIndicator.animateToStep(position + 1);
-        updateNextButtonIfNeeded();
-    }
-
-    @Override
-    public void onPageScrollStateChanged(int state) {
-        // unused but to be implemented due to abstract parent
-    }
-}
-

+ 169 - 0
app/src/main/java/com/nextcloud/client/onboarding/WhatsNewActivity.kt

@@ -0,0 +1,169 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Bartosz Przybylski
+ * @author Chris Narkiewicz
+ * Copyright (C) 2015 Bartosz Przybylski
+ * Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2016 Nextcloud.
+ * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.client.onboarding
+
+import android.os.Bundle
+import android.view.View
+import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.FragmentActivity
+import androidx.viewpager.widget.ViewPager
+import com.nextcloud.android.common.ui.theme.utils.ColorRole
+import com.nextcloud.client.appinfo.AppInfo
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.BuildConfig
+import com.owncloud.android.R
+import com.owncloud.android.databinding.WhatsNewActivityBinding
+import com.owncloud.android.ui.adapter.FeaturesViewAdapter
+import com.owncloud.android.ui.adapter.FeaturesWebViewAdapter
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+/**
+ * Activity displaying new features after an update.
+ */
+class WhatsNewActivity : FragmentActivity(), ViewPager.OnPageChangeListener, Injectable {
+
+    @JvmField
+    @Inject
+    var preferences: AppPreferences? = null
+
+    @JvmField
+    @Inject
+    var appInfo: AppInfo? = null
+
+    @JvmField
+    @Inject
+    var onboarding: OnboardingService? = null
+
+    @JvmField
+    @Inject
+    var viewThemeUtilsFactory: ViewThemeUtils.Factory? = null
+
+    private var viewThemeUtils: ViewThemeUtils? = null
+
+    private lateinit var binding: WhatsNewActivityBinding
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        binding = WhatsNewActivityBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+
+        viewThemeUtils = viewThemeUtilsFactory?.withPrimaryAsBackground()
+        viewThemeUtils?.platform?.themeStatusBar(this, ColorRole.PRIMARY)
+
+        val urls = resources.getStringArray(R.array.whatsnew_urls)
+        val showWebView = urls.isNotEmpty()
+
+        setupFeatureViewAdapter(showWebView, urls)
+        binding.contentPanel.addOnPageChangeListener(this)
+        setupForwardImageButton()
+        setupSkipImageButton()
+        setupWelcomeText(showWebView)
+        updateNextButtonIfNeeded()
+        handleOnBackPressed()
+    }
+
+    @Suppress("SpreadOperator")
+    private fun setupFeatureViewAdapter(showWebView: Boolean, urls: Array<String>) {
+        val adapter = if (showWebView) {
+            FeaturesWebViewAdapter(supportFragmentManager, *urls)
+        } else {
+            onboarding?.let {
+                FeaturesViewAdapter(supportFragmentManager, *it.whatsNew)
+            }
+        }
+
+        adapter?.let {
+            binding.progressIndicator.setNumberOfSteps(it.count)
+            binding.contentPanel.adapter = it
+        }
+    }
+
+    private fun setupForwardImageButton() {
+        viewThemeUtils?.platform?.colorImageView(binding.forward, ColorRole.ON_PRIMARY)
+        binding.forward.setOnClickListener {
+            if (binding.progressIndicator.hasNextStep()) {
+                binding.contentPanel.setCurrentItem(binding.contentPanel.currentItem + 1, true)
+                binding.progressIndicator.animateToStep(binding.contentPanel.currentItem + 1)
+            } else {
+                onFinish()
+                finish()
+            }
+            updateNextButtonIfNeeded()
+        }
+        binding.forward.background = null
+    }
+
+    private fun setupSkipImageButton() {
+        viewThemeUtils?.platform?.colorTextView(binding.skip, ColorRole.ON_PRIMARY)
+        binding.skip.setOnClickListener {
+            onFinish()
+            finish()
+        }
+    }
+
+    private fun setupWelcomeText(showWebView: Boolean) {
+        viewThemeUtils?.platform?.colorTextView(binding.welcomeText, ColorRole.ON_PRIMARY)
+        binding.welcomeText.text = if (showWebView) {
+            getString(R.string.app_name)
+        } else {
+            String.format(getString(R.string.whats_new_title), appInfo?.versionName)
+        }
+    }
+
+    private fun handleOnBackPressed() {
+        onBackPressedDispatcher.addCallback(
+            this,
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    onFinish()
+                    onBackPressedDispatcher.onBackPressed()
+                }
+            }
+        )
+    }
+
+    private fun updateNextButtonIfNeeded() {
+        val hasNextStep = binding.progressIndicator.hasNextStep()
+        binding.forward.setImageResource(if (hasNextStep) R.drawable.arrow_right else R.drawable.ic_ok)
+        binding.skip.visibility = if (hasNextStep) View.VISIBLE else View.INVISIBLE
+    }
+
+    private fun onFinish() {
+        preferences?.lastSeenVersionCode = BuildConfig.VERSION_CODE
+    }
+
+    override fun onPageSelected(position: Int) {
+        binding.progressIndicator.animateToStep(position + 1)
+        updateNextButtonIfNeeded()
+    }
+
+    @Suppress("EmptyFunctionBlock")
+    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
+
+    @Suppress("EmptyFunctionBlock")
+    override fun onPageScrollStateChanged(state: Int) {}
+}

+ 10 - 21
app/src/main/java/com/nextcloud/ui/SquareLoaderImageView.java → app/src/main/java/com/nextcloud/ui/SquareLoaderImageView.kt

@@ -17,32 +17,21 @@
  * You should have received a copy of the GNU Affero General Public
  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
+package com.nextcloud.ui
 
-package com.nextcloud.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.elyeproj.loaderviewlibrary.LoaderImageView;
+import android.content.Context
+import android.util.AttributeSet
+import com.elyeproj.loaderviewlibrary.LoaderImageView
 
 /**
  * Square version of loader image.
  */
-class SquareLoaderImageView extends LoaderImageView {
-    public SquareLoaderImageView(Context context) {
-        super(context);
-    }
-
-    public SquareLoaderImageView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SquareLoaderImageView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
+internal class SquareLoaderImageView : LoaderImageView {
+    constructor(context: Context?) : super(context)
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+    constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, widthMeasureSpec)
     }
 }

+ 2 - 4
app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt

@@ -39,8 +39,7 @@ enum class FileAction(@IdRes val id: Int, @StringRes val title: Int, @DrawableRe
 
     // File moving
     RENAME_FILE(R.id.action_rename_file, R.string.common_rename, R.drawable.ic_rename),
-    MOVE(R.id.action_move, R.string.actionbar_move, R.drawable.ic_move),
-    COPY(R.id.action_copy, R.string.actionbar_copy, R.drawable.ic_content_copy),
+    MOVE_OR_COPY(R.id.action_move_or_copy, R.string.actionbar_move_or_copy, R.drawable.ic_external),
 
     // favorites
     FAVORITE(R.id.action_favorite, R.string.favorite, R.drawable.ic_star),
@@ -83,8 +82,7 @@ enum class FileAction(@IdRes val id: Int, @StringRes val title: Int, @DrawableRe
             SEE_DETAILS,
             LOCK_FILE,
             RENAME_FILE,
-            MOVE,
-            COPY,
+            MOVE_OR_COPY,
             DOWNLOAD_FILE,
             EXPORT_FILE,
             STREAM_MEDIA,

+ 8 - 4
app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt

@@ -79,13 +79,13 @@ class FileActionsBottomSheet : BottomSheetDialogFragment(), Injectable {
     @Inject
     lateinit var syncedFolderProvider: SyncedFolderProvider
 
-    lateinit var viewModel: FileActionsViewModel
+    private lateinit var viewModel: FileActionsViewModel
 
     private var _binding: FileActionsBottomSheetBinding? = null
     private val binding
         get() = _binding!!
 
-    lateinit var componentsGetter: ComponentsGetter
+    private lateinit var componentsGetter: ComponentsGetter
 
     private val thumbnailAsyncTasks = mutableListOf<ThumbnailsCacheManager.ThumbnailGenerationTask>()
 
@@ -109,6 +109,8 @@ class FileActionsBottomSheet : BottomSheetDialogFragment(), Injectable {
         bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
         bottomSheetDialog.behavior.skipCollapsed = true
 
+        viewThemeUtils.platform.colorViewBackground(binding.bottomSheet, ColorRole.SURFACE)
+
         return binding.root
     }
 
@@ -125,11 +127,13 @@ class FileActionsBottomSheet : BottomSheetDialogFragment(), Injectable {
                 displayActions(state.actions)
                 displayTitle(state.titleFile)
             }
+
             is FileActionsViewModel.UiState.LoadedForMultipleFiles -> {
                 setMultipleFilesThumbnail()
                 displayActions(state.actions)
                 displayTitle(state.fileCount)
             }
+
             FileActionsViewModel.UiState.Loading -> {}
             FileActionsViewModel.UiState.Error -> {
                 context?.let {
@@ -195,11 +199,11 @@ class FileActionsBottomSheet : BottomSheetDialogFragment(), Injectable {
     private fun toggleLoadingOrContent(state: FileActionsViewModel.UiState) {
         if (state is FileActionsViewModel.UiState.Loading) {
             binding.bottomSheetLoading.isVisible = true
-            binding.bottomSheetContent.isVisible = false
+            binding.bottomSheetHeader.isVisible = false
             viewThemeUtils.platform.colorCircularProgressBar(binding.bottomSheetLoading, ColorRole.PRIMARY)
         } else {
             binding.bottomSheetLoading.isVisible = false
-            binding.bottomSheetContent.isVisible = true
+            binding.bottomSheetHeader.isVisible = true
         }
     }
 

+ 44 - 0
app/src/main/java/com/nextcloud/utils/extensions/BundleExtensions.kt

@@ -0,0 +1,44 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.utils.extensions
+
+import android.os.Build
+import android.os.Bundle
+import android.os.Parcelable
+import java.io.Serializable
+
+fun <T : Serializable?> Bundle.getSerializableArgument(key: String, type: Class<T>): T? {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        this.getSerializable(key, type)
+    } else {
+        @Suppress("UNCHECKED_CAST")
+        this.getSerializable(key) as T
+    }
+}
+
+fun <T : Parcelable?> Bundle.getParcelableArgument(key: String, type: Class<T>): T? {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        this.getParcelable(key, type)
+    } else {
+        this.getParcelable(key)
+    }
+}

+ 39 - 31
app/src/main/java/com/owncloud/android/MainApp.java

@@ -254,9 +254,12 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
 
         if (!isCrashReportingProcess && !appInfo.isDebugBuild()) {
             Thread.UncaughtExceptionHandler defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler();
-            final ExceptionHandler crashReporter = new ExceptionHandler(this,
-                                                                        defaultPlatformHandler);
-            Thread.setDefaultUncaughtExceptionHandler(crashReporter);
+
+            if (defaultPlatformHandler != null) {
+                final ExceptionHandler crashReporter = new ExceptionHandler(this,
+                                                                            defaultPlatformHandler);
+                Thread.setDefaultUncaughtExceptionHandler(crashReporter);
+            }
         }
     }
 
@@ -791,25 +794,34 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
                     + syncedFolder.getId() + " - " + syncedFolder.getLocalPath());
 
                 for (MediaFolder imageMediaFolder : imageMediaFolders) {
-                    if (imageMediaFolder.absolutePath.equals(syncedFolder.getLocalPath())) {
-                        newSyncedFolder = (SyncedFolder) syncedFolder.clone();
-                        newSyncedFolder.setType(MediaFolderType.IMAGE);
-                        primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
-                        Log_OC.i(TAG, "Migrated image synced_folders record: "
-                            + primaryKey + " - " + newSyncedFolder.getLocalPath());
-                        break;
+                    String absolutePathOfImageFolder = imageMediaFolder.absolutePath;
+
+                    if (absolutePathOfImageFolder != null) {
+                        if (absolutePathOfImageFolder.equals(syncedFolder.getLocalPath())) {
+                            newSyncedFolder = (SyncedFolder) syncedFolder.clone();
+                            newSyncedFolder.setType(MediaFolderType.IMAGE);
+                            primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
+                            Log_OC.i(TAG, "Migrated image synced_folders record: "
+                                + primaryKey + " - " + newSyncedFolder.getLocalPath());
+                            break;
+                        }
                     }
                 }
 
                 for (MediaFolder videoMediaFolder : videoMediaFolders) {
-                    if (videoMediaFolder.absolutePath.equals(syncedFolder.getLocalPath())) {
-                        newSyncedFolder = (SyncedFolder) syncedFolder.clone();
-                        newSyncedFolder.setType(MediaFolderType.VIDEO);
-                        primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
-                        Log_OC.i(TAG, "Migrated video synced_folders record: "
-                            + primaryKey + " - " + newSyncedFolder.getLocalPath());
-                        break;
+                    String absolutePathOfVideoFolder = videoMediaFolder.absolutePath;
+
+                    if (absolutePathOfVideoFolder != null) {
+                        if (absolutePathOfVideoFolder.equals(syncedFolder.getLocalPath())) {
+                            newSyncedFolder = (SyncedFolder) syncedFolder.clone();
+                            newSyncedFolder.setType(MediaFolderType.VIDEO);
+                            primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
+                            Log_OC.i(TAG, "Migrated video synced_folders record: "
+                                + primaryKey + " - " + newSyncedFolder.getLocalPath());
+                            break;
+                        }
                     }
+
                 }
             }
 
@@ -835,19 +847,22 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
 
             List<SyncedFolder> syncedFolderList = syncedFolderProvider.getSyncedFolders();
             Map<Pair<String, String>, Long> syncedFolders = new HashMap<>();
-            ArrayList<Long> ids = new ArrayList<>();
             for (SyncedFolder syncedFolder : syncedFolderList) {
                 Pair<String, String> checkPair = new Pair<>(syncedFolder.getAccount(), syncedFolder.getLocalPath());
                 if (syncedFolders.containsKey(checkPair)) {
-                    if (syncedFolder.getId() > syncedFolders.get(checkPair)) {
-                        syncedFolders.put(checkPair, syncedFolder.getId());
+                    Long folderId = syncedFolders.get(checkPair);
+
+                    if (folderId != null) {
+                        if (syncedFolder.getId() > folderId) {
+                            syncedFolders.put(checkPair, syncedFolder.getId());
+                        }
                     }
                 } else {
                     syncedFolders.put(checkPair, syncedFolder.getId());
                 }
             }
 
-            ids.addAll(syncedFolders.values());
+            ArrayList<Long> ids = new ArrayList<>(syncedFolders.values());
 
             if (ids.size() > 0) {
                 int deletedCount = syncedFolderProvider.deleteSyncedFoldersNotInList(ids);
@@ -865,18 +880,11 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         return dispatchingAndroidInjector;
     }
 
-
     public static void setAppTheme(DarkMode mode) {
         switch (mode) {
-            case LIGHT:
-                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
-                break;
-            case DARK:
-                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
-                break;
-            case SYSTEM:
-                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
-                break;
+            case LIGHT -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+            case DARK -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+            case SYSTEM -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
         }
     }
 }

+ 4 - 0
app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java

@@ -125,6 +125,7 @@ import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertL
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.PermissionUtil;
+import com.owncloud.android.utils.WebViewUtil;
 import com.owncloud.android.utils.theme.CapabilityUtils;
 import com.owncloud.android.utils.theme.ViewThemeUtils;
 
@@ -268,6 +269,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         viewThemeUtils = viewThemeUtilsFactory.withPrimaryAsBackground();
         viewThemeUtils.platform.themeStatusBar(this, ColorRole.PRIMARY);
 
+        WebViewUtil webViewUtil = new WebViewUtil(this);
 
         Uri data = getIntent().getData();
         boolean directLogin = data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme));
@@ -337,6 +339,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         }
 
         initServerPreFragment(savedInstanceState);
+
+        webViewUtil.checkWebViewVersion();
     }
 
     private void deleteCookies() {

+ 0 - 40
app/src/main/java/com/owncloud/android/authentication/DeepLinkLoginActivity.java

@@ -1,40 +0,0 @@
-package com.owncloud.android.authentication;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.nextcloud.client.di.Injectable;
-import com.owncloud.android.R;
-
-public class DeepLinkLoginActivity extends AuthenticatorActivity implements Injectable {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        if (!getResources().getBoolean(R.bool.multiaccount_support) &&
-            accountManager.getAccounts().length == 1) {
-            Toast.makeText(this, R.string.no_mutliple_accounts_allowed, Toast.LENGTH_LONG).show();
-            return;
-        }
-
-        setContentView(R.layout.deep_link_login);
-
-        Uri data = getIntent().getData();
-
-        if (data != null) {
-            try {
-                String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
-                LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, data.toString());
-
-                TextView loginText = findViewById(R.id.loginInfo);
-                loginText.setText(String.format(getString(R.string.direct_login_text), loginUrlInfo.username,
-                                                loginUrlInfo.serverAddress));
-            } catch (IllegalArgumentException e) {
-                Toast.makeText(this, R.string.direct_login_failed, Toast.LENGTH_LONG).show();
-            }
-        }
-    }
-}

+ 37 - 0
app/src/main/java/com/owncloud/android/authentication/DeepLinkLoginActivity.kt

@@ -0,0 +1,37 @@
+package com.owncloud.android.authentication
+
+import android.os.Bundle
+import android.widget.TextView
+import android.widget.Toast
+import com.nextcloud.client.di.Injectable
+import com.owncloud.android.R
+
+class DeepLinkLoginActivity : AuthenticatorActivity(), Injectable {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        if (!resources.getBoolean(R.bool.multiaccount_support) &&
+            accountManager.accounts.size == 1
+        ) {
+            Toast.makeText(this, R.string.no_mutliple_accounts_allowed, Toast.LENGTH_LONG).show()
+            return
+        }
+
+        setContentView(R.layout.deep_link_login)
+
+        intent.data?.let {
+            try {
+                val prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/"
+                val loginUrlInfo = parseLoginDataUrl(prefix, it.toString())
+                val loginText = findViewById<TextView>(R.id.loginInfo)
+                loginText.text = String.format(
+                    getString(R.string.direct_login_text), loginUrlInfo.username,
+                    loginUrlInfo.serverAddress
+                )
+            } catch (e: IllegalArgumentException) {
+                Toast.makeText(this, R.string.direct_login_failed, Toast.LENGTH_LONG).show()
+            }
+        }
+    }
+}

+ 19 - 17
app/src/main/java/com/owncloud/android/datamodel/MediaFolder.java → app/src/main/java/com/owncloud/android/datamodel/MediaFolder.kt

@@ -16,29 +16,31 @@
  * 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 <http://www.gnu.org/licenses/>.
+ * License along with this program. If not, see <http:></http:>//www.gnu.org/licenses/>.
  */
-package com.owncloud.android.datamodel;
-
-import java.util.ArrayList;
-import java.util.List;
+package com.owncloud.android.datamodel
 
 /**
  * Business object representing a media folder with all information that are gathered via media queries.
  */
-public class MediaFolder {
-    /** name of the folder. */
-    public String folderName;
+class MediaFolder {
+    /** name of the folder.  */
+    @JvmField
+    var folderName: String? = null
+
+    /** absolute path of the folder.  */
+    @JvmField
+    var absolutePath: String? = null
 
-    /** absolute path of the folder. */
-    public String absolutePath;
-    
-    /** list of file paths of the folder's content */
-    public List<String> filePaths = new ArrayList<>();
+    /** list of file paths of the folder's content  */
+    @JvmField
+    var filePaths: List<String> = ArrayList()
 
-    /** total number of files in the media folder. */
-    public long numberOfFiles;
+    /** total number of files in the media folder.  */
+    @JvmField
+    var numberOfFiles: Long = 0
 
-    /** type of media folder. */
-    public MediaFolderType type;
+    /** type of media folder.  */
+    @JvmField
+    var type: MediaFolderType? = null
 }

+ 18 - 27
app/src/main/java/com/owncloud/android/datamodel/MediaFolderType.java → app/src/main/java/com/owncloud/android/datamodel/MediaFolderType.kt

@@ -17,37 +17,28 @@
  * You should have received a copy of the GNU Affero General Public
  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-package com.owncloud.android.datamodel;
+package com.owncloud.android.datamodel
 
-import android.util.SparseArray;
+import android.util.SparseArray
 
 /**
  * Types of media folder.
  */
-public enum MediaFolderType {
-    CUSTOM(0),
-    IMAGE(1),
-    VIDEO(2);
-
-    private Integer id;
-
-    private static SparseArray<MediaFolderType> reverseMap = new SparseArray<>(3);
-
-    static {
-        reverseMap.put(CUSTOM.getId(), CUSTOM);
-        reverseMap.put(IMAGE.getId(), IMAGE);
-        reverseMap.put(VIDEO.getId(), VIDEO);
-    }
-
-    MediaFolderType(Integer id) {
-        this.id = id;
-    }
-
-    public static MediaFolderType getById(Integer id) {
-        return reverseMap.get(id);
-    }
-
-    public Integer getId() {
-        return this.id;
+enum class MediaFolderType(@JvmField val id: Int) {
+    CUSTOM(0), IMAGE(1), VIDEO(2);
+
+    companion object {
+        private val reverseMap = SparseArray<MediaFolderType>(3)
+
+        init {
+            reverseMap.put(CUSTOM.id, CUSTOM)
+            reverseMap.put(IMAGE.id, IMAGE)
+            reverseMap.put(VIDEO.id, VIDEO)
+        }
+
+        @JvmStatic
+        fun getById(id: Int?): MediaFolderType {
+            return reverseMap[id!!]
+        }
     }
 }

+ 0 - 51
app/src/main/java/com/owncloud/android/datamodel/MediaFoldersModel.java

@@ -1,51 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2018 Mario Danic
- * Copyright (C) 2018 Andy Scherzinger
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.datamodel;
-
-import java.util.List;
-
-public class MediaFoldersModel {
-    private List<String> imageMediaFolders;
-    private List<String> videoMediaFolders;
-
-    public MediaFoldersModel(List<String> imageMediaFolders, List<String> videoMediaFolders) {
-        this.imageMediaFolders = imageMediaFolders;
-        this.videoMediaFolders = videoMediaFolders;
-    }
-
-    public List<String> getImageMediaFolders() {
-        return this.imageMediaFolders;
-    }
-
-    public List<String> getVideoMediaFolders() {
-        return this.videoMediaFolders;
-    }
-
-    public void setImageMediaFolders(List<String> imageMediaFolders) {
-        this.imageMediaFolders = imageMediaFolders;
-    }
-
-    public void setVideoMediaFolders(List<String> videoMediaFolders) {
-        this.videoMediaFolders = videoMediaFolders;
-    }
-}

+ 24 - 0
app/src/main/java/com/owncloud/android/datamodel/MediaFoldersModel.kt

@@ -0,0 +1,24 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author Andy Scherzinger
+ * Copyright (C) 2018 Mario Danic
+ * Copyright (C) 2018 Andy Scherzinger
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.datamodel
+
+class MediaFoldersModel(var imageMediaFolders: List<String>, var videoMediaFolders: List<String>)

+ 7 - 6
app/src/main/java/com/owncloud/android/datamodel/OCFile.java

@@ -24,14 +24,11 @@ package com.owncloud.android.datamodel;
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
-import com.nextcloud.android.common.ui.theme.utils.ColorRole;
 import com.owncloud.android.R;
 import com.owncloud.android.lib.common.network.WebdavEntry;
 import com.owncloud.android.lib.common.network.WebdavUtils;
@@ -41,9 +38,7 @@ import com.owncloud.android.lib.resources.files.model.GeoLocation;
 import com.owncloud.android.lib.resources.files.model.ImageDimension;
 import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
 import com.owncloud.android.lib.resources.shares.ShareeUser;
-import com.owncloud.android.utils.DrawableUtil;
 import com.owncloud.android.utils.MimeType;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -52,7 +47,6 @@ import java.util.List;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.core.content.ContextCompat;
 import androidx.core.content.FileProvider;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import third_parties.daveKoeller.AlphanumComparator;
@@ -350,6 +344,13 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         return false;
     }
 
+    public String getFileNameWithExtension(int fileNameLength) {
+        String fileName = getFileName();
+        String shortFileName = fileName.substring(0, Math.min(fileName.length(), fileNameLength));
+        String extension = "." + fileName.substring(fileName.lastIndexOf('.') + 1);
+        return shortFileName + extension;
+    }
+
     /**
      * The path, where the file is stored locally
      *

+ 1 - 1
app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java

@@ -415,7 +415,7 @@ public class SyncedFolderProvider extends Observable {
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY,
                syncedFolder.getNameCollisionPolicyInt());
-        cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().getId());
+        cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().id);
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN, syncedFolder.isHidden());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE, syncedFolder.getSubfolderRule().ordinal());
 

+ 3 - 11
app/src/main/java/com/owncloud/android/files/FileMenuFilter.java

@@ -161,8 +161,7 @@ public class FileMenuFilter {
         filterDownload(toHide, synchronizing);
         filterExport(toHide);
         filterRename(toHide, synchronizing);
-        filterCopy(toHide, synchronizing);
-        filterMove(toHide, synchronizing);
+        filterMoveOrCopy(toHide, synchronizing);
         filterRemove(toHide, synchronizing);
         filterSelectAll(toHide, inSingleFileFragment);
         filterDeselectAll(toHide, inSingleFileFragment);
@@ -346,19 +345,12 @@ public class FileMenuFilter {
         }
     }
 
-    private void filterMove(List<Integer> toHide, boolean synchronizing) {
+    private void filterMoveOrCopy(List<Integer> toHide, boolean synchronizing) {
         if (files.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder() || containsLockedFile()) {
-            toHide.add(R.id.action_move);
+            toHide.add(R.id.action_move_or_copy);
         }
     }
 
-    private void filterCopy(List<Integer> toHide, boolean synchronizing) {
-        if (files.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
-            toHide.add(R.id.action_copy);
-        }
-    }
-
-
     private void filterRename(Collection<Integer> toHide, boolean synchronizing) {
         if (!isSingleSelection() || synchronizing || containsEncryptedFile() || containsEncryptedFolder() || containsLockedFile()) {
             toHide.add(R.id.action_rename_file);

+ 7 - 0
app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java

@@ -779,6 +779,13 @@ public abstract class FileActivity extends DrawerActivity
         }
     }
 
+    public void refreshList() {
+        final Fragment fileListFragment = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES);
+        if (fileListFragment != null)  {
+            ((OCFileListFragment) fileListFragment).onRefresh();
+        }
+    }
+
     private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation,
                                                      RemoteOperationResult result) {
         FileDetailSharingFragment sharingFragment = getShareFileFragment();

+ 9 - 57
app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -90,7 +90,6 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.RestoreFileVersionRemoteOperation;
 import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
-import com.owncloud.android.lib.resources.status.OwnCloudVersion;
 import com.owncloud.android.operations.CopyFileOperation;
 import com.owncloud.android.operations.CreateFolderOperation;
 import com.owncloud.android.operations.MoveFileOperation;
@@ -204,8 +203,7 @@ public class FileDisplayActivity extends FileActivity
 
     public static final int REQUEST_CODE__SELECT_CONTENT_FROM_APPS = REQUEST_CODE__LAST_SHARED + 1;
     public static final int REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM = REQUEST_CODE__LAST_SHARED + 2;
-    public static final int REQUEST_CODE__MOVE_FILES = REQUEST_CODE__LAST_SHARED + 3;
-    public static final int REQUEST_CODE__COPY_FILES = REQUEST_CODE__LAST_SHARED + 4;
+    public static final int REQUEST_CODE__MOVE_OR_COPY_FILES = REQUEST_CODE__LAST_SHARED + 3;
     public static final int REQUEST_CODE__UPLOAD_FROM_CAMERA = REQUEST_CODE__LAST_SHARED + 5;
     public static final int REQUEST_CODE__UPLOAD_SCAN_DOC_FROM_CAMERA = REQUEST_CODE__LAST_SHARED + 6;
 
@@ -437,12 +435,10 @@ public class FileDisplayActivity extends FileActivity
 
     private void checkOutdatedServer() {
         Optional<User> user = getUser();
-        OwnCloudVersion serverVersion = user.get().getServer().getVersion();
-
         // show outdated warning
         if (user.isPresent() &&
             CapabilityUtils.checkOutdatedWarning(getResources(),
-                                                 serverVersion,
+                                                 user.get().getServer().getVersion(),
                                                  getCapabilities().getExtendedSupport().isTrue())) {
             DisplayUtils.showServerOutdatedSnackbar(this, Snackbar.LENGTH_LONG);
         }
@@ -886,32 +882,9 @@ public class FileDisplayActivity extends FileActivity
                                                            FileUploader.LOCAL_BEHAVIOUR_DELETE);
                     }
                 }
-            }, new String[]{FileOperationsHelper.createImageFile(getActivity()).getAbsolutePath()}).execute();
-        } else if (requestCode == REQUEST_CODE__MOVE_FILES && resultCode == RESULT_OK) {
-            exitSelectionMode();
-            final Intent fData = data;
-            getHandler().postDelayed(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        requestMoveOperation(fData);
-                    }
-                },
-                DELAY_TO_REQUEST_OPERATIONS_LATER
-                                    );
-
-        } else if (requestCode == REQUEST_CODE__COPY_FILES && resultCode == RESULT_OK) {
+            }, new String[] { FileOperationsHelper.createImageFile(getActivity()).getAbsolutePath() }).execute();
+        } else if (requestCode == REQUEST_CODE__MOVE_OR_COPY_FILES && resultCode == RESULT_OK) {
             exitSelectionMode();
-            final Intent fData = data;
-            getHandler().postDelayed(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        requestCopyOperation(fData);
-                    }
-                },
-                DELAY_TO_REQUEST_OPERATIONS_LATER
-                                    );
         } else if (requestCode == PermissionUtil.REQUEST_CODE_MANAGE_ALL_FILES) {
             syncAndUpdateFolder(true);
         } else {
@@ -1018,28 +991,6 @@ public class FileDisplayActivity extends FileActivity
 
     }
 
-    /**
-     * Request the operation for moving the file/folder from one path to another
-     *
-     * @param data Intent received
-     */
-    private void requestMoveOperation(Intent data) {
-        final OCFile folderToMoveAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
-        final List<String> filePaths = data.getStringArrayListExtra(FolderPickerActivity.EXTRA_FILE_PATHS);
-        getFileOperationsHelper().moveFiles(filePaths, folderToMoveAt);
-    }
-
-    /**
-     * Request the operation for copying the file/folder from one path to another
-     *
-     * @param data Intent received
-     */
-    private void requestCopyOperation(Intent data) {
-        final OCFile targetFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
-        final List<String> filePaths = data.getStringArrayListExtra(FolderPickerActivity.EXTRA_FILE_PATHS);
-        getFileOperationsHelper().copyFiles(filePaths, targetFolder);
-    }
-
     private boolean isSearchOpen() {
         if (searchView == null) {
             return false;
@@ -1781,8 +1732,9 @@ public class FileDisplayActivity extends FileActivity
             boolean fileAvailable = getStorageManager().fileExists(removedFile.getFileId());
 
             if (leftFragment instanceof FileFragment && !fileAvailable && removedFile.equals(((FileFragment) leftFragment).getFile())) {
-                if (leftFragment instanceof PreviewMediaFragment) {
-                    ((PreviewMediaFragment) leftFragment).stopPreview(true);
+                if (leftFragment instanceof PreviewMediaFragment previewMediaFragment) {
+                    previewMediaFragment.stopPreview(true);
+                    onBackPressed();
                 }
                 setFile(getStorageManager().getFileById(removedFile.getParentId()));
                 resetTitleBarAndScrolling();
@@ -1790,8 +1742,8 @@ public class FileDisplayActivity extends FileActivity
             OCFile parentFile = getStorageManager().getFileById(removedFile.getParentId());
             if (parentFile != null && parentFile.equals(getCurrentDir())) {
                 updateListOfFilesFragment(false);
-            } else if (getLeftFragment() instanceof GalleryFragment) {
-                ((GalleryFragment) getLeftFragment()).onRefresh();
+            } else if (getLeftFragment() instanceof GalleryFragment galleryFragment) {
+                galleryFragment.onRefresh();
             }
             supportInvalidateOptionsMenu();
         } else {

+ 54 - 45
app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt

@@ -44,6 +44,7 @@ import com.owncloud.android.lib.common.utils.Log_OC
 import com.owncloud.android.lib.resources.files.SearchRemoteOperation
 import com.owncloud.android.operations.CreateFolderOperation
 import com.owncloud.android.operations.RefreshFolderOperation
+import com.owncloud.android.services.OperationsService
 import com.owncloud.android.syncadapter.FileSyncAdapter
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment
 import com.owncloud.android.ui.dialog.SortingOrderDialogFragment.OnSortingOrderListener
@@ -73,7 +74,9 @@ open class FolderPickerActivity :
     var isDoNotEnterEncryptedFolder = false
         private set
     private var mCancelBtn: MaterialButton? = null
-    private var mChooseBtn: MaterialButton? = null
+    private var mCopyBtn: MaterialButton? = null
+    private var mMoveBtn: MaterialButton? = null
+
     private var caption: String? = null
 
     private var mAction: String? = null
@@ -85,6 +88,7 @@ open class FolderPickerActivity :
     override fun onCreate(savedInstanceState: Bundle?) {
         Log_OC.d(TAG, "onCreate() start")
         super.onCreate(savedInstanceState)
+
         if (this is FilePickerActivity) {
             setContentView(R.layout.files_picker)
         } else {
@@ -101,29 +105,15 @@ open class FolderPickerActivity :
         findViewById<View>(R.id.switch_grid_view_button).visibility =
             View.GONE
         mAction = intent.getStringExtra(EXTRA_ACTION)
+
         if (mAction != null) {
-            when (mAction) {
-                MOVE -> {
-                    caption = resources.getText(R.string.move_to).toString()
-                    mSearchOnlyFolders = true
-                    isDoNotEnterEncryptedFolder = true
-                }
-                COPY -> {
-                    caption = resources.getText(R.string.copy_to).toString()
-                    mSearchOnlyFolders = true
-                    isDoNotEnterEncryptedFolder = true
-                }
-                CHOOSE_LOCATION -> {
-                    caption = resources.getText(R.string.choose_location).toString()
-                    mSearchOnlyFolders = true
-                    isDoNotEnterEncryptedFolder = true
-                    mChooseBtn!!.text = resources.getString(R.string.common_select)
-                }
-                else -> caption = themeUtils.getDefaultDisplayNameForRootFolder(this)
-            }
+            caption = resources.getText(R.string.folder_picker_choose_caption_text).toString()
+            mSearchOnlyFolders = true
+            isDoNotEnterEncryptedFolder = true
         } else {
             caption = themeUtils.getDefaultDisplayNameForRootFolder(this)
         }
+
         mTargetFilePaths = intent.getStringArrayListExtra(EXTRA_FILE_PATHS)
 
         if (savedInstanceState == null) {
@@ -351,13 +341,14 @@ open class FolderPickerActivity :
     }
 
     private fun toggleChooseEnabled() {
-        mChooseBtn?.isEnabled = checkFolderSelectable()
+        mCopyBtn?.isEnabled = checkFolderSelectable()
+        mMoveBtn?.isEnabled = checkFolderSelectable()
     }
 
     // for copy and move, disable selecting parent folder of target files
     private fun checkFolderSelectable(): Boolean {
         return when {
-            mAction != COPY && mAction != MOVE -> true
+            mAction != MOVE_OR_COPY -> true
             mTargetFilePaths.isNullOrEmpty() -> true
             file?.isFolder != true -> true
             // all of the target files are already in the selected directory
@@ -385,38 +376,57 @@ open class FolderPickerActivity :
      */
     private fun initControls() {
         mCancelBtn = findViewById(R.id.folder_picker_btn_cancel)
-        mChooseBtn = findViewById(R.id.folder_picker_btn_choose)
-        if (mChooseBtn != null) {
-            viewThemeUtils.material.colorMaterialButtonPrimaryFilled(mChooseBtn!!)
-            mChooseBtn!!.setOnClickListener(this)
+        mCopyBtn = findViewById(R.id.folder_picker_btn_copy)
+        mMoveBtn = findViewById(R.id.folder_picker_btn_move)
+
+        if (mCopyBtn != null) {
+            viewThemeUtils.material.colorMaterialButtonPrimaryFilled(mCopyBtn!!)
+            mCopyBtn!!.setOnClickListener(this)
+        }
+        if (mMoveBtn != null) {
+            viewThemeUtils.material.colorMaterialButtonPrimaryTonal(mMoveBtn!!)
+            mMoveBtn!!.setOnClickListener(this)
         }
+
         if (mCancelBtn != null) {
             if (this is FilePickerActivity) {
                 viewThemeUtils.material.colorMaterialButtonPrimaryFilled(mCancelBtn!!)
             } else {
-                viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(mCancelBtn!!)
+                viewThemeUtils.material.colorMaterialButtonText(mCancelBtn!!)
             }
             mCancelBtn!!.setOnClickListener(this)
         }
     }
 
     override fun onClick(v: View) {
-        if (v == mCancelBtn) {
-            finish()
-        } else if (v == mChooseBtn) {
-            val i = intent
-            val resultData = Intent()
-            resultData.putExtra(EXTRA_FOLDER, listOfFilesFragment!!.currentFile)
-            val targetFiles = i.getParcelableArrayListExtra<Parcelable>(EXTRA_FILES)
-            if (targetFiles != null) {
-                resultData.putParcelableArrayListExtra(EXTRA_FILES, targetFiles)
-            }
-            mTargetFilePaths.let {
-                resultData.putStringArrayListExtra(EXTRA_FILE_PATHS, it)
+        when (v) {
+            mCancelBtn -> finish()
+            mCopyBtn, mMoveBtn -> copyOrMove(v)
+        }
+    }
+
+    private fun copyOrMove(v: View) {
+        val i = intent
+        val resultData = Intent()
+        resultData.putExtra(EXTRA_FOLDER, listOfFilesFragment?.currentFile)
+
+        i.getParcelableArrayListExtra<Parcelable>(EXTRA_FILES)?.let { targetFiles ->
+            resultData.putParcelableArrayListExtra(EXTRA_FILES, targetFiles)
+        }
+
+        mTargetFilePaths?.let {
+            val action = when (v) {
+                mCopyBtn -> OperationsService.ACTION_COPY_FILE
+                mMoveBtn -> OperationsService.ACTION_MOVE_FILE
+                else -> throw IllegalArgumentException("Unknown operation")
             }
-            setResult(RESULT_OK, resultData)
-            finish()
+
+            fileOperationsHelper.moveOrCopyFiles(action, it, file)
+            resultData.putStringArrayListExtra(EXTRA_FILE_PATHS, it)
         }
+
+        setResult(RESULT_OK, resultData)
+        finish()
     }
 
     override fun onRemoteOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) {
@@ -571,8 +581,8 @@ open class FolderPickerActivity :
         }
     }
 
-    override fun onSortingOrderChosen(selection: FileSortOrder) {
-        listOfFilesFragment!!.sortFiles(selection)
+    override fun onSortingOrderChosen(selection: FileSortOrder?) {
+        listOfFilesFragment?.sortFiles(selection)
     }
 
     companion object {
@@ -592,8 +602,7 @@ open class FolderPickerActivity :
         @JvmField
         val EXTRA_ACTION = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_ACTION")
 
-        const val MOVE = "MOVE"
-        const val COPY = "COPY"
+        const val MOVE_OR_COPY = "MOVE_OR_COPY"
         const val CHOOSE_LOCATION = "CHOOSE_LOCATION"
         private val TAG = FolderPickerActivity::class.java.simpleName
         protected const val TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS"

+ 1 - 1
app/src/main/java/com/owncloud/android/ui/activity/PassCodeActivity.kt

@@ -431,4 +431,4 @@ class PassCodeActivity : AppCompatActivity(), Injectable {
         const val PREFERENCE_PASSCODE_D3 = "PrefPinCode3"
         const val PREFERENCE_PASSCODE_D4 = "PrefPinCode4"
     }
-}
+}

+ 10 - 2
app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt

@@ -643,7 +643,11 @@ class SyncedFoldersActivity :
         }
     }
 
-    override fun onSaveSyncedFolderPreference(syncedFolder: SyncedFolderParcelable) {
+    override fun onSaveSyncedFolderPreference(syncedFolder: SyncedFolderParcelable?) {
+        if (syncedFolder == null) {
+            return
+        }
+
         // custom folders newly created aren't in the list already,
         // so triggering a refresh
         if (MediaFolderType.CUSTOM == syncedFolder.type && syncedFolder.id == SyncedFolder.UNPERSISTED_ID) {
@@ -730,7 +734,11 @@ class SyncedFoldersActivity :
         syncedFolderPreferencesDialogFragment = null
     }
 
-    override fun onDeleteSyncedFolderPreference(syncedFolder: SyncedFolderParcelable) {
+    override fun onDeleteSyncedFolderPreference(syncedFolder: SyncedFolderParcelable?) {
+        if (syncedFolder == null) {
+            return
+        }
+
         syncedFolderProvider.deleteSyncedFolder(syncedFolder.id)
         adapter.removeItem(syncedFolder.section)
     }

+ 1 - 3
app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -528,9 +528,7 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
             // to the ownCloud folder instead of copying
             String[] args = {getString(R.string.app_name)};
             ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(
-                R.string.upload_query_move_foreign_files, args, 0, R.string.common_yes, -1,
-                R.string.common_no
-                                                                                      );
+                R.string.upload_query_move_foreign_files, args, 0, R.string.common_yes,  R.string.common_no, -1);
             dialog.setOnConfirmationListener(this);
             dialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG);
         }

+ 19 - 19
app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java

@@ -25,6 +25,7 @@
 
 package com.owncloud.android.ui.adapter;
 
+import android.annotation.SuppressLint;
 import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
@@ -94,15 +95,15 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
     @Override
     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
         switch (ShareType.fromValue(viewType)) {
-            case PUBLIC_LINK:
-            case EMAIL:
+            case PUBLIC_LINK, EMAIL -> {
                 return new LinkShareViewHolder(
                     FileDetailsShareLinkShareItemBinding.inflate(LayoutInflater.from(fileActivity),
                                                                  parent,
                                                                  false),
                     fileActivity,
                     viewThemeUtils);
-            case NEW_PUBLIC_LINK:
+            }
+            case NEW_PUBLIC_LINK -> {
                 if (encrypted) {
                     return new NewSecureFileDropViewHolder(
                         FileDetailsShareSecureFileDropAddNewItemBinding.inflate(LayoutInflater.from(fileActivity),
@@ -116,17 +117,20 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                                                                             false)
                     );
                 }
-            case INTERNAL:
+            }
+            case INTERNAL -> {
                 return new InternalShareViewHolder(
                     FileDetailsShareInternalShareLinkBinding.inflate(LayoutInflater.from(fileActivity), parent, false),
                     fileActivity);
-            default:
+            }
+            default -> {
                 return new ShareViewHolder(FileDetailsShareShareItemBinding.inflate(LayoutInflater.from(fileActivity),
                                                                                     parent,
                                                                                     false),
                                            user,
                                            fileActivity,
                                            viewThemeUtils);
+            }
         }
     }
 
@@ -138,17 +142,13 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
         final OCShare share = shares.get(position);
 
-        if (holder instanceof LinkShareViewHolder) {
-            LinkShareViewHolder publicShareViewHolder = (LinkShareViewHolder) holder;
+        if (holder instanceof LinkShareViewHolder publicShareViewHolder) {
             publicShareViewHolder.bind(share, listener);
-        } else if (holder instanceof InternalShareViewHolder) {
-            InternalShareViewHolder internalShareViewHolder = (InternalShareViewHolder) holder;
+        } else if (holder instanceof InternalShareViewHolder internalShareViewHolder) {
             internalShareViewHolder.bind(share, listener);
-        } else if (holder instanceof NewLinkShareViewHolder) {
-            NewLinkShareViewHolder newLinkShareViewHolder = (NewLinkShareViewHolder) holder;
+        } else if (holder instanceof NewLinkShareViewHolder newLinkShareViewHolder) {
             newLinkShareViewHolder.bind(listener);
-        } else if (holder instanceof NewSecureFileDropViewHolder) {
-            NewSecureFileDropViewHolder newSecureFileDropViewHolder = (NewSecureFileDropViewHolder) holder;
+        } else if (holder instanceof NewSecureFileDropViewHolder newSecureFileDropViewHolder) {
             newSecureFileDropViewHolder.bind(listener);
         } else {
             ShareViewHolder userViewHolder = (ShareViewHolder) holder;
@@ -166,6 +166,7 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         return shares.size();
     }
 
+    @SuppressLint("NotifyDataSetChanged")
     public void addShares(List<OCShare> sharesToAdd) {
         shares.addAll(sharesToAdd);
         sortShares();
@@ -174,22 +175,21 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
     @Override
     public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
-        if (callContext instanceof ImageView) {
-            ImageView iv = (ImageView) callContext;
+        if (callContext instanceof ImageView iv) {
             iv.setImageDrawable(avatarDrawable);
         }
     }
 
     @Override
     public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
-        if (callContext instanceof ImageView) {
-            ImageView iv = (ImageView) callContext;
+        if (callContext instanceof ImageView iv) {
             // needs to be changed once federated users have avatars
             return String.valueOf(iv.getTag()).equals(tag.split("@")[0]);
         }
         return false;
     }
 
+    @SuppressLint("NotifyDataSetChanged")
     public void remove(OCShare share) {
         shares.remove(share);
         notifyDataSetChanged();
@@ -210,8 +210,8 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             }
         }
 
-        Collections.sort(links, (o1, o2) -> Long.compare(o2.getSharedDate(), o1.getSharedDate()));
-        Collections.sort(users, (o1, o2) -> Long.compare(o2.getSharedDate(), o1.getSharedDate()));
+        links.sort((o1, o2) -> Long.compare(o2.getSharedDate(), o1.getSharedDate()));
+        users.sort((o1, o2) -> Long.compare(o2.getSharedDate(), o1.getSharedDate()));
 
         shares = links;
         shares.addAll(users);

+ 0 - 89
app/src/main/java/com/owncloud/android/ui/adapter/StoragePathAdapter.java

@@ -1,89 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Andy Scherzinger
- * Copyright (C) 2019 Andy Scherzinger
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.ui.adapter;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.owncloud.android.databinding.StoragePathItemBinding;
-
-import java.util.List;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-public class StoragePathAdapter extends RecyclerView.Adapter<StoragePathAdapter.StoragePathViewHolder> {
-    private List<StoragePathItem> pathList;
-    private StoragePathAdapterListener storagePathAdapterListener;
-
-    public StoragePathAdapter(List<StoragePathItem> pathList, StoragePathAdapterListener storagePathAdapterListener) {
-        this.pathList = pathList;
-        this.storagePathAdapterListener = storagePathAdapterListener;
-    }
-
-    @NonNull
-    @Override
-    public StoragePathViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        return new StoragePathAdapter.StoragePathViewHolder(
-            StoragePathItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)
-        );
-    }
-
-    @Override
-    public void onBindViewHolder(@NonNull StoragePathViewHolder holder, int position) {
-        if (pathList != null && pathList.size() > position) {
-            StoragePathItem storagePathItem = pathList.get(position);
-
-            holder.binding.icon.setImageResource(storagePathItem.getIcon());
-            holder.binding.name.setText(storagePathItem.getName());
-        }
-    }
-
-    @Override
-    public int getItemCount() {
-        return pathList.size();
-    }
-
-    public interface StoragePathAdapterListener {
-        /**
-         * sets the chosen path.
-         *
-         * @param path chosen path
-         */
-        void chosenPath(String path);
-    }
-
-    class StoragePathViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
-        StoragePathItemBinding binding;
-
-        public StoragePathViewHolder(StoragePathItemBinding binding) {
-            super(binding.getRoot());
-            this.binding = binding;
-            this.binding.getRoot().setOnClickListener(this);
-        }
-
-        @Override
-        public void onClick(View view) {
-            storagePathAdapterListener.chosenPath(pathList.get(getAdapterPosition()).getPath());
-        }
-    }
-}

+ 80 - 0
app/src/main/java/com/owncloud/android/ui/adapter/StoragePathAdapter.kt

@@ -0,0 +1,80 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2019 Andy Scherzinger
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.owncloud.android.databinding.StoragePathItemBinding
+import com.owncloud.android.ui.adapter.StoragePathAdapter.StoragePathViewHolder
+import com.owncloud.android.utils.theme.ViewThemeUtils
+
+class StoragePathAdapter(
+    private val pathList: List<StoragePathItem>?,
+    private val storagePathAdapterListener: StoragePathAdapterListener,
+    private val viewThemeUtils: ViewThemeUtils
+) : RecyclerView.Adapter<StoragePathViewHolder>() {
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StoragePathViewHolder {
+        return StoragePathViewHolder(
+            StoragePathItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+        )
+    }
+
+    override fun onBindViewHolder(holder: StoragePathViewHolder, position: Int) {
+        if (pathList != null && pathList.size > position) {
+            val storagePathItem = pathList[position]
+            holder.binding.btnStoragePath.setIconResource(storagePathItem.icon)
+            holder.binding.btnStoragePath.text = storagePathItem.name
+            viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(holder.binding.btnStoragePath)
+        }
+    }
+
+    override fun getItemCount(): Int {
+        return pathList?.size ?: 0
+    }
+
+    interface StoragePathAdapterListener {
+        /**
+         * sets the chosen path.
+         *
+         * @param path chosen path
+         */
+        fun chosenPath(path: String)
+    }
+
+    inner class StoragePathViewHolder(var binding: StoragePathItemBinding) :
+        RecyclerView.ViewHolder(
+            binding.root
+        ),
+        View.OnClickListener {
+        init {
+            binding.root.setOnClickListener(this)
+        }
+
+        override fun onClick(view: View) {
+            val path = pathList?.get(absoluteAdapterPosition)?.path
+            path?.let {
+                storagePathAdapterListener.chosenPath(it)
+            }
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java

@@ -248,7 +248,7 @@ public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedV
     public int getSectionByLocalPathAndType(String localPath, int type) {
         for (int i = 0; i < filteredSyncFolderItems.size(); i++) {
             if (filteredSyncFolderItems.get(i).getLocalPath().equalsIgnoreCase(localPath) &&
-                filteredSyncFolderItems.get(i).getType().getId().equals(type)) {
+                filteredSyncFolderItems.get(i).getType().id == type) {
                 return i;
             }
         }

+ 1 - 2
app/src/main/java/com/owncloud/android/ui/components/PassCodeEditText.kt

@@ -29,7 +29,7 @@ import android.view.View
 import androidx.appcompat.widget.AppCompatEditText
 
 @SuppressLint("ClickableViewAccessibility")
-class PassCodeEditText(context: Context, attrs: AttributeSet?): AppCompatEditText(context, attrs) {
+class PassCodeEditText(context: Context, attrs: AttributeSet?) : AppCompatEditText(context, attrs) {
 
     init {
         disableFocusChangeViaTap()
@@ -49,5 +49,4 @@ class PassCodeEditText(context: Context, attrs: AttributeSet?): AppCompatEditTex
         }
         return super.dispatchKeyEvent(event)
     }
-
 }

+ 0 - 105
app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.java

@@ -1,105 +0,0 @@
-/*
- *
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2020 Tobias Kaminsky
- * Copyright (C) 2020 Nextcloud GmbH
- *
- * 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 <https://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.ui.dialog;
-
-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;
-import com.nextcloud.client.jobs.BackgroundJobManager;
-import com.owncloud.android.R;
-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;
-
-public class AccountRemovalConfirmationDialog extends DialogFragment implements Injectable {
-    private static final String KEY_USER = "USER";
-
-    @Inject BackgroundJobManager backgroundJobManager;
-    @Inject ViewThemeUtils viewThemeUtils;
-    private User user;
-
-    public static AccountRemovalConfirmationDialog newInstance(User user) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(KEY_USER, user);
-
-        AccountRemovalConfirmationDialog dialog = new AccountRemovalConfirmationDialog();
-        dialog.setArguments(bundle);
-
-        return dialog;
-    }
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Bundle arguments = getArguments();
-        if (arguments != null) {
-            user = arguments.getParcelable(KEY_USER);
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        AlertDialog alertDialog = (AlertDialog) getDialog();
-        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
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity())
-            .setTitle(R.string.delete_account)
-            .setMessage(getResources().getString(R.string.delete_account_warning, user.getAccountName()))
-            .setIcon(R.drawable.ic_warning)
-            .setPositiveButton(R.string.common_ok,
-                               (dialogInterface, i) -> backgroundJobManager.startAccountRemovalJob(user.getAccountName(),
-                                                                                                   false))
-            .setNegativeButton(R.string.common_cancel, null);
-
-        viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireActivity(), builder);
-
-        return  builder.create();
-    }
-}

+ 105 - 0
app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalConfirmationDialog.kt

@@ -0,0 +1,105 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.dialog
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Build
+import android.os.Bundle
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+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
+import com.nextcloud.client.jobs.BackgroundJobManager
+import com.owncloud.android.R
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+class AccountRemovalConfirmationDialog : DialogFragment(), Injectable {
+    @JvmField
+    @Inject
+    var backgroundJobManager: BackgroundJobManager? = null
+
+    @JvmField
+    @Inject
+    var viewThemeUtils: ViewThemeUtils? = null
+
+    private var user: User? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        user = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            requireArguments().getParcelable(KEY_USER, User::class.java)
+        } else {
+            @Suppress("DEPRECATION")
+            requireArguments().getParcelable(KEY_USER)
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+
+        val alertDialog = dialog as AlertDialog?
+
+        if (alertDialog != null) {
+            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)
+        }
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        val builder = MaterialAlertDialogBuilder(requireActivity())
+            .setTitle(R.string.delete_account)
+            .setMessage(resources.getString(R.string.delete_account_warning, user!!.accountName))
+            .setIcon(R.drawable.ic_warning)
+            .setPositiveButton(R.string.common_ok) { _: DialogInterface?, _: Int ->
+                backgroundJobManager?.startAccountRemovalJob(
+                    user!!.accountName,
+                    false
+                )
+            }
+            .setNegativeButton(R.string.common_cancel, null)
+
+        viewThemeUtils?.dialog?.colorMaterialAlertDialogBackground(requireActivity(), builder)
+
+        return builder.create()
+    }
+
+    companion object {
+
+        private const val KEY_USER = "USER"
+
+        @JvmStatic
+        fun newInstance(user: User?): AccountRemovalConfirmationDialog {
+            val bundle = Bundle()
+            bundle.putParcelable(KEY_USER, user)
+            val dialog = AccountRemovalConfirmationDialog()
+            dialog.arguments = bundle
+            return dialog
+        }
+    }
+}

+ 16 - 14
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) {

+ 0 - 173
app/src/main/java/com/owncloud/android/ui/dialog/ConfirmationDialogFragment.java

@@ -1,173 +0,0 @@
-/*
- * ownCloud Android client application
- *
- * Copyright (C) 2012 Bartek Przybylski 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
- * <http://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.ui.dialog;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.os.Bundle;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.nextcloud.client.di.Injectable;
-import com.owncloud.android.R;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-
-
-public class ConfirmationDialogFragment extends DialogFragment implements Injectable {
-
-    final static String ARG_MESSAGE_RESOURCE_ID = "resource_id";
-    final static String ARG_MESSAGE_ARGUMENTS = "string_array";
-    final static String ARG_TITLE_ID = "title_id";
-
-    final static String ARG_POSITIVE_BTN_RES = "positive_btn_res";
-    final static String ARG_NEUTRAL_BTN_RES = "neutral_btn_res";
-    final static String ARG_NEGATIVE_BTN_RES = "negative_btn_res";
-
-    public static final String FTAG_CONFIRMATION = "CONFIRMATION_FRAGMENT";
-
-    @Inject ViewThemeUtils viewThemeUtils;
-
-
-    private ConfirmationDialogFragmentListener mListener;
-
-    /**
-     * Public factory method to create new ConfirmationDialogFragment instances.
-     *
-     * @param messageResId     Resource id for a message to show in the dialog.
-     * @param messageArguments Arguments to complete the message, if it's a format string. May be null.
-     * @param titleResId       Resource id for a text to show in the title. 0 for default alert title, -1 for no title.
-     * @param posBtn           Resource id for the text of the positive button. -1 for no positive button.
-     * @param neuBtn           Resource id for the text of the neutral button. -1 for no neutral button.
-     * @param negBtn           Resource id for the text of the negative button. -1 for no negative button.
-     * @return Dialog ready to show.
-     */
-    public static ConfirmationDialogFragment newInstance(int messageResId, String[] messageArguments, int titleResId,
-                                                         int posBtn, int neuBtn, int negBtn) {
-        if (messageResId == -1) {
-            throw new IllegalStateException("Calling confirmation dialog without message resource");
-        }
-
-        ConfirmationDialogFragment frag = new ConfirmationDialogFragment();
-        Bundle args = new Bundle();
-        args.putInt(ARG_MESSAGE_RESOURCE_ID, messageResId);
-        args.putStringArray(ARG_MESSAGE_ARGUMENTS, messageArguments);
-        args.putInt(ARG_TITLE_ID, titleResId);
-        args.putInt(ARG_POSITIVE_BTN_RES, posBtn);
-        args.putInt(ARG_NEUTRAL_BTN_RES, neuBtn);
-        args.putInt(ARG_NEGATIVE_BTN_RES, negBtn);
-        frag.setArguments(args);
-        return frag;
-    }
-
-    @Override
-    public void onStart() {
-        super.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));
-        }
-    }
-
-    public void setOnConfirmationListener(ConfirmationDialogFragmentListener listener) {
-        mListener = listener;
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        Bundle arguments = getArguments();
-
-        if (arguments == null) {
-            throw new IllegalArgumentException("Arguments may not be null");
-        }
-
-        Activity activity = getActivity();
-
-        if (activity == null) {
-            throw new IllegalArgumentException("Activity may not be null");
-        }
-
-        Object[] messageArguments = arguments.getStringArray(ARG_MESSAGE_ARGUMENTS);
-        int messageId = arguments.getInt(ARG_MESSAGE_RESOURCE_ID, -1);
-        int titleId = arguments.getInt(ARG_TITLE_ID, -1);
-        int posBtn = arguments.getInt(ARG_POSITIVE_BTN_RES, -1);
-        int neuBtn = arguments.getInt(ARG_NEUTRAL_BTN_RES, -1);
-        int negBtn = arguments.getInt(ARG_NEGATIVE_BTN_RES, -1);
-
-        if (messageArguments == null) {
-            messageArguments = new String[]{};
-        }
-
-        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity)
-            .setIcon(R.drawable.ic_warning)
-            .setIconAttribute(android.R.attr.alertDialogIcon)
-            .setMessage(String.format(getString(messageId), messageArguments));
-
-        if (titleId == 0) {
-            builder.setTitle(android.R.string.dialog_alert_title);
-        } else if (titleId != -1) {
-            builder.setTitle(titleId);
-        }
-
-        if (posBtn != -1) {
-            builder.setPositiveButton(posBtn, (dialog, whichButton) -> {
-                if (mListener != null) {
-                    mListener.onConfirmation(getTag());
-                }
-                dialog.dismiss();
-            });
-        }
-        if (neuBtn != -1) {
-            builder.setNeutralButton(neuBtn, (dialog, whichButton) -> {
-                if (mListener != null) {
-                    mListener.onNeutral(getTag());
-                }
-                dialog.dismiss();
-            });
-        }
-        if (negBtn != -1) {
-            builder.setNegativeButton(negBtn, (dialog, which) -> {
-                if (mListener != null) {
-                    mListener.onCancel(getTag());
-                }
-                dialog.dismiss();
-            });
-        }
-
-        viewThemeUtils.dialog.colorMaterialAlertDialogBackground(activity, builder);
-
-        return builder.create();
-    }
-
-    public interface ConfirmationDialogFragmentListener {
-        void onConfirmation(String callerTag);
-
-        void onNeutral(String callerTag);
-
-        void onCancel(String callerTag);
-    }
-}
-

+ 161 - 0
app/src/main/java/com/owncloud/android/ui/dialog/ConfirmationDialogFragment.kt

@@ -0,0 +1,161 @@
+/*
+ * ownCloud Android client application
+ *
+ * Copyright (C) 2012 Bartek Przybylski 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
+ * <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.dialog
+
+//noinspection SuspiciousImport
+import android.R
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import androidx.appcompat.app.AlertDialog
+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.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+open class ConfirmationDialogFragment : DialogFragment(), Injectable {
+
+    @JvmField
+    @Inject
+    var viewThemeUtils: ViewThemeUtils? = null
+
+    private var mListener: ConfirmationDialogFragmentListener? = null
+
+    override fun onStart() {
+        super.onStart()
+
+        val alertDialog = dialog as AlertDialog?
+
+        if (alertDialog != null) {
+            val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton?
+            if (positiveButton != null) {
+                viewThemeUtils?.material?.colorMaterialButtonPrimaryTonal(positiveButton)
+            }
+
+            val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton?
+            if (negativeButton != null) {
+                viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(negativeButton)
+            }
+
+            val neutralButton = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL) as MaterialButton?
+            if (neutralButton != null) {
+                viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(neutralButton)
+            }
+        }
+    }
+
+    fun setOnConfirmationListener(listener: ConfirmationDialogFragmentListener?) {
+        mListener = listener
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        val messageArguments = requireArguments().getStringArray(ARG_MESSAGE_ARGUMENTS) ?: arrayOf<String>()
+        val titleId = requireArguments().getInt(ARG_TITLE_ID, -1)
+        val messageId = requireArguments().getInt(ARG_MESSAGE_RESOURCE_ID, -1)
+        val positiveButtonTextId = requireArguments().getInt(ARG_POSITIVE_BTN_RES, -1)
+        val negativeButtonTextId = requireArguments().getInt(ARG_NEGATIVE_BTN_RES, -1)
+        val neutralButtonTextId = requireArguments().getInt(ARG_NEUTRAL_BTN_RES, -1)
+
+        @Suppress("SpreadOperator")
+        val message = getString(messageId, *messageArguments)
+
+        val builder = MaterialAlertDialogBuilder(requireActivity())
+            .setIcon(com.owncloud.android.R.drawable.ic_warning)
+            .setIconAttribute(R.attr.alertDialogIcon)
+            .setMessage(message)
+
+        if (titleId == 0) {
+            builder.setTitle(R.string.dialog_alert_title)
+        } else if (titleId != -1) {
+            builder.setTitle(titleId)
+        }
+
+        if (positiveButtonTextId != -1) {
+            builder.setPositiveButton(positiveButtonTextId) { dialog: DialogInterface, _: Int ->
+                mListener?.onConfirmation(tag)
+                dialog.dismiss()
+            }
+        }
+        if (negativeButtonTextId != -1) {
+            builder.setNegativeButton(negativeButtonTextId) { dialog: DialogInterface, _: Int ->
+                mListener?.onCancel(tag)
+                dialog.dismiss()
+            }
+        }
+        if (neutralButtonTextId != -1) {
+            builder.setNeutralButton(neutralButtonTextId) { dialog: DialogInterface, _: Int ->
+                mListener?.onNeutral(tag)
+                dialog.dismiss()
+            }
+        }
+
+        viewThemeUtils?.dialog?.colorMaterialAlertDialogBackground(requireActivity(), builder)
+
+        return builder.create()
+    }
+
+    interface ConfirmationDialogFragmentListener {
+        fun onConfirmation(callerTag: String?)
+        fun onNeutral(callerTag: String?)
+        fun onCancel(callerTag: String?)
+    }
+
+    companion object {
+        const val ARG_MESSAGE_RESOURCE_ID = "resource_id"
+        const val ARG_MESSAGE_ARGUMENTS = "string_array"
+        const val ARG_TITLE_ID = "title_id"
+        const val ARG_POSITIVE_BTN_RES = "positive_btn_res"
+        const val ARG_NEUTRAL_BTN_RES = "neutral_btn_res"
+        const val ARG_NEGATIVE_BTN_RES = "negative_btn_res"
+        const val FTAG_CONFIRMATION = "CONFIRMATION_FRAGMENT"
+
+        /**
+         * Public factory method to create new ConfirmationDialogFragment instances.
+         *
+         * @param messageResId         Resource id for a message to show in the dialog.
+         * @param messageArguments     Arguments to complete the message, if it's a format string. May be null.
+         * @param titleResId           Resource id for a text to show in the title. 0 for default alert title, -1 for no
+         * title.
+         * @param positiveButtonTextId Resource id for the text of the positive button. -1 for no positive button.
+         * @param neutralButtonTextId  Resource id for the text of the neutral button. -1 for no neutral button.
+         * @param negativeButtonTextId Resource id for the text of the negative button. -1 for no negative button.
+         * @return Dialog ready to show.
+         */
+        @JvmStatic
+        fun newInstance(
+            messageResId: Int,
+            messageArguments: Array<String?>?,
+            titleResId: Int,
+            positiveButtonTextId: Int,
+            negativeButtonTextId: Int,
+            neutralButtonTextId: Int
+        ): ConfirmationDialogFragment {
+            check(messageResId != -1) { "Calling confirmation dialog without message resource" }
+            val frag = ConfirmationDialogFragment()
+            val args = Bundle()
+            args.putInt(ARG_MESSAGE_RESOURCE_ID, messageResId)
+            args.putStringArray(ARG_MESSAGE_ARGUMENTS, messageArguments)
+            args.putInt(ARG_TITLE_ID, titleResId)
+            args.putInt(ARG_POSITIVE_BTN_RES, positiveButtonTextId)
+            args.putInt(ARG_NEGATIVE_BTN_RES, negativeButtonTextId)
+            args.putInt(ARG_NEUTRAL_BTN_RES, neutralButtonTextId)
+            frag.arguments = args
+            return frag
+        }
+    }
+}

+ 9 - 5
app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java

@@ -29,6 +29,7 @@ import android.view.View;
 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<ThumbnailsCacheManager.ThumbnailGenerationTask> asyncTasks = new ArrayList<>();
-    private Button positiveButton;
+    private MaterialButton positiveButton;
     @Inject ViewThemeUtils viewThemeUtils;
     @Inject SyncedFolderProvider syncedFolderProvider;
 
@@ -119,9 +120,11 @@ public class ConflictsResolveDialog extends DialogFragment implements Injectable
             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 class ConflictsResolveDialog extends DialogFragment implements Injectable
 
                 }
             })
-            .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 class ConflictsResolveDialog extends DialogFragment implements Injectable
 
         asyncTasks.clear();
     }
+
 }

+ 0 - 230
app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java

@@ -1,230 +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 <http://www.gnu.org/licenses/>.
- *
- */
-
-package com.owncloud.android.ui.dialog;
-
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.view.LayoutInflater;
-import android.view.View;
-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;
-import com.owncloud.android.R;
-import com.owncloud.android.databinding.EditBoxDialogBinding;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.resources.files.FileUtils;
-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 java.util.List;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-
-/**
- * Dialog to input the name for a new folder to create.
- * <p>
- * Triggers the folder creation when name is confirmed.
- */
-public class CreateFolderDialogFragment
-    extends DialogFragment implements DialogInterface.OnClickListener, Injectable {
-
-    private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
-
-    public static final String CREATE_FOLDER_FRAGMENT = "CREATE_FOLDER_FRAGMENT";
-
-    @Inject FileDataStorageManager fileDataStorageManager;
-    @Inject ViewThemeUtils viewThemeUtils;
-    @Inject KeyboardUtils keyboardUtils;
-
-
-    private OCFile mParentFolder;
-    private MaterialButton positiveButton;
-
-
-    private EditBoxDialogBinding binding;
-
-    /**
-     * Public factory method to create new CreateFolderDialogFragment instances.
-     *
-     * @param parentFolder Folder to create
-     * @return Dialog ready to show.
-     */
-    public static CreateFolderDialogFragment newInstance(OCFile parentFolder) {
-        CreateFolderDialogFragment frag = new CreateFolderDialogFragment();
-        Bundle args = new Bundle();
-        args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
-        frag.setArguments(args);
-        return frag;
-
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        bindButton();
-    }
-
-    private void bindButton() {
-        Dialog dialog = getDialog();
-
-        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);
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        bindButton();
-        keyboardUtils.showKeyboardForEditText(requireDialog().getWindow(), binding.userInput);
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        mParentFolder = getArguments().getParcelable(ARG_PARENT_FOLDER);
-
-        // Inflate the layout for the dialog
-        LayoutInflater inflater = requireActivity().getLayoutInflater();
-        binding = EditBoxDialogBinding.inflate(inflater, null, false);
-        View view = binding.getRoot();
-
-        // Setup layout
-        binding.userInput.setText("");
-        viewThemeUtils.material.colorTextInputLayout(binding.userInputContainer);
-
-        OCFile parentFolder = requireArguments().getParcelable(ARG_PARENT_FOLDER);
-        List<OCFile> folderContent = fileDataStorageManager.getFolderContent(parentFolder, false);
-        Set<String> fileNames = Sets.newHashSetWithExpectedSize(folderContent.size());
-
-        for (OCFile file : folderContent) {
-            fileNames.add(file.getFileName());
-        }
-
-        // Add TextChangedListener to handle showing/hiding the input warning message
-        binding.userInput.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void afterTextChanged(Editable s) {
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            /**
-             * When user enters a hidden file name, the 'hidden file' message is shown. Otherwise,
-             * the message is ensured to be hidden.
-             */
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                String newFileName = "";
-                if (binding.userInput.getText() != null) {
-                    newFileName = binding.userInput.getText().toString().trim();
-                }
-
-                if (!TextUtils.isEmpty(newFileName) && newFileName.charAt(0) == '.') {
-                    binding.userInputContainer.setError(getText(R.string.hidden_file_name_warning));
-                } else if (TextUtils.isEmpty(newFileName)) {
-                    binding.userInputContainer.setError(getString(R.string.filename_empty));
-                    if (positiveButton == null) {
-                        bindButton();
-                    }
-                    positiveButton.setEnabled(false);
-                } else if (!FileUtils.isValidName(newFileName)) {
-                    binding.userInputContainer.setError(getString(R.string.filename_forbidden_charaters_from_server));
-                    positiveButton.setEnabled(false);
-                } else if (fileNames.contains(newFileName)) {
-                    binding.userInputContainer.setError(getText(R.string.file_already_exists));
-                    positiveButton.setEnabled(false);
-                } else if (binding.userInputContainer.getError() != null) {
-                    binding.userInputContainer.setError(null);
-                    // Called to remove extra padding
-                    binding.userInputContainer.setErrorEnabled(false);
-                    positiveButton.setEnabled(true);
-                }
-            }
-        });
-
-        // Build the dialog
-        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) {
-            String newFolderName =
-                ((TextView) (getDialog().findViewById(R.id.user_input)))
-                    .getText().toString().trim();
-
-            if (TextUtils.isEmpty(newFolderName)) {
-                DisplayUtils.showSnackMessage(requireActivity(), R.string.filename_empty);
-                return;
-            }
-
-            if (!FileUtils.isValidName(newFolderName)) {
-                DisplayUtils.showSnackMessage(requireActivity(), R.string.filename_forbidden_charaters_from_server);
-
-                return;
-            }
-
-            String path = mParentFolder.getDecryptedRemotePath() + newFolderName + OCFile.PATH_SEPARATOR;
-
-            ((ComponentsGetter) requireActivity()).getFileOperationsHelper().createFolder(path);
-        }
-    }
-}

+ 203 - 0
app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt

@@ -0,0 +1,203 @@
+/*
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.dialog
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextUtils
+import android.text.TextWatcher
+import android.view.View
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+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
+import com.owncloud.android.R
+import com.owncloud.android.databinding.EditBoxDialogBinding
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.resources.files.FileUtils
+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
+
+/**
+ * Dialog to input the name for a new folder to create.
+ *
+ *
+ * Triggers the folder creation when name is confirmed.
+ */
+class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickListener, Injectable {
+    @JvmField
+    @Inject
+    var fileDataStorageManager: FileDataStorageManager? = null
+
+    @JvmField
+    @Inject
+    var viewThemeUtils: ViewThemeUtils? = null
+
+    @JvmField
+    @Inject
+    var keyboardUtils: KeyboardUtils? = null
+    private var mParentFolder: OCFile? = null
+    private var positiveButton: MaterialButton? = null
+
+    private lateinit var binding: EditBoxDialogBinding
+
+    override fun onStart() {
+        super.onStart()
+        bindButton()
+    }
+
+    private fun bindButton() {
+        val dialog = dialog
+
+        if (dialog is AlertDialog) {
+            positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton
+            val negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton
+
+            viewThemeUtils?.material?.colorMaterialButtonPrimaryTonal(positiveButton!!)
+            viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(negativeButton)
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        bindButton()
+        keyboardUtils!!.showKeyboardForEditText(requireDialog().window, binding.userInput)
+    }
+
+    @Suppress("EmptyFunctionBlock")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        mParentFolder = arguments?.getParcelable(ARG_PARENT_FOLDER)
+
+        // Inflate the layout for the dialog
+        val inflater = requireActivity().layoutInflater
+        binding = EditBoxDialogBinding.inflate(inflater, null, false)
+        val view: View = binding.root
+
+        // Setup layout
+        binding.userInput.setText("")
+        viewThemeUtils?.material?.colorTextInputLayout(binding.userInputContainer)
+
+        val parentFolder = requireArguments().getParcelable<OCFile>(ARG_PARENT_FOLDER)
+
+        val folderContent = fileDataStorageManager!!.getFolderContent(parentFolder, false)
+        val fileNames: MutableSet<String> = Sets.newHashSetWithExpectedSize(folderContent.size)
+        for (file in folderContent) {
+            fileNames.add(file.fileName)
+        }
+
+        // Add TextChangedListener to handle showing/hiding the input warning message
+        binding.userInput.addTextChangedListener(object : TextWatcher {
+            override fun afterTextChanged(s: Editable) {}
+            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+
+            /**
+             * When user enters a hidden file name, the 'hidden file' message is shown. Otherwise,
+             * the message is ensured to be hidden.
+             */
+            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+                var newFileName = ""
+                if (binding.userInput.text != null) {
+                    newFileName = binding.userInput.text.toString().trim { it <= ' ' }
+                }
+                if (!TextUtils.isEmpty(newFileName) && newFileName[0] == '.') {
+                    binding.userInputContainer.error = getText(R.string.hidden_file_name_warning)
+                } else if (TextUtils.isEmpty(newFileName)) {
+                    binding.userInputContainer.error = getString(R.string.filename_empty)
+                    if (positiveButton == null) {
+                        bindButton()
+                    }
+                    positiveButton!!.isEnabled = false
+                } else if (!FileUtils.isValidName(newFileName)) {
+                    binding.userInputContainer.error = getString(R.string.filename_forbidden_charaters_from_server)
+                    positiveButton!!.isEnabled = false
+                } else if (fileNames.contains(newFileName)) {
+                    binding.userInputContainer.error = getText(R.string.file_already_exists)
+                    positiveButton!!.isEnabled = false
+                } else if (binding.userInputContainer.error != null) {
+                    binding.userInputContainer.error = null
+                    // Called to remove extra padding
+                    binding.userInputContainer.isErrorEnabled = false
+                    positiveButton!!.isEnabled = true
+                }
+            }
+        })
+
+        // Build the dialog
+        val builder = buildMaterialAlertDialog(view)
+        viewThemeUtils?.dialog?.colorMaterialAlertDialogBackground(binding.userInputContainer.context, builder)
+        return builder.create()
+    }
+
+    private fun buildMaterialAlertDialog(view: View): MaterialAlertDialogBuilder {
+        val builder = 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 fun onClick(dialog: DialogInterface, which: Int) {
+        if (which == AlertDialog.BUTTON_POSITIVE) {
+            val newFolderName = (getDialog()!!.findViewById<View>(R.id.user_input) as TextView)
+                .text.toString().trim { it <= ' ' }
+            if (TextUtils.isEmpty(newFolderName)) {
+                DisplayUtils.showSnackMessage(requireActivity(), R.string.filename_empty)
+                return
+            }
+            if (!FileUtils.isValidName(newFolderName)) {
+                DisplayUtils.showSnackMessage(requireActivity(), R.string.filename_forbidden_charaters_from_server)
+                return
+            }
+            val path = mParentFolder!!.decryptedRemotePath + newFolderName + OCFile.PATH_SEPARATOR
+            (requireActivity() as ComponentsGetter).fileOperationsHelper.createFolder(path)
+        }
+    }
+
+    companion object {
+        private const val ARG_PARENT_FOLDER = "PARENT_FOLDER"
+        const val CREATE_FOLDER_FRAGMENT = "CREATE_FOLDER_FRAGMENT"
+
+        /**
+         * Public factory method to create new CreateFolderDialogFragment instances.
+         *
+         * @param parentFolder Folder to create
+         * @return Dialog ready to show.
+         */
+        @JvmStatic
+        fun newInstance(parentFolder: OCFile?): CreateFolderDialogFragment {
+            val frag = CreateFolderDialogFragment()
+            val args = Bundle()
+            args.putParcelable(ARG_PARENT_FOLDER, parentFolder)
+            frag.arguments = args
+            return frag
+        }
+    }
+}

+ 0 - 196
app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java

@@ -1,196 +0,0 @@
-/*
- *   ownCloud Android client application
- *
- *   @author David A. Velasco
- *   @author Andy Scherzinger
- *   @author TSI-mc
- *   Copyright (C) 2015 ownCloud Inc.
- *   Copyright (C) 2018 Andy Scherzinger
- *   Copyright (C) 2018 TSI-mc
- *
- *   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 <http://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.ui.dialog;
-
-
-import android.app.DatePickerDialog;
-import android.app.Dialog;
-import android.os.Bundle;
-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;
-
-import java.util.Calendar;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.DialogFragment;
-
-/**
- * Dialog requesting a date after today.
- */
-public class ExpirationDatePickerDialogFragment
-    extends DialogFragment
-    implements DatePickerDialog.OnDateSetListener, Injectable {
-
-    /** Tag for FragmentsManager */
-    public static final String DATE_PICKER_DIALOG = "DATE_PICKER_DIALOG";
-
-    /** Parameter constant for date chosen initially */
-    private static final String ARG_CHOSEN_DATE_IN_MILLIS = "CHOSEN_DATE_IN_MILLIS";
-
-    @Inject ViewThemeUtils viewThemeUtils;
-    private OnExpiryDateListener onExpiryDateListener;
-
-    /**
-     * Factory method to create new instances
-     *
-     * @param chosenDateInMillis Date chosen when the dialog appears
-     * @return New dialog instance
-     */
-    public static ExpirationDatePickerDialogFragment newInstance(long chosenDateInMillis) {
-        Bundle arguments = new Bundle();
-        arguments.putLong(ARG_CHOSEN_DATE_IN_MILLIS, chosenDateInMillis);
-
-        ExpirationDatePickerDialogFragment dialog = new ExpirationDatePickerDialogFragment();
-        dialog.setArguments(arguments);
-        return dialog;
-    }
-
-    public void setOnExpiryDateListener(OnExpiryDateListener onExpiryDateListener) {
-        this.onExpiryDateListener = onExpiryDateListener;
-    }
-
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        final Dialog currentDialog = getDialog();
-
-        if (currentDialog != null) {
-            final DatePickerDialog dialog = (DatePickerDialog) currentDialog;
-
-            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);
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * @return A new dialog to let the user choose an expiration date that will be bound to a share link.
-     */
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-
-        // Chosen date received as an argument must be later than tomorrow ; default to tomorrow in other case
-        final Calendar chosenDate = Calendar.getInstance();
-        long tomorrowInMillis = chosenDate.getTimeInMillis() + DateUtils.DAY_IN_MILLIS;
-        long chosenDateInMillis = requireArguments().getLong(ARG_CHOSEN_DATE_IN_MILLIS);
-        chosenDate.setTimeInMillis(Math.max(chosenDateInMillis, tomorrowInMillis));
-
-        // Create a new instance of DatePickerDialog
-        DatePickerDialog dialog = new DatePickerDialog(
-            requireActivity(),
-            R.style.FallbackDatePickerDialogTheme,
-            this,
-            chosenDate.get(Calendar.YEAR),
-            chosenDate.get(Calendar.MONTH),
-            chosenDate.get(Calendar.DAY_OF_MONTH)
-        );
-
-        //show unset button only when date is already selected
-        if (chosenDateInMillis > 0) {
-            dialog.setButton(
-                Dialog.BUTTON_NEGATIVE,
-                getText(R.string.share_via_link_unset_password),
-                (dialog1, which) -> {
-                    if (onExpiryDateListener != null) {
-                        onExpiryDateListener.onDateUnSet();
-                    }
-                });
-        }
-
-        // Prevent days in the past may be chosen
-        DatePicker picker = dialog.getDatePicker();
-        picker.setMinDate(tomorrowInMillis - 1000);
-
-        // Enforce spinners view; ignored by MD-based theme in Android >=5, but calendar is REALLY buggy
-        // in Android < 5, so let's be sure it never appears (in tablets both spinners and calendar are
-        // shown by default)
-        picker.setCalendarViewShown(false);
-
-        return dialog;
-    }
-
-    public long getCurrentSelectionMillis() {
-        final Dialog dialog = getDialog();
-        if (dialog != null) {
-            final DatePickerDialog datePickerDialog = (DatePickerDialog) dialog;
-            final DatePicker picker = datePickerDialog.getDatePicker();
-            return yearMonthDayToMillis(picker.getYear(), picker.getMonth(), picker.getDayOfMonth());
-        }
-        return 0;
-    }
-
-    /**
-     * Called when the user chooses an expiration date.
-     *
-     * @param view        View instance where the date was chosen
-     * @param year        Year of the date chosen.
-     * @param monthOfYear Month of the date chosen [0, 11]
-     * @param dayOfMonth  Day of the date chosen
-     */
-    @Override
-    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
-
-        long chosenDateInMillis = yearMonthDayToMillis(year, monthOfYear, dayOfMonth);
-
-        if (onExpiryDateListener != null) {
-            onExpiryDateListener.onDateSet(year, monthOfYear, dayOfMonth, chosenDateInMillis);
-        }
-    }
-
-    private long yearMonthDayToMillis(int year, int monthOfYear, int dayOfMonth) {
-        Calendar date = Calendar.getInstance();
-        date.set(Calendar.YEAR, year);
-        date.set(Calendar.MONTH, monthOfYear);
-        date.set(Calendar.DAY_OF_MONTH, dayOfMonth);
-        return date.getTimeInMillis();
-    }
-
-    public interface OnExpiryDateListener {
-        void onDateSet(int year, int monthOfYear, int dayOfMonth, long chosenDateInMillis);
-
-        void onDateUnSet();
-    }
-}

+ 183 - 0
app/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.kt

@@ -0,0 +1,183 @@
+/*
+ *   ownCloud Android client application
+ *
+ *   @author David A. Velasco
+ *   @author Andy Scherzinger
+ *   @author TSI-mc
+ *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2018 Andy Scherzinger
+ *   Copyright (C) 2018 TSI-mc
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.dialog
+
+import android.app.DatePickerDialog
+import android.app.DatePickerDialog.OnDateSetListener
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.format.DateUtils
+import android.widget.DatePicker
+import androidx.fragment.app.DialogFragment
+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
+import java.util.Calendar
+import javax.inject.Inject
+
+/**
+ * Dialog requesting a date after today.
+ */
+class ExpirationDatePickerDialogFragment : DialogFragment(), OnDateSetListener, Injectable {
+
+    @JvmField
+    @Inject
+    var viewThemeUtils: ViewThemeUtils? = null
+
+    private var onExpiryDateListener: OnExpiryDateListener? = null
+
+    fun setOnExpiryDateListener(onExpiryDateListener: OnExpiryDateListener?) {
+        this.onExpiryDateListener = onExpiryDateListener
+    }
+
+    override fun onStart() {
+        super.onStart()
+
+        val currentDialog = dialog
+
+        if (currentDialog != null) {
+            val dialog = currentDialog as DatePickerDialog
+
+            val positiveButton = dialog.getButton(DatePickerDialog.BUTTON_POSITIVE) as MaterialButton?
+            if (positiveButton != null) {
+                viewThemeUtils?.material?.colorMaterialButtonPrimaryTonal(positiveButton)
+            }
+            val negativeButton = dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE) as MaterialButton?
+            if (negativeButton != null) {
+                viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(negativeButton)
+            }
+            val neutralButton = dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL) as MaterialButton?
+            if (neutralButton != null) {
+                viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(neutralButton)
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return A new dialog to let the user choose an expiration date that will be bound to a share link.
+     */
+    @Suppress("DEPRECATION", "MagicNumber")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Chosen date received as an argument must be later than tomorrow ; default to tomorrow in other case
+        val chosenDate = Calendar.getInstance()
+        val tomorrowInMillis = chosenDate.timeInMillis + DateUtils.DAY_IN_MILLIS
+        val chosenDateInMillis = requireArguments().getLong(ARG_CHOSEN_DATE_IN_MILLIS)
+        chosenDate.timeInMillis = chosenDateInMillis.coerceAtLeast(tomorrowInMillis)
+
+        // Create a new instance of DatePickerDialog
+        val dialog = DatePickerDialog(
+            requireActivity(),
+            R.style.FallbackDatePickerDialogTheme,
+            this,
+            chosenDate[Calendar.YEAR],
+            chosenDate[Calendar.MONTH],
+            chosenDate[Calendar.DAY_OF_MONTH]
+        )
+
+        // show unset button only when date is already selected
+        if (chosenDateInMillis > 0) {
+            dialog.setButton(
+                Dialog.BUTTON_NEGATIVE,
+                getText(R.string.share_via_link_unset_password)
+            ) { _: DialogInterface?, _: Int ->
+                onExpiryDateListener?.onDateUnSet()
+            }
+        }
+
+        // Prevent days in the past may be chosen
+        val picker = dialog.datePicker
+        picker.minDate = tomorrowInMillis - 1000
+
+        // Enforce spinners view; ignored by MD-based theme in Android >=5, but calendar is REALLY buggy
+        // in Android < 5, so let's be sure it never appears (in tablets both spinners and calendar are
+        // shown by default)
+        @Suppress("DEPRECATION")
+        picker.calendarViewShown = false
+        return dialog
+    }
+
+    val currentSelectionMillis: Long
+        get() {
+            val dialog = dialog
+            if (dialog != null) {
+                val datePickerDialog = dialog as DatePickerDialog
+                val picker = datePickerDialog.datePicker
+                return yearMonthDayToMillis(picker.year, picker.month, picker.dayOfMonth)
+            }
+            return 0
+        }
+
+    /**
+     * Called when the user chooses an expiration date.
+     *
+     * @param view        View instance where the date was chosen
+     * @param year        Year of the date chosen.
+     * @param monthOfYear Month of the date chosen [0, 11]
+     * @param dayOfMonth  Day of the date chosen
+     */
+    override fun onDateSet(view: DatePicker, year: Int, monthOfYear: Int, dayOfMonth: Int) {
+        val chosenDateInMillis = yearMonthDayToMillis(year, monthOfYear, dayOfMonth)
+        if (onExpiryDateListener != null) {
+            onExpiryDateListener?.onDateSet(year, monthOfYear, dayOfMonth, chosenDateInMillis)
+        }
+    }
+
+    private fun yearMonthDayToMillis(year: Int, monthOfYear: Int, dayOfMonth: Int): Long {
+        val date = Calendar.getInstance()
+        date[Calendar.YEAR] = year
+        date[Calendar.MONTH] = monthOfYear
+        date[Calendar.DAY_OF_MONTH] = dayOfMonth
+        return date.timeInMillis
+    }
+
+    interface OnExpiryDateListener {
+        fun onDateSet(year: Int, monthOfYear: Int, dayOfMonth: Int, chosenDateInMillis: Long)
+        fun onDateUnSet()
+    }
+
+    companion object {
+        /** Tag for FragmentsManager  */
+        const val DATE_PICKER_DIALOG = "DATE_PICKER_DIALOG"
+
+        /** Parameter constant for date chosen initially  */
+        private const val ARG_CHOSEN_DATE_IN_MILLIS = "CHOSEN_DATE_IN_MILLIS"
+
+        /**
+         * Factory method to create new instances
+         *
+         * @param chosenDateInMillis Date chosen when the dialog appears
+         * @return New dialog instance
+         */
+        fun newInstance(chosenDateInMillis: Long): ExpirationDatePickerDialogFragment {
+            val arguments = Bundle()
+            arguments.putLong(ARG_CHOSEN_DATE_IN_MILLIS, chosenDateInMillis)
+            val dialog = ExpirationDatePickerDialogFragment()
+            dialog.arguments = arguments
+            return dialog
+        }
+    }
+}

+ 0 - 105
app/src/main/java/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java

@@ -1,105 +0,0 @@
-/**
- *   ownCloud Android client application
- *
- *   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 <http://www.gnu.org/licenses/>.
- *
- */
-
-package com.owncloud.android.ui.dialog;
-
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnKeyListener;
-import android.os.Bundle;
-import android.view.KeyEvent;
-import android.widget.ProgressBar;
-
-import com.nextcloud.client.di.Injectable;
-import com.owncloud.android.R;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.DialogFragment;
-
-
-public class IndeterminateProgressDialog extends DialogFragment implements Injectable {
-
-    private static final String ARG_MESSAGE_ID = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_MESSAGE_ID";
-    private static final String ARG_CANCELABLE = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_CANCELABLE";
-
-    @Inject ViewThemeUtils viewThemeUtils;
-
-    /**
-     * Public factory method to get dialog instances.
-     *
-     * @param messageId     Resource id for a message to show in the dialog.
-     * @param cancelable    If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...)
-     * @return              New dialog instance, ready to show.
-     */
-    public static IndeterminateProgressDialog newInstance(int messageId, boolean cancelable) {
-        IndeterminateProgressDialog fragment = new IndeterminateProgressDialog();
-        fragment.setStyle(DialogFragment.STYLE_NO_FRAME, R.style.ownCloud_AlertDialog);
-        Bundle args = new Bundle();
-        args.putInt(ARG_MESSAGE_ID, messageId);
-        args.putBoolean(ARG_CANCELABLE, cancelable);
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        /// create indeterminate progress dialog
-        final ProgressDialog progressDialog = new ProgressDialog(getActivity(), R.style.ProgressDialogTheme);
-        progressDialog.setIndeterminate(true);
-        progressDialog.setOnShowListener(dialog -> {
-            ProgressBar v = progressDialog.findViewById(android.R.id.progress);
-            viewThemeUtils.platform.tintDrawable(requireContext(), v.getIndeterminateDrawable());
-        });
-
-        /// set message
-        int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.placeholder_sentence);
-        progressDialog.setMessage(getString(messageId));
-
-        /// set cancellation behavior
-        boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE, false);
-        if (!cancelable) {
-            progressDialog.setCancelable(false);
-            // disable the back button
-            OnKeyListener keyListener = new OnKeyListener() {
-                @Override
-                public boolean onKey(DialogInterface dialog, int keyCode,
-                        KeyEvent event) {
-
-                    return keyCode == KeyEvent.KEYCODE_BACK;
-                }
-
-            };
-            progressDialog.setOnKeyListener(keyListener);
-        }
-
-        return progressDialog;
-    }
-
-}
-
-

+ 92 - 0
app/src/main/java/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.kt

@@ -0,0 +1,92 @@
+/**
+ * ownCloud Android client application
+ *
+ * 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 <http:></http:>//www.gnu.org/licenses/>.
+ *
+ */
+
+@file:Suppress("DEPRECATION")
+
+package com.owncloud.android.ui.dialog
+
+import android.app.Dialog
+import android.app.ProgressDialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.KeyEvent
+import android.widget.ProgressBar
+import androidx.fragment.app.DialogFragment
+import com.nextcloud.client.di.Injectable
+import com.owncloud.android.R
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+class IndeterminateProgressDialog : DialogFragment(), Injectable {
+
+    @JvmField
+    @Inject
+    var viewThemeUtils: ViewThemeUtils? = null
+
+    /**
+     * {@inheritDoc}
+     */
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // / create indeterminate progress dialog
+        val progressDialog = ProgressDialog(requireActivity(), R.style.ProgressDialogTheme)
+        progressDialog.isIndeterminate = true
+        progressDialog.setOnShowListener {
+            val v = progressDialog.findViewById<ProgressBar>(android.R.id.progress)
+            viewThemeUtils?.platform?.tintDrawable(requireContext(), v.indeterminateDrawable)
+        }
+
+        // / set message
+        val messageId = requireArguments().getInt(ARG_MESSAGE_ID, R.string.placeholder_sentence)
+        progressDialog.setMessage(getString(messageId))
+
+        // / set cancellation behavior
+        val cancelable = requireArguments().getBoolean(ARG_CANCELABLE, false)
+        if (!cancelable) {
+            progressDialog.setCancelable(false)
+            // disable the back button
+            val keyListener =
+                DialogInterface.OnKeyListener { _, keyCode, _ -> keyCode == KeyEvent.KEYCODE_BACK }
+            progressDialog.setOnKeyListener(keyListener)
+        }
+        return progressDialog
+    }
+
+    companion object {
+        private val ARG_MESSAGE_ID = IndeterminateProgressDialog::class.java.canonicalName?.plus(".ARG_MESSAGE_ID")
+        private val ARG_CANCELABLE = IndeterminateProgressDialog::class.java.canonicalName?.plus(".ARG_CANCELABLE")
+
+        /**
+         * Public factory method to get dialog instances.
+         *
+         * @param messageId     Resource id for a message to show in the dialog.
+         * @param cancelable    If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...)
+         * @return New dialog instance, ready to show.
+         */
+        @JvmStatic
+        fun newInstance(messageId: Int, cancelable: Boolean): IndeterminateProgressDialog {
+            val fragment = IndeterminateProgressDialog()
+            fragment.setStyle(STYLE_NO_FRAME, R.style.ownCloud_AlertDialog)
+            val args = Bundle()
+            args.putInt(ARG_MESSAGE_ID, messageId)
+            args.putBoolean(ARG_CANCELABLE, cancelable)
+            fragment.arguments = args
+            return fragment
+        }
+    }
+}

+ 0 - 88
app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java

@@ -1,88 +0,0 @@
-/*
- *   ownCloud Android client application
- *
- *   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 <http://www.gnu.org/licenses/>.
- *
- */
-package com.owncloud.android.ui.dialog;
-
-import android.app.Dialog;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.nextcloud.client.di.Injectable;
-import com.owncloud.android.R;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.DialogFragment;
-
-public class LoadingDialog extends DialogFragment implements Injectable {
-
-    @Inject ViewThemeUtils viewThemeUtils;
-    private String mMessage;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setRetainInstance(true);
-        setCancelable(false);
-    }
-
-    public static LoadingDialog newInstance(String message) {
-        LoadingDialog loadingDialog = new LoadingDialog();
-        loadingDialog.mMessage = message;
-        return loadingDialog;
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        // Create a view by inflating desired layout
-        View v = inflater.inflate(R.layout.loading_dialog, container,  false);
-
-        // set value
-        TextView tv = v.findViewById(R.id.loadingText);
-        tv.setText(mMessage);
-
-        // set progress wheel color
-        ProgressBar progressBar = v.findViewById(R.id.loadingBar);
-        viewThemeUtils.platform.tintDrawable(requireContext(), progressBar.getIndeterminateDrawable());
-
-        return v;
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        Dialog dialog = super.onCreateDialog(savedInstanceState);
-        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
-        return dialog;
-    }
-
-    @Override
-    public void onDestroyView() {
-        if (getDialog() != null && getRetainInstance()) {
-            getDialog().setDismissMessage(null);
-        }
-        super.onDestroyView();
-    }
-}

+ 79 - 0
app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.kt

@@ -0,0 +1,79 @@
+/*
+ *   ownCloud Android client application
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.dialog
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import com.nextcloud.android.common.ui.theme.utils.ColorRole
+import com.nextcloud.client.di.Injectable
+import com.owncloud.android.databinding.LoadingDialogBinding
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+class LoadingDialog : DialogFragment(), Injectable {
+
+    @JvmField
+    @Inject
+    var viewThemeUtils: ViewThemeUtils? = null
+
+    private var mMessage: String? = null
+    private lateinit var binding: LoadingDialogBinding
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        retainInstance = true
+        isCancelable = false
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        binding = LoadingDialogBinding.inflate(inflater, container, false)
+        binding.loadingText.text = mMessage
+
+        val loadingDrawable = binding.loadingBar.indeterminateDrawable
+        if (loadingDrawable != null) {
+            viewThemeUtils?.platform?.tintDrawable(requireContext(), loadingDrawable)
+        }
+
+        viewThemeUtils?.platform?.colorViewBackground(binding.loadingLayout, ColorRole.SURFACE)
+
+        return binding.root
+    }
+
+    override fun onDestroyView() {
+        if (dialog != null && retainInstance) {
+            dialog?.setDismissMessage(null)
+        }
+
+        super.onDestroyView()
+    }
+
+    companion object {
+
+        @JvmStatic
+        fun newInstance(message: String?): LoadingDialog {
+            val loadingDialog = LoadingDialog()
+            loadingDialog.mMessage = message
+            return loadingDialog
+        }
+    }
+}

+ 0 - 169
app/src/main/java/com/owncloud/android/ui/dialog/LocalStoragePathPickerDialogFragment.java

@@ -1,169 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Andy Scherzinger
- * Copyright (C) 2019 Andy Scherzinger
- *
- * 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 <https://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.ui.dialog;
-
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.os.Environment;
-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.StoragePathDialogBinding;
-import com.owncloud.android.ui.adapter.StoragePathAdapter;
-import com.owncloud.android.ui.adapter.StoragePathItem;
-import com.owncloud.android.utils.FileStorageUtils;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.recyclerview.widget.LinearLayoutManager;
-
-/**
- * Picker dialog for choosing a (storage) path.
- */
-public class LocalStoragePathPickerDialogFragment extends DialogFragment
-    implements DialogInterface.OnClickListener, StoragePathAdapter.StoragePathAdapterListener, Injectable {
-
-    public static final String LOCAL_STORAGE_PATH_PICKER_FRAGMENT = "LOCAL_STORAGE_PATH_PICKER_FRAGMENT";
-
-    private static Set<String> internalStoragePaths = new HashSet<>();
-
-    @Inject ViewThemeUtils viewThemeUtils;
-
-    static {
-        internalStoragePaths.add("/storage/emulated/legacy");
-        internalStoragePaths.add("/storage/emulated/0");
-        internalStoragePaths.add("/mnt/sdcard");
-    }
-
-    private StoragePathDialogBinding binding;
-
-    public static LocalStoragePathPickerDialogFragment newInstance() {
-        return new LocalStoragePathPickerDialogFragment();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        AlertDialog alertDialog = (AlertDialog) getDialog();
-
-        if (alertDialog != null) {
-            viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE));
-        }
-    }
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        if (!(getActivity() instanceof StoragePathAdapter.StoragePathAdapterListener)) {
-            throw new IllegalArgumentException("Calling activity must implement " +
-                                                   "StoragePathAdapter.StoragePathAdapterListener");
-        }
-
-        // Inflate the layout for the dialog
-        LayoutInflater inflater = requireActivity().getLayoutInflater();
-        binding = StoragePathDialogBinding.inflate(inflater, null, false);
-        View view = binding.getRoot();
-
-        StoragePathAdapter adapter = new StoragePathAdapter(getPathList(), this);
-
-        binding.storagePathRecyclerView.setAdapter(adapter);
-        binding.storagePathRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
-
-        // Build the dialog
-        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(binding.getRoot().getContext());
-        builder.setView(view)
-            .setNegativeButton(R.string.common_cancel, this)
-            .setTitle(R.string.storage_choose_location);
-
-        viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.getRoot().getContext(), builder);
-
-        return builder.create();
-    }
-
-    @Override
-    public void onDestroyView() {
-        binding = null;
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        if (which == AlertDialog.BUTTON_NEGATIVE) {
-            dismissAllowingStateLoss();
-        }
-    }
-
-    private List<StoragePathItem> getPathList() {
-        List<StoragePathItem> storagePathItems = new ArrayList<>();
-
-        for (FileStorageUtils.StandardDirectory standardDirectory : FileStorageUtils.StandardDirectory.getStandardDirectories()) {
-            addIfExists(storagePathItems, standardDirectory.getIcon(), getString(standardDirectory.getDisplayName()),
-                        Environment.getExternalStoragePublicDirectory(standardDirectory.getName()).getAbsolutePath());
-        }
-
-        String sdCard = getString(R.string.storage_internal_storage);
-        for (String dir : FileStorageUtils.getStorageDirectories(requireActivity())) {
-            if (internalStoragePaths.contains(dir)) {
-                addIfExists(storagePathItems, R.drawable.ic_sd_grey600, sdCard, dir);
-            } else {
-                addIfExists(storagePathItems, R.drawable.ic_sd_grey600, new File(dir).getName(), dir);
-            }
-        }
-
-        return storagePathItems;
-    }
-
-    private void addIfExists(List<StoragePathItem> storagePathItems, int icon, String name, String path) {
-        File file = new File(path);
-        if (file.exists() && file.canRead()) {
-            storagePathItems.add(new StoragePathItem(icon, name, path));
-        }
-    }
-
-    @Override
-    public void chosenPath(String path) {
-        if (getActivity() != null) {
-            ((StoragePathAdapter.StoragePathAdapterListener) getActivity()).chosenPath(path);
-        }
-        dismissAllowingStateLoss();
-    }
-}

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä