瀏覽代碼

Merge remote-tracking branch 'origin/newDesign-loader-thumbnail' into newDesign-loader-thumbnail

# Conflicts:
#	src/main/res/layout/grid_sync_item.xml
Abdourahamane Boinaidi 5 年之前
父節點
當前提交
0edbd4832b
共有 100 個文件被更改,包括 1324 次插入1284 次删除
  1. 1 0
      .drone.yml
  2. 11 0
      CHANGELOG.md
  3. 2 2
      build.gradle
  4. 5 1
      lint.xml
  5. 1 1
      scripts/analysis/findbugs-results.txt
  6. 1 0
      scripts/updateScreenshots.sh
  7. 2 2
      src/androidTest/java/com/nextcloud/client/jobs/BackgroundJobManagerTest.kt
  8. 1 1
      src/main/AndroidManifest.xml
  9. 7 2
      src/main/java/com/nextcloud/client/etm/EtmViewModel.kt
  10. 4 1
      src/main/java/com/nextcloud/client/etm/pages/EtmBackgroundJobsFragment.kt
  11. 52 5
      src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
  12. 9 0
      src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
  13. 85 8
      src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
  14. 1 0
      src/main/java/com/nextcloud/client/jobs/ContactsBackupWork.kt
  15. 1 0
      src/main/java/com/nextcloud/client/jobs/ContactsImportWork.kt
  16. 2 22
      src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt
  17. 234 0
      src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt
  18. 244 0
      src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt
  19. 143 0
      src/main/java/com/nextcloud/client/jobs/OfflineSyncWork.kt
  20. 1 1
      src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt
  21. 13 43
      src/main/java/com/owncloud/android/MainApp.java
  22. 0 17
      src/main/java/com/owncloud/android/authentication/AuthenticatorUrlUtils.java
  23. 4 1
      src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
  24. 0 273
      src/main/java/com/owncloud/android/jobs/FilesSyncJob.java
  25. 0 278
      src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java
  26. 0 23
      src/main/java/com/owncloud/android/jobs/NCJobCreator.java
  27. 0 185
      src/main/java/com/owncloud/android/jobs/OfflineSyncJob.java
  28. 1 10
      src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
  29. 6 10
      src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
  30. 5 5
      src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  31. 4 6
      src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
  32. 14 0
      src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
  33. 2 0
      src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java
  34. 3 9
      src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
  35. 4 23
      src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
  36. 9 8
      src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java
  37. 13 29
      src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
  38. 1 2
      src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
  39. 6 14
      src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
  40. 16 0
      src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
  41. 1 4
      src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java
  42. 3 0
      src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  43. 1 1
      src/main/java/com/owncloud/android/ui/asynctasks/PhotoSearchTask.java
  44. 6 3
      src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java
  45. 3 23
      src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
  46. 2 11
      src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
  47. 3 3
      src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  48. 1 1
      src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
  49. 4 15
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  50. 2 3
      src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java
  51. 1 5
      src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java
  52. 19 19
      src/main/java/com/owncloud/android/utils/CsrHelper.java
  53. 6 35
      src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
  54. 96 61
      src/main/java/com/owncloud/android/utils/ThemeUtils.java
  55. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_1.png
  56. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_2.png
  57. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_3.png
  58. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_4.png
  59. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_5.png
  60. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_6.png
  61. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_7.png
  62. 二進制
      src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_8.png
  63. 13 0
      src/main/res/drawable-night/favorite.xml
  64. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_1.png
  65. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_2.png
  66. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_3.png
  67. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_4.png
  68. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_5.png
  69. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_6.png
  70. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_7.png
  71. 二進制
      src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_8.png
  72. 0 19
      src/main/res/drawable/actionbar_progress_horizontal.xml
  73. 0 13
      src/main/res/drawable/actionbar_progress_indeterminate_horizontal.xml
  74. 2 2
      src/main/res/drawable/uploader_list_separator.xml
  75. 2 2
      src/main/res/layout/conflict_resolve_dialog.xml
  76. 1 1
      src/main/res/layout/drawer.xml
  77. 8 8
      src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml
  78. 1 1
      src/main/res/layout/fragment_etm_background_jobs.xml
  79. 1 11
      src/main/res/layout/grid_sync_item.xml
  80. 14 16
      src/main/res/layout/toolbar_standard.xml
  81. 6 0
      src/main/res/menu/fragment_etm_background_jobs.xml
  82. 13 0
      src/main/res/values-bg-rBG/strings.xml
  83. 44 0
      src/main/res/values-ca/strings.xml
  84. 5 0
      src/main/res/values-da/strings.xml
  85. 4 0
      src/main/res/values-fa/strings.xml
  86. 1 1
      src/main/res/values-gl/strings.xml
  87. 5 0
      src/main/res/values-hu-rHU/strings.xml
  88. 1 1
      src/main/res/values-lt-rLT/strings.xml
  89. 30 0
      src/main/res/values-lv/strings.xml
  90. 8 0
      src/main/res/values-nb-rNO/strings.xml
  91. 10 6
      src/main/res/values-night/colors.xml
  92. 16 11
      src/main/res/values-nl/strings.xml
  93. 14 0
      src/main/res/values-pt-rPT/strings.xml
  94. 5 0
      src/main/res/values-ru/strings.xml
  95. 5 0
      src/main/res/values-sk-rSK/strings.xml
  96. 38 0
      src/main/res/values-uk/strings.xml
  97. 6 19
      src/main/res/values-v21/styles.xml
  98. 1 0
      src/main/res/values-zh-rCN/strings.xml
  99. 3 0
      src/main/res/values-zh-rTW/strings.xml
  100. 11 7
      src/main/res/values/colors.xml

+ 1 - 0
.drone.yml

@@ -124,6 +124,7 @@ services:
       - su www-data -c "php /var/www/html/occ app:enable -f circles"
       - su www-data -c "php /var/www/html/occ config:app:set circles --value 1 allow_non_ssl_links"
       - su www-data -c "php /var/www/html/occ config:app:set circles --value 1 local_is_non_ssl"
+      - su www-data -c "php /var/www/html/occ config:system:set allow_local_remote_servers --value true --type bool"
       - su www-data -c "php /var/www/html/occ circles:manage:create test public publicCircle"
       - /usr/local/bin/run.sh
 

+ 11 - 0
CHANGELOG.md

@@ -1,3 +1,14 @@
+## 3.11.1 (April, 23, 2020)
+
+- Crash while browsing files
+- auto upload:
+ - fix wrong conflict detection on custom folder
+ - allow to choose default conflict strategy @fodinabor
+ - fix hanging UI after saving
+- open office files online if no local app installed
+
+For a full list, please see https://github.com/nextcloud/android/milestone/47
+
 ## 3.11.0 (March, 26, 2020)
 
 - not enough space dialog @Shagequi

+ 2 - 2
build.gradle

@@ -84,7 +84,7 @@ repositories {
 
 // semantic versioning for version code
 def versionMajor = 3
-def versionMinor = 12
+def versionMinor = 13
 def versionPatch = 0
 def versionBuild = 0 // 0-50=Alpha / 51-98=RC / 90-99=stable
 
@@ -311,7 +311,7 @@ dependencies {
     implementation 'org.greenrobot:eventbus:3.2.0'
     implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.6'
     implementation 'org.lukhnos:nnio:0.2'
-    implementation 'com.madgag.spongycastle:pkix:1.54.0.0'
+    implementation 'org.bouncycastle:bcpkix-jdk15on:1.65'
     implementation 'com.google.code.gson:gson:2.8.6'
     implementation 'com.afollestad:sectioned-recyclerview:0.5.0'
     implementation 'com.github.chrisbanes:PhotoView:2.3.0'

+ 5 - 1
lint.xml

@@ -7,7 +7,7 @@
     <issue id="InvalidPackage">
         <ignore path="**/freemarker-2.*.*.jar" />
         <ignore path="**/nnio-0.2.jar"/>
-        <ignore path="**/pkix-1.54.0.0.jar"/>
+        <ignore path="**/bcpkix-jdk15on-1.65.jar" />
     </issue>
 
     <issue id="UnusedResources">
@@ -44,4 +44,8 @@
         <ignore path="**/values-**/strings.xml" />
     </issue>
 
+    <issue id="TrustAllX509TrustManager">
+        <ignore path="**/bouncycastle/est/jcajce/*.class" />
+    </issue>
+
 </lint>

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-381
+376

+ 1 - 0
scripts/updateScreenshots.sh

@@ -52,6 +52,7 @@ docker exec uiComparison /bin/sh -c "su www-data -c \"cd /var/www/html/apps/circ
 docker exec uiComparison /bin/sh -c "su www-data -c \"php /var/www/html/occ app:enable -f circles\""
 docker exec uiComparison /bin/sh -c "su www-data -c \"php /var/www/html/occ config:app:set circles --value 1 allow_non_ssl_links\""
 docker exec uiComparison /bin/sh -c "su www-data -c \"php /var/www/html/occ config:app:set circles --value 1 local_is_non_ssl\""
+docker exec uiComparison /bin/sh -c "su www-data -c \"php /var/www/html/occ config:system:set allow_local_remote_servers --value true --type bool\""
 docker exec uiComparison /bin/sh -c "su www-data -c \"php /var/www/html/occ circles:manage:create test public publicCircle\""
 docker exec uiComparison /bin/sh -c "/usr/local/bin/run.sh"
 

+ 2 - 2
src/androidTest/java/com/nextcloud/client/jobs/BackgroundJobManagerTest.kt

@@ -210,10 +210,10 @@ class BackgroundJobManagerTest {
         }
 
         @Test
-        fun job_is_unique() {
+        fun job_is_unique_and_replaces_previous_job() {
             verify(workManager).enqueueUniqueWork(
                 eq(BackgroundJobManagerImpl.JOB_CONTENT_OBSERVER),
-                eq(ExistingWorkPolicy.KEEP),
+                eq(ExistingWorkPolicy.REPLACE),
                 argThat(IsOneTimeWorkRequest())
             )
         }

+ 1 - 1
src/main/AndroidManifest.xml

@@ -140,7 +140,7 @@
         <activity android:name=".ui.activities.ActivitiesActivity"
             android:configChanges="orientation|screenSize|keyboardHidden" />
         <activity android:name=".ui.activity.SyncedFoldersActivity"/>
-        <receiver android:name="com.owncloud.android.jobs.MediaFoldersDetectionJob$NotificationReceiver" />
+        <receiver android:name="com.nextcloud.client.jobs.MediaFoldersDetectionWork$NotificationReceiver" />
         <receiver android:name="com.owncloud.android.jobs.NotificationJob$NotificationReceiver" />
         <activity android:name=".ui.activity.UploadFilesActivity" />
         <activity android:name=".ui.activity.ExternalSiteWebView"

+ 7 - 2
src/main/java/com/nextcloud/client/etm/EtmViewModel.kt

@@ -39,6 +39,7 @@ import com.owncloud.android.R
 import com.owncloud.android.lib.common.accounts.AccountUtils
 import javax.inject.Inject
 
+@Suppress("LongParameterList") // Dependencies Injection
 class EtmViewModel @Inject constructor(
     private val defaultPreferences: SharedPreferences,
     private val platformAccountManager: AccountManager,
@@ -158,8 +159,12 @@ class EtmViewModel @Inject constructor(
         backgroundJobManager.cancelAllJobs()
     }
 
-    fun startTestJob() {
-        backgroundJobManager.scheduleTestJob()
+    fun startTestJob(periodic: Boolean) {
+        if (periodic) {
+            backgroundJobManager.scheduleTestJob()
+        } else {
+            backgroundJobManager.startImmediateTestJob()
+        }
     }
 
     fun cancelTestJob() {

+ 4 - 1
src/main/java/com/nextcloud/client/etm/pages/EtmBackgroundJobsFragment.kt

@@ -130,7 +130,10 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
                 vm.pruneJobs(); true
             }
             R.id.etm_background_jobs_start_test -> {
-                vm.startTestJob(); true
+                vm.startTestJob(periodic = false); true
+            }
+            R.id.etm_background_jobs_schedule_test -> {
+                vm.startTestJob(periodic = true); true
             }
             R.id.etm_background_jobs_cancel_test -> {
                 vm.cancelTestJob(); true

+ 52 - 5
src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

@@ -32,26 +32,31 @@ import com.nextcloud.client.core.Clock
 import com.nextcloud.client.device.DeviceInfo
 import com.nextcloud.client.device.PowerManagementService
 import com.nextcloud.client.logger.Logger
+import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.client.preferences.AppPreferences
 import com.owncloud.android.datamodel.ArbitraryDataProvider
 import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.datamodel.UploadsStorageManager
 import javax.inject.Inject
 import javax.inject.Provider
 
 /**
- * This factory is responsible for creating all background jobs and for injecting job dependencies.
+ * This factory is responsible for creating all background jobs and for injecting worker dependencies.
  */
+@Suppress("LongParameterList") // satisfied by DI
 class BackgroundJobFactory @Inject constructor(
     private val logger: Logger,
     private val preferences: AppPreferences,
     private val contentResolver: ContentResolver,
     private val clock: Clock,
-    private val powerManagerService: PowerManagementService,
+    private val powerManagementService: PowerManagementService,
     private val backgroundJobManager: Provider<BackgroundJobManager>,
     private val deviceInfo: DeviceInfo,
     private val accountManager: UserAccountManager,
     private val resources: Resources,
-    private val dataProvider: ArbitraryDataProvider
+    private val dataProvider: ArbitraryDataProvider,
+    private val uploadsStorageManager: UploadsStorageManager,
+    private val connectivityService: ConnectivityService
 ) : WorkerFactory() {
 
     override fun createWorker(
@@ -70,7 +75,10 @@ class BackgroundJobFactory @Inject constructor(
             ContentObserverWork::class -> createContentObserverJob(context, workerParameters, clock)
             ContactsBackupWork::class -> createContactsBackupWork(context, workerParameters)
             ContactsImportWork::class -> createContactsImportWork(context, workerParameters)
-            else -> null // falls back to default factory
+            FilesSyncWork::class -> createFilesSyncWork(context, workerParameters)
+            OfflineSyncWork::class -> createOfflineSyncWork(context, workerParameters)
+            MediaFoldersDetectionWork::class -> createMediaFoldersDetectionWork(context, workerParameters)
+            else -> null // caller falls back to default factory
         }
     }
 
@@ -86,7 +94,7 @@ class BackgroundJobFactory @Inject constructor(
                 context,
                 workerParameters,
                 folderResolver,
-                powerManagerService,
+                powerManagementService,
                 backgroundJobManager.get()
             )
         } else {
@@ -113,4 +121,43 @@ class BackgroundJobFactory @Inject constructor(
             contentResolver
         )
     }
+
+    private fun createFilesSyncWork(context: Context, params: WorkerParameters): FilesSyncWork {
+        return FilesSyncWork(
+            context = context,
+            params = params,
+            resources = resources,
+            contentResolver = contentResolver,
+            userAccountManager = accountManager,
+            preferences = preferences,
+            uploadsStorageManager = uploadsStorageManager,
+            connectivityService = connectivityService,
+            powerManagementService = powerManagementService,
+            clock = clock,
+            backgroundJobManager = backgroundJobManager.get()
+        )
+    }
+
+    private fun createOfflineSyncWork(context: Context, params: WorkerParameters): OfflineSyncWork {
+        return OfflineSyncWork(
+            context = context,
+            params = params,
+            contentResolver = contentResolver,
+            userAccountManager = accountManager,
+            connectivityService = connectivityService,
+            powerManagementService = powerManagementService
+        )
+    }
+
+    private fun createMediaFoldersDetectionWork(context: Context, params: WorkerParameters): MediaFoldersDetectionWork {
+        return MediaFoldersDetectionWork(
+            context,
+            params,
+            resources,
+            contentResolver,
+            accountManager,
+            preferences,
+            clock
+        )
+    }
 }

+ 9 - 0
src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt

@@ -28,6 +28,7 @@ import com.nextcloud.client.account.User
  * This interface allows to control, schedule and monitor all application
  * long-running background tasks, such as periodic checks or synchronization.
  */
+@Suppress("TooManyFunctions") // we expect this implementation to have rich API
 interface BackgroundJobManager {
 
     /**
@@ -89,7 +90,15 @@ interface BackgroundJobManager {
         selectedContacts: IntArray
     ): LiveData<JobInfo?>
 
+    fun schedulePeriodicFilesSyncJob()
+    fun startImmediateFilesSyncJob(skipCustomFolders: Boolean = false, overridePowerSaving: Boolean = false)
+    fun scheduleOfflineSync()
+
+    fun scheduleMediaFoldersDetectionJob()
+    fun startMediaFoldersDetectionJob()
+
     fun scheduleTestJob()
+    fun startImmediateTestJob()
     fun cancelTestJob()
 
     fun pruneJobs()

+ 85 - 8
src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt

@@ -29,6 +29,7 @@ import androidx.work.Data
 import androidx.work.ExistingPeriodicWorkPolicy
 import androidx.work.ExistingWorkPolicy
 import androidx.work.ListenableWorker
+import androidx.work.NetworkType
 import androidx.work.OneTimeWorkRequest
 import androidx.work.Operation
 import androidx.work.PeriodicWorkRequest
@@ -67,6 +68,11 @@ internal class BackgroundJobManagerImpl(
         const val JOB_PERIODIC_CONTACTS_BACKUP = "periodic_contacts_backup"
         const val JOB_IMMEDIATE_CONTACTS_BACKUP = "immediate_contacts_backup"
         const val JOB_IMMEDIATE_CONTACTS_IMPORT = "immediate_contacts_import"
+        const val JOB_PERIODIC_FILES_SYNC = "periodic_files_sync"
+        const val JOB_IMMEDIATE_FILES_SYNC = "immediate_files_sync"
+        const val JOB_PERIODIC_OFFLINE_SYNC = "periodic_offline_sync"
+        const val JOB_PERIODIC_MEDIA_FOLDER_DETECTION = "periodic_media_folder_detection"
+        const val JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION = "immediate_media_folder_detection"
         const val JOB_TEST = "test_job"
 
         const val MAX_CONTENT_TRIGGER_DELAY_MS = 1500L
@@ -81,6 +87,7 @@ internal class BackgroundJobManagerImpl(
         const val INTERVAL_MINUTE = 60L * INTERVAL_SECOND
         const val INTERVAL_HOUR = 60 * INTERVAL_MINUTE
         const val INTERVAL_24H = 24L * INTERVAL_HOUR
+        const val DEFAULT_IMMEDIATE_JOB_DELAY_SEC = 3L
 
         fun formatNameTag(name: String, user: User? = null): String {
             return if (user == null) {
@@ -157,9 +164,16 @@ internal class BackgroundJobManagerImpl(
         jobClass: KClass<out ListenableWorker>,
         jobName: String,
         intervalMins: Long = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES,
+        flexIntervalMins: Long = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES,
         user: User? = null
     ): PeriodicWorkRequest.Builder {
-        val builder = PeriodicWorkRequest.Builder(jobClass.java, intervalMins, TimeUnit.MINUTES)
+        val builder = PeriodicWorkRequest.Builder(
+            jobClass.java,
+            intervalMins,
+            TimeUnit.MINUTES,
+            flexIntervalMins,
+            TimeUnit.MINUTES
+        )
             .addTag(TAG_ALL)
             .addTag(formatNameTag(jobName, user))
             .addTag(formatTimeTag(clock.currentTime))
@@ -203,7 +217,7 @@ internal class BackgroundJobManagerImpl(
             .setConstraints(constrains)
             .build()
 
-        workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.KEEP, request)
+        workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.REPLACE, request)
     }
 
     override fun schedulePeriodicContactsBackup(user: User) {
@@ -211,12 +225,11 @@ internal class BackgroundJobManagerImpl(
             .putString(ContactsBackupWork.ACCOUNT, user.accountName)
             .putBoolean(ContactsBackupWork.FORCE, true)
             .build()
-
         val request = periodicRequestBuilder(
-            ContactsBackupWork::class,
-            JOB_PERIODIC_CONTACTS_BACKUP,
-            INTERVAL_24H,
-            user
+            jobClass = ContactsBackupWork::class,
+            jobName = JOB_PERIODIC_CONTACTS_BACKUP,
+            intervalMins = INTERVAL_24H,
+            user = user
         ).setInputData(data).build()
 
         workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_CONTACTS_BACKUP, ExistingPeriodicWorkPolicy.KEEP, request)
@@ -268,10 +281,74 @@ internal class BackgroundJobManagerImpl(
         return workManager.getJobInfo(request.id)
     }
 
+    override fun schedulePeriodicFilesSyncJob() {
+        val request = periodicRequestBuilder(
+            jobClass = FilesSyncWork::class,
+            jobName = JOB_PERIODIC_FILES_SYNC,
+            intervalMins = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES).build()
+        workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_FILES_SYNC, ExistingPeriodicWorkPolicy.REPLACE, request)
+    }
+
+    override fun startImmediateFilesSyncJob(skipCustomFolders: Boolean, overridePowerSaving: Boolean) {
+        val arguments = Data.Builder()
+            .putBoolean(FilesSyncWork.SKIP_CUSTOM, skipCustomFolders)
+            .putBoolean(FilesSyncWork.OVERRIDE_POWER_SAVING, overridePowerSaving)
+            .build()
+
+        val request = oneTimeRequestBuilder(
+            jobClass = FilesSyncWork::class,
+            jobName = JOB_IMMEDIATE_FILES_SYNC)
+            .setInputData(arguments)
+            .build()
+
+        workManager.enqueueUniqueWork(JOB_IMMEDIATE_FILES_SYNC, ExistingWorkPolicy.KEEP, request)
+    }
+
+    override fun scheduleOfflineSync() {
+        val constrains = Constraints.Builder()
+            .setRequiredNetworkType(NetworkType.UNMETERED)
+            .build()
+
+        val request = periodicRequestBuilder(OfflineSyncWork::class, JOB_PERIODIC_OFFLINE_SYNC)
+            .setConstraints(constrains)
+            .build()
+
+        workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_OFFLINE_SYNC, ExistingPeriodicWorkPolicy.KEEP, request)
+    }
+
+    override fun scheduleMediaFoldersDetectionJob() {
+        val request = periodicRequestBuilder(MediaFoldersDetectionWork::class, JOB_PERIODIC_MEDIA_FOLDER_DETECTION)
+            .build()
+
+        workManager.enqueueUniquePeriodicWork(
+            JOB_PERIODIC_MEDIA_FOLDER_DETECTION,
+            ExistingPeriodicWorkPolicy.KEEP,
+            request
+        )
+    }
+
+    override fun startMediaFoldersDetectionJob() {
+        val request = oneTimeRequestBuilder(MediaFoldersDetectionWork::class, JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION)
+            .build()
+
+        workManager.enqueueUniqueWork(
+            JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION,
+            ExistingWorkPolicy.KEEP,
+            request
+        )
+    }
+
     override fun scheduleTestJob() {
         val request = periodicRequestBuilder(TestJob::class, JOB_TEST)
+            .setInitialDelay(DEFAULT_IMMEDIATE_JOB_DELAY_SEC, TimeUnit.SECONDS)
+            .build()
+        workManager.enqueueUniquePeriodicWork(JOB_TEST, ExistingPeriodicWorkPolicy.REPLACE, request)
+    }
+
+    override fun startImmediateTestJob() {
+        val request = oneTimeRequestBuilder(TestJob::class, JOB_TEST)
             .build()
-        workManager.enqueueUniquePeriodicWork(JOB_TEST, ExistingPeriodicWorkPolicy.KEEP, request)
+        workManager.enqueueUniqueWork(JOB_TEST, ExistingWorkPolicy.REPLACE, request)
     }
 
     override fun cancelTestJob() {

+ 1 - 0
src/main/java/com/nextcloud/client/jobs/ContactsBackupWork.kt

@@ -55,6 +55,7 @@ import java.io.InputStreamReader
 import java.util.ArrayList
 import java.util.Calendar
 
+@Suppress("LongParameterList") // legacy code
 class ContactsBackupWork(
     appContext: Context,
     params: WorkerParameters,

+ 1 - 0
src/main/java/com/nextcloud/client/jobs/ContactsImportWork.kt

@@ -65,6 +65,7 @@ class ContactsImportWork(
         val vCards = ArrayList<VCard>()
 
         var cursor: Cursor? = null
+        @Suppress("TooGenericExceptionCaught") // legacy code
         try {
             val operations = ContactOperations(applicationContext, contactsAccountName, contactsAccountType)
             vCards.addAll(Ezvcard.parse(file).all())

+ 2 - 22
src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt

@@ -24,12 +24,8 @@ import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.work.Worker
 import androidx.work.WorkerParameters
-import com.evernote.android.job.JobRequest
-import com.evernote.android.job.util.support.PersistableBundleCompat
 import com.nextcloud.client.device.PowerManagementService
 import com.owncloud.android.datamodel.SyncedFolderProvider
-import com.owncloud.android.jobs.FilesSyncJob
-import com.owncloud.android.jobs.MediaFoldersDetectionJob
 
 /**
  * This work is triggered when OS detects change in media folders.
@@ -50,7 +46,7 @@ class ContentObserverWork(
     override fun doWork(): Result {
         if (params.triggeredContentUris.size > 0) {
             checkAndStartFileSyncJob()
-            startMediaFolderDetectionJob()
+            backgroundJobManager.startMediaFoldersDetectionJob()
         }
         recheduleSelf()
         return Result.success()
@@ -63,23 +59,7 @@ class ContentObserverWork(
     private fun checkAndStartFileSyncJob() {
         val syncFolders = syncerFolderProvider.countEnabledSyncedFolders() > 0
         if (!powerManagementService.isPowerSavingEnabled && syncFolders) {
-            val persistableBundleCompat = PersistableBundleCompat()
-            persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true)
-
-            JobRequest.Builder(FilesSyncJob.TAG)
-                .startNow()
-                .setExtras(persistableBundleCompat)
-                .setUpdateCurrent(false)
-                .build()
-                .schedule()
+            backgroundJobManager.startImmediateFilesSyncJob(true, false)
         }
     }
-
-    private fun startMediaFolderDetectionJob() {
-        JobRequest.Builder(MediaFoldersDetectionJob.TAG)
-            .startNow()
-            .setUpdateCurrent(false)
-            .build()
-            .schedule()
-    }
 }

+ 234 - 0
src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt

@@ -0,0 +1,234 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author Chris Narkiewicz
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud
+ * Copyright (C) 2020 Chris Narkiewicz
+ *
+ * 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.jobs
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.res.Resources
+import android.os.Build
+import android.os.PowerManager
+import android.os.PowerManager.WakeLock
+import android.text.TextUtils
+import androidx.exifinterface.media.ExifInterface
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.core.Clock
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.network.ConnectivityService
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.MainApp
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.ArbitraryDataProvider
+import com.owncloud.android.datamodel.FilesystemDataProvider
+import com.owncloud.android.datamodel.MediaFolderType
+import com.owncloud.android.datamodel.SyncedFolder
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.datamodel.UploadsStorageManager
+import com.owncloud.android.files.services.FileUploader
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.operations.UploadFileOperation
+import com.owncloud.android.ui.activity.SettingsActivity
+import com.owncloud.android.utils.FileStorageUtils
+import com.owncloud.android.utils.FilesSyncHelper
+import com.owncloud.android.utils.MimeType
+import com.owncloud.android.utils.MimeTypeUtil
+import java.io.File
+import java.text.ParsePosition
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
+
+@Suppress("LongParameterList") // legacy code
+class FilesSyncWork(
+    private val context: Context,
+    params: WorkerParameters,
+    private val resources: Resources,
+    private val contentResolver: ContentResolver,
+    private val userAccountManager: UserAccountManager,
+    private val preferences: AppPreferences,
+    private val uploadsStorageManager: UploadsStorageManager,
+    private val connectivityService: ConnectivityService,
+    private val powerManagementService: PowerManagementService,
+    private val clock: Clock,
+    private val backgroundJobManager: BackgroundJobManager
+) : Worker(context, params) {
+
+    companion object {
+        const val TAG = "FilesSyncJob"
+        const val SKIP_CUSTOM = "skipCustom"
+        const val OVERRIDE_POWER_SAVING = "overridePowerSaving"
+        private const val WAKELOCK_TAG_SEPARATION = ":"
+        private const val WAKELOCK_ACQUIRE_TIMEOUT_MS = 10L * 60L * 1000L
+    }
+
+    override fun doWork(): Result {
+        var wakeLock: WakeLock? = null
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
+                WAKELOCK_TAG_SEPARATION + TAG)
+            wakeLock.acquire(WAKELOCK_ACQUIRE_TIMEOUT_MS)
+        }
+        val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
+        // If we are in power save mode, better to postpone upload
+        if (powerManagementService.isPowerSavingEnabled && !overridePowerSaving) {
+            wakeLock?.release()
+            return Result.success()
+        }
+        val resources = context.resources
+        val lightVersion = resources.getBoolean(R.bool.syncedFolder_light)
+        val skipCustom = inputData.getBoolean(SKIP_CUSTOM, false)
+        FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager,
+            userAccountManager,
+            connectivityService,
+            powerManagementService)
+        FilesSyncHelper.insertAllDBEntries(preferences, clock, skipCustom)
+        // Create all the providers we'll needq
+        val filesystemDataProvider = FilesystemDataProvider(contentResolver)
+        val syncedFolderProvider = SyncedFolderProvider(contentResolver, preferences, clock)
+        val currentLocale = resources.configuration.locale
+        val dateFormat = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale)
+        dateFormat.timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
+        for (syncedFolder in syncedFolderProvider.syncedFolders) {
+            if (syncedFolder.isEnabled && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.type)) {
+                syncFolder(context, resources, lightVersion, filesystemDataProvider, currentLocale, dateFormat,
+                    syncedFolder)
+            }
+        }
+        wakeLock?.release()
+        return Result.success()
+    }
+
+    @Suppress("LongMethod") // legacy code
+    private fun syncFolder(
+        context: Context,
+        resources: Resources,
+        lightVersion: Boolean,
+        filesystemDataProvider: FilesystemDataProvider,
+        currentLocale: Locale,
+        sFormatter: SimpleDateFormat,
+        syncedFolder: SyncedFolder
+    ) {
+        var remotePath: String?
+        var subfolderByDate: Boolean
+        var uploadAction: Int?
+        var needsCharging: Boolean
+        var needsWifi: Boolean
+        var file: File
+        val accountName = syncedFolder.account
+        val optionalUser = userAccountManager.getUser(accountName)
+        if (!optionalUser.isPresent) {
+            return
+        }
+        val user = optionalUser.get()
+        val arbitraryDataProvider = if (lightVersion) {
+            ArbitraryDataProvider(contentResolver)
+        } else {
+            null
+        }
+        val paths = filesystemDataProvider.getFilesForUpload(
+            syncedFolder.localPath,
+            java.lang.Long.toString(syncedFolder.id)
+        )
+        for (path in paths) {
+            file = File(path)
+            val lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter)
+            val mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.absolutePath)
+            if (lightVersion) {
+                needsCharging = resources.getBoolean(R.bool.syncedFolder_light_on_charging)
+                needsWifi = arbitraryDataProvider!!.getBooleanValue(accountName,
+                    SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI)
+                val uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour)
+                uploadAction = getUploadAction(uploadActionString)
+                subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders)
+                remotePath = resources.getString(R.string.syncedFolder_remote_folder)
+            } else {
+                needsCharging = syncedFolder.isChargingOnly
+                needsWifi = syncedFolder.isWifiOnly
+                uploadAction = syncedFolder.uploadAction
+                subfolderByDate = syncedFolder.isSubfolderByDate
+                remotePath = syncedFolder.remotePath
+            }
+            FileUploader.uploadNewFile(
+                context,
+                user.toPlatformAccount(),
+                file.absolutePath,
+                FileStorageUtils.getInstantUploadFilePath(
+                    file,
+                    currentLocale,
+                    remotePath,
+                    syncedFolder.localPath,
+                    lastModificationTime,
+                    subfolderByDate
+                ),
+                uploadAction!!,
+                mimeType,
+                true, // create parent folder if not existent
+                UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
+                needsWifi,
+                needsCharging,
+                FileUploader.NameCollisionPolicy.ASK_USER
+            )
+            filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
+                java.lang.Long.toString(syncedFolder.id))
+        }
+    }
+
+    private fun hasExif(file: File): Boolean {
+        val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath)
+        return MimeType.JPEG.equals(mimeType, ignoreCase = true) || MimeType.TIFF.equals(mimeType, ignoreCase = true)
+    }
+
+    private fun calculateLastModificationTime(
+        file: File,
+        syncedFolder: SyncedFolder,
+        formatter: SimpleDateFormat
+    ): Long {
+        var lastModificationTime = file.lastModified()
+        if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) {
+            @Suppress("TooGenericExceptionCaught") // legacy code
+            try {
+                val exifInterface = ExifInterface(file.absolutePath)
+                val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME)
+                if (!TextUtils.isEmpty(exifDate)) {
+                    val pos = ParsePosition(0)
+                    val dateTime = formatter.parse(exifDate, pos)
+                    lastModificationTime = dateTime.time
+                }
+            } catch (e: Exception) {
+                Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage)
+            }
+        }
+        return lastModificationTime
+    }
+
+    private fun getUploadAction(action: String): Int? {
+        return when (action) {
+            "LOCAL_BEHAVIOUR_FORGET" -> FileUploader.LOCAL_BEHAVIOUR_FORGET
+            "LOCAL_BEHAVIOUR_MOVE" -> FileUploader.LOCAL_BEHAVIOUR_MOVE
+            "LOCAL_BEHAVIOUR_DELETE" -> FileUploader.LOCAL_BEHAVIOUR_DELETE
+            else -> FileUploader.LOCAL_BEHAVIOUR_FORGET
+        }
+    }
+}

+ 244 - 0
src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt

@@ -0,0 +1,244 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author Andy Scherzinger
+ * @author Chris Narkiewicz
+ * Copyright (C) 2018 Mario Danic
+ * Copyright (C) 2018 Andy Scherzinger
+ * Copyright (C) 2020 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.jobs
+
+import android.app.Activity
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.graphics.BitmapFactory
+import android.media.RingtoneManager
+import android.text.TextUtils
+import androidx.core.app.NotificationCompat
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.google.gson.Gson
+import com.nextcloud.client.account.User
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.core.Clock
+import com.nextcloud.client.preferences.AppPreferences
+import com.nextcloud.client.preferences.AppPreferencesImpl
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.ArbitraryDataProvider
+import com.owncloud.android.datamodel.MediaFoldersModel
+import com.owncloud.android.datamodel.MediaProvider
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.jobs.NotificationJob
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.ui.activity.ManageAccountsActivity
+import com.owncloud.android.ui.activity.SyncedFoldersActivity
+import com.owncloud.android.ui.notifications.NotificationUtils
+import com.owncloud.android.utils.ThemeUtils
+import java.util.ArrayList
+import java.util.Random
+
+@Suppress("LongParameterList") // dependencies injection
+class MediaFoldersDetectionWork constructor(
+    private val context: Context,
+    params: WorkerParameters,
+    private val resources: Resources,
+    private val contentResolver: ContentResolver,
+    private val userAccountManager: UserAccountManager,
+    private val preferences: AppPreferences,
+    private val clock: Clock
+) : Worker(context, params) {
+
+    companion object {
+        const val TAG = "MediaFoldersDetectionJob"
+        const val KEY_MEDIA_FOLDER_PATH = "KEY_MEDIA_FOLDER_PATH"
+        const val KEY_MEDIA_FOLDER_TYPE = "KEY_MEDIA_FOLDER_TYPE"
+        private const val ACCOUNT_NAME_GLOBAL = "global"
+        private const val KEY_MEDIA_FOLDERS = "media_folders"
+        const val NOTIFICATION_ID = "NOTIFICATION_ID"
+        private const val DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK"
+    }
+
+    private val randomIdGenerator = Random(clock.currentTime)
+
+    @Suppress("LongMethod", "ComplexMethod", "NestedBlockDepth") // legacy code
+    override fun doWork(): Result {
+        val arbitraryDataProvider = ArbitraryDataProvider(contentResolver)
+        val syncedFolderProvider = SyncedFolderProvider(contentResolver, preferences, clock)
+        val gson = Gson()
+        val arbitraryDataString: String
+        val mediaFoldersModel: MediaFoldersModel
+        val imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true)
+        val videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true)
+        val imageMediaFolderPaths: MutableList<String> = ArrayList()
+        val videoMediaFolderPaths: MutableList<String> = ArrayList()
+        for (imageMediaFolder in imageMediaFolders) {
+            imageMediaFolderPaths.add(imageMediaFolder.absolutePath)
+        }
+        for (videoMediaFolder in videoMediaFolders) {
+            imageMediaFolderPaths.add(videoMediaFolder.absolutePath)
+        }
+        arbitraryDataString = arbitraryDataProvider.getValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS)
+        if (!TextUtils.isEmpty(arbitraryDataString)) {
+            mediaFoldersModel = gson.fromJson(arbitraryDataString, MediaFoldersModel::class.java)
+            // merge new detected paths with already notified ones
+            for (existingImageFolderPath in mediaFoldersModel.imageMediaFolders) {
+                if (!imageMediaFolderPaths.contains(existingImageFolderPath)) {
+                    imageMediaFolderPaths.add(existingImageFolderPath)
+                }
+            }
+            for (existingVideoFolderPath in mediaFoldersModel.videoMediaFolders) {
+                if (!videoMediaFolderPaths.contains(existingVideoFolderPath)) {
+                    videoMediaFolderPaths.add(existingVideoFolderPath)
+                }
+            }
+            // Store updated values
+            arbitraryDataProvider.storeOrUpdateKeyValue(
+                ACCOUNT_NAME_GLOBAL,
+                KEY_MEDIA_FOLDERS,
+                gson.toJson(MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths))
+            )
+            if (preferences.isShowMediaScanNotifications) {
+                imageMediaFolderPaths.removeAll(mediaFoldersModel.imageMediaFolders)
+                videoMediaFolderPaths.removeAll(mediaFoldersModel.videoMediaFolders)
+                if (!imageMediaFolderPaths.isEmpty() || !videoMediaFolderPaths.isEmpty()) {
+                    val allUsers = userAccountManager.allUsers
+                    val activeUsers: MutableList<User> = ArrayList()
+                    for (account in allUsers) {
+                        if (!arbitraryDataProvider.getBooleanValue(account.toPlatformAccount(),
+                                ManageAccountsActivity.PENDING_FOR_REMOVAL)) {
+                            activeUsers.add(account)
+                        }
+                    }
+                    for (user in activeUsers) {
+                        for (imageMediaFolder in imageMediaFolderPaths) {
+                            val folder = syncedFolderProvider.findByLocalPathAndAccount(imageMediaFolder,
+                                user.toPlatformAccount())
+                            if (folder == null) {
+                                val contentTitle = String.format(
+                                    resources.getString(R.string.new_media_folder_detected),
+                                    resources.getString(R.string.new_media_folder_photos)
+                                )
+                                sendNotification(contentTitle,
+                                    imageMediaFolder.substring(imageMediaFolder.lastIndexOf('/') + 1),
+                                    user,
+                                    imageMediaFolder,
+                                    1)
+                            }
+                        }
+                        for (videoMediaFolder in videoMediaFolderPaths) {
+                            val folder = syncedFolderProvider.findByLocalPathAndAccount(videoMediaFolder,
+                                user.toPlatformAccount())
+                            if (folder == null) {
+                                val contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
+                                    context.getString(R.string.new_media_folder_videos))
+                                sendNotification(contentTitle,
+                                    videoMediaFolder.substring(videoMediaFolder.lastIndexOf('/') + 1),
+                                    user,
+                                    videoMediaFolder,
+                                    2)
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            mediaFoldersModel = MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths)
+            arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS,
+                gson.toJson(mediaFoldersModel))
+        }
+        return Result.success()
+    }
+
+    private fun sendNotification(contentTitle: String, subtitle: String, user: User, path: String, type: Int) {
+        val notificationId = randomIdGenerator.nextInt()
+        val context = context
+        val intent = Intent(context, SyncedFoldersActivity::class.java)
+        intent.putExtra(NOTIFICATION_ID, notificationId)
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        intent.putExtra(NotificationJob.KEY_NOTIFICATION_ACCOUNT, user.accountName)
+        intent.putExtra(KEY_MEDIA_FOLDER_PATH, path)
+        intent.putExtra(KEY_MEDIA_FOLDER_TYPE, type)
+        intent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, true)
+        val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
+        val notificationBuilder = NotificationCompat.Builder(
+            context, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL)
+            .setSmallIcon(R.drawable.notification_icon)
+            .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
+            .setColor(ThemeUtils.primaryColor(context))
+            .setSubText(user.accountName)
+            .setContentTitle(contentTitle)
+            .setContentText(subtitle)
+            .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
+            .setAutoCancel(true)
+            .setContentIntent(pendingIntent)
+        val disableDetection = Intent(context, NotificationReceiver::class.java)
+        disableDetection.putExtra(NOTIFICATION_ID, notificationId)
+        disableDetection.action = DISABLE_DETECTION_CLICK
+        val disableIntent = PendingIntent.getBroadcast(
+            context,
+            notificationId,
+            disableDetection,
+            PendingIntent.FLAG_CANCEL_CURRENT
+        )
+        notificationBuilder.addAction(
+            NotificationCompat.Action(
+                R.drawable.ic_close,
+                context.getString(R.string.disable_new_media_folder_detection_notifications),
+                disableIntent
+            )
+        )
+        val configureIntent = PendingIntent.getActivity(
+            context,
+            notificationId,
+            intent,
+            PendingIntent.FLAG_CANCEL_CURRENT
+        )
+        notificationBuilder.addAction(
+            NotificationCompat.Action(
+                R.drawable.ic_settings,
+                context.getString(R.string.configure_new_media_folder_detection_notifications),
+                configureIntent
+            )
+        )
+        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+        notificationManager.notify(notificationId, notificationBuilder.build())
+    }
+
+    class NotificationReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            val action = intent.action
+            val notificationId = intent.getIntExtra(NOTIFICATION_ID, 0)
+            val preferences = AppPreferencesImpl.fromContext(context)
+            if (DISABLE_DETECTION_CLICK == action) {
+                Log_OC.d(this, "Disable media scan notifications")
+                preferences.isShowMediaScanNotifications = false
+                cancel(context, notificationId)
+            }
+        }
+
+        private fun cancel(context: Context, notificationId: Int) {
+            val notificationManager = context.getSystemService(Activity.NOTIFICATION_SERVICE) as NotificationManager
+            notificationManager.cancel(notificationId)
+        }
+    }
+}

+ 143 - 0
src/main/java/com/nextcloud/client/jobs/OfflineSyncWork.kt

@@ -0,0 +1,143 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author Chris Narkiewicz
+ * Copyright (C) 2018 Mario Danic
+ * Copyright (C) 2020 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.jobs
+
+import android.content.ContentResolver
+import android.content.Context
+import android.os.Build
+import android.os.PowerManager
+import android.os.PowerManager.WakeLock
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.account.User
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.network.ConnectivityService
+import com.owncloud.android.MainApp
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.lib.resources.files.CheckEtagRemoteOperation
+import com.owncloud.android.operations.SynchronizeFileOperation
+import com.owncloud.android.utils.FileStorageUtils
+import java.io.File
+
+@Suppress("LongParameterList") // Legacy code
+class OfflineSyncWork constructor(
+    private val context: Context,
+    params: WorkerParameters,
+    private val contentResolver: ContentResolver,
+    private val userAccountManager: UserAccountManager,
+    private val connectivityService: ConnectivityService,
+    private val powerManagementService: PowerManagementService
+) : Worker(context, params) {
+
+    companion object {
+        const val TAG = "OfflineSyncJob"
+        private const val WAKELOCK_TAG_SEPARATION = ":"
+        private const val WAKELOCK_ACQUISITION_TIMEOUT_MS = 10L * 60L * 1000L
+    }
+
+    override fun doWork(): Result {
+        var wakeLock: WakeLock? = null
+        if (!powerManagementService.isPowerSavingEnabled && !connectivityService.isInternetWalled) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+                val wakeLockTag = MainApp.getAuthority() + WAKELOCK_TAG_SEPARATION + TAG
+                wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag)
+                wakeLock.acquire(WAKELOCK_ACQUISITION_TIMEOUT_MS)
+            }
+            val users = userAccountManager.allUsers
+            for (user in users) {
+                val storageManager = FileDataStorageManager(user.toPlatformAccount(), contentResolver)
+                val ocRoot = storageManager.getFileByPath(OCFile.ROOT_PATH)
+                if (ocRoot.storagePath == null) {
+                    break
+                }
+                recursive(File(ocRoot.storagePath), storageManager, user)
+            }
+            wakeLock?.release()
+        }
+        return Result.success()
+    }
+
+    @Suppress("ReturnCount", "ComplexMethod") // legacy code
+    private fun recursive(folder: File, storageManager: FileDataStorageManager, user: User) {
+        val downloadFolder = FileStorageUtils.getSavePath(user.accountName)
+        val folderName = folder.absolutePath.replaceFirst(downloadFolder.toRegex(), "") + OCFile.PATH_SEPARATOR
+        Log_OC.d(TAG, "$folderName: enter")
+        // exit
+        if (folder.listFiles() == null) {
+            return
+        }
+        val ocFolder = storageManager.getFileByPath(folderName)
+        Log_OC.d(TAG, folderName + ": currentEtag: " + ocFolder.etag)
+        // check for etag change, if false, skip
+        val checkEtagOperation = CheckEtagRemoteOperation(ocFolder.remotePath,
+            ocFolder.etagOnServer)
+        val result = checkEtagOperation.execute(user.toPlatformAccount(), context)
+        when (result.code) {
+            ResultCode.ETAG_UNCHANGED -> {
+                Log_OC.d(TAG, "$folderName: eTag unchanged")
+                return
+            }
+            ResultCode.FILE_NOT_FOUND -> {
+                val removalResult = storageManager.removeFolder(ocFolder, true, true)
+                if (!removalResult) {
+                    Log_OC.e(TAG, "removal of " + ocFolder.storagePath + " failed: file not found")
+                }
+                return
+            }
+            ResultCode.ETAG_CHANGED -> Log_OC.d(TAG, "$folderName: eTag changed")
+            else -> Log_OC.d(TAG, "$folderName: eTag changed")
+        }
+        // iterate over downloaded files
+        val files = folder.listFiles { obj: File -> obj.isFile }
+        if (files != null) {
+            for (file in files) {
+                val ocFile = storageManager.getFileByLocalPath(file.path)
+                val synchronizeFileOperation = SynchronizeFileOperation(ocFile.remotePath,
+                    user,
+                    true,
+                    context)
+                synchronizeFileOperation.execute(storageManager, context)
+            }
+        }
+        // recursive into folder
+        val subfolders = folder.listFiles { obj: File -> obj.isDirectory }
+        if (subfolders != null) {
+            for (subfolder in subfolders) {
+                recursive(subfolder, storageManager, user)
+            }
+        }
+        // update eTag
+        @Suppress("TooGenericExceptionCaught") // legacy code
+        try {
+            val updatedEtag = result.data[0] as String
+            ocFolder.etagOnServer = updatedEtag
+            storageManager.saveFile(ocFolder)
+        } catch (e: Exception) {
+            Log_OC.e(TAG, "Failed to update etag on " + folder.absolutePath, e)
+        }
+    }
+}

+ 1 - 1
src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt

@@ -88,7 +88,7 @@ class LogsActivity : ToolbarActivity() {
         (menu.findItem(R.id.action_search).actionView as SearchView).apply {
             setOnQueryTextListener(searchBoxListener)
 
-            ThemeUtils.themeSearchView(this, true, context)
+            ThemeUtils.themeSearchView(this, context)
         }
         return super.onCreateOptionsMenu(menu)
     }

+ 13 - 43
src/main/java/com/owncloud/android/MainApp.java

@@ -41,7 +41,6 @@ import android.text.TextUtils;
 import android.view.WindowManager;
 
 import com.evernote.android.job.JobManager;
-import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.appinfo.AppInfo;
@@ -70,7 +69,6 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.datastorage.DataStorageProvider;
 import com.owncloud.android.datastorage.StoragePoint;
-import com.owncloud.android.jobs.MediaFoldersDetectionJob;
 import com.owncloud.android.jobs.NCJobCreator;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -95,7 +93,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 import javax.net.ssl.SSLContext;
@@ -264,10 +261,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
             new NCJobCreator(
                 getApplicationContext(),
                 accountManager,
-                preferences,
                 uploadsStorageManager,
-                connectivityService,
-                powerManagementService,
                 clock,
                 eventBus,
                 backgroundJobManager
@@ -302,7 +296,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
                 Log_OC.d("Debug", "Failed to disable uri exposure");
             }
         }
-        initSyncOperations(uploadsStorageManager,
+        initSyncOperations(preferences,
+                           uploadsStorageManager,
                            accountManager,
                            connectivityService,
                            powerManagementService,
@@ -311,18 +306,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         initContactsBackup(accountManager, backgroundJobManager);
         notificationChannels();
 
-
-        new JobRequest.Builder(MediaFoldersDetectionJob.TAG)
-            .setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
-            .setUpdateCurrent(true)
-            .build()
-            .schedule();
-
-        new JobRequest.Builder(MediaFoldersDetectionJob.TAG)
-            .startNow()
-            .setUpdateCurrent(false)
-            .build()
-            .schedule();
+        backgroundJobManager.scheduleMediaFoldersDetectionJob();
+        backgroundJobManager.startMediaFoldersDetectionJob();
 
         registerGlobalPassCodeProtection();
     }
@@ -461,11 +446,12 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
     }
 
     public static void initSyncOperations(
+        final AppPreferences preferences,
         final UploadsStorageManager uploadsStorageManager,
         final UserAccountManager accountManager,
         final ConnectivityService connectivityService,
         final PowerManagementService powerManagementService,
-        final BackgroundJobManager jobManager,
+        final BackgroundJobManager backgroundJobManager,
         final Clock clock
     ) {
         updateToAutoUpload();
@@ -477,20 +463,23 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
                                                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                 splitOutAutoUploadEntries(clock);
             } else {
-                AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
                 preferences.setAutoUploadSplitEntriesEnabled(true);
             }
         }
 
-        initiateExistingAutoUploadEntries(clock);
+        if (!preferences.isAutoUploadInitialized()) {
+            backgroundJobManager.startImmediateFilesSyncJob(false, false);
+            preferences.setAutoUploadInit(true);
+        }
 
-        FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, jobManager);
+        FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, backgroundJobManager);
         FilesSyncHelper.restartJobsIfNeeded(
             uploadsStorageManager,
             accountManager,
             connectivityService,
             powerManagementService);
-        FilesSyncHelper.scheduleOfflineSyncIfNeeded();
+
+        backgroundJobManager.scheduleOfflineSync();
 
         ReceiversHelper.registerNetworkChangeReceiver(uploadsStorageManager,
                                                       accountManager,
@@ -755,25 +744,6 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         }
     }
 
-    private static void initiateExistingAutoUploadEntries(Clock clock) {
-        new Thread(() -> {
-            AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
-            if (!preferences.isAutoUploadInitialized()) {
-                SyncedFolderProvider syncedFolderProvider =
-                    new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock);
-
-                for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
-                    if (syncedFolder.isEnabled()) {
-                        FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder, true);
-                    }
-                }
-
-                preferences.setAutoUploadInit(true);
-            }
-
-        }).start();
-    }
-
     private static void cleanOldEntries(Clock clock) {
         // previous versions of application created broken entries in the SyncedFolderProvider
         // database, and this cleans all that and leaves 1 (newest) entry per synced folder

+ 0 - 17
src/main/java/com/owncloud/android/authentication/AuthenticatorUrlUtils.java

@@ -21,11 +21,8 @@
 
 package com.owncloud.android.authentication;
 
-import android.content.Context;
 import android.text.TextUtils;
 
-import com.owncloud.android.lib.resources.status.OwnCloudVersion;
-
 import java.util.Locale;
 
 /**
@@ -40,20 +37,6 @@ public final class AuthenticatorUrlUtils {
     private AuthenticatorUrlUtils() {
     }
 
-    /**
-     * Returns the proper URL path to access the WebDAV interface of an ownCloud server,
-     * according to its version and the authorization method used.
-     *
-     * @param   version         Version of ownCloud server.
-     * @param   authTokenType   Authorization token type, matching some of the AUTH_TOKEN_TYPE_* constants in
-     *                          {@link AccountAuthenticator}.
-     * @return                  WebDAV path for given OC version and authorization method, null if OC version
-     *                          is unknown; versions prior to ownCloud 4 are not supported anymore
-     */
-    public static String getWebdavPath(OwnCloudVersion version, String authTokenType, Context context) {
-            return WEBDAV_PATH_4_0_AND_LATER;
-    }
-
     public static String normalizeUrlSuffix(String url) {
         String normalizedUrl = url;
         if (normalizedUrl.endsWith("/")) {

+ 4 - 1
src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java

@@ -32,6 +32,7 @@ import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.jobs.BackgroundJobManager;
 import com.nextcloud.client.network.ConnectivityService;
+import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -49,6 +50,7 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
 
     private static final String TAG = BootupBroadcastReceiver.class.getSimpleName();
 
+    @Inject AppPreferences preferences;
     @Inject UserAccountManager accountManager;
     @Inject UploadsStorageManager uploadsStorageManager;
     @Inject ConnectivityService connectivityService;
@@ -67,7 +69,8 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
         AndroidInjection.inject(this, context);
 
         if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
-            MainApp.initSyncOperations(uploadsStorageManager,
+            MainApp.initSyncOperations(preferences,
+                                       uploadsStorageManager,
                                        accountManager,
                                        connectivityService,
                                        powerManagementService,

+ 0 - 273
src/main/java/com/owncloud/android/jobs/FilesSyncJob.java

@@ -1,273 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author Chris Narkiewicz
- * Copyright (C) 2017 Mario Danic
- * Copyright (C) 2017 Nextcloud
- * Copyright (C) 2919 Chris Narkiewicz
- *
- * 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.jobs;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Build;
-import android.os.PowerManager;
-import android.text.TextUtils;
-
-import com.evernote.android.job.Job;
-import com.evernote.android.job.util.support.PersistableBundleCompat;
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.core.Clock;
-import com.nextcloud.client.device.PowerManagementService;
-import com.nextcloud.client.network.ConnectivityService;
-import com.nextcloud.client.preferences.AppPreferences;
-import com.nextcloud.java.util.Optional;
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.ArbitraryDataProvider;
-import com.owncloud.android.datamodel.FilesystemDataProvider;
-import com.owncloud.android.datamodel.MediaFolderType;
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.datamodel.UploadsStorageManager;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.operations.UploadFileOperation;
-import com.owncloud.android.ui.activity.SettingsActivity;
-import com.owncloud.android.utils.FileStorageUtils;
-import com.owncloud.android.utils.FilesSyncHelper;
-import com.owncloud.android.utils.MimeType;
-import com.owncloud.android.utils.MimeTypeUtil;
-
-import java.io.File;
-import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import androidx.annotation.NonNull;
-import androidx.exifinterface.media.ExifInterface;
-
-/*
-    Job that:
-        - restarts existing jobs if required
-        - finds new and modified files since we last run this
-        - creates upload tasks
- */
-public class FilesSyncJob extends Job {
-    public static final String TAG = "FilesSyncJob";
-    public static final String SKIP_CUSTOM = "skipCustom";
-    public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
-    private static final String WAKELOCK_TAG_SEPARATION = ":";
-
-    private final UserAccountManager userAccountManager;
-    private final AppPreferences preferences;
-    private final UploadsStorageManager uploadsStorageManager;
-    private final ConnectivityService connectivityService;
-    private final PowerManagementService powerManagementService;
-    private final Clock clock;
-
-    FilesSyncJob(final UserAccountManager userAccountManager,
-                        final AppPreferences preferences,
-                        final UploadsStorageManager uploadsStorageManager,
-                        final ConnectivityService connectivityService,
-                        final PowerManagementService powerManagementService,
-                        final Clock clock) {
-        this.userAccountManager = userAccountManager;
-        this.preferences = preferences;
-        this.uploadsStorageManager = uploadsStorageManager;
-        this.connectivityService = connectivityService;
-        this.powerManagementService = powerManagementService;
-        this.clock = clock;
-    }
-
-    @NonNull
-    @Override
-    protected Result onRunJob(@NonNull Params params) {
-        final Context context = MainApp.getAppContext();
-        PowerManager.WakeLock wakeLock = null;
-
-        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
-            PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
-                    WAKELOCK_TAG_SEPARATION + TAG);
-            wakeLock.acquire(10 * 60 * 1000);
-        }
-
-        PersistableBundleCompat bundle = params.getExtras();
-        final boolean overridePowerSaving = bundle.getBoolean(OVERRIDE_POWER_SAVING, false);
-
-        // If we are in power save mode, better to postpone upload
-        if (powerManagementService.isPowerSavingEnabled() && !overridePowerSaving) {
-            if (wakeLock != null) {
-                wakeLock.release();
-            }
-            return Result.SUCCESS;
-        }
-
-        Resources resources = MainApp.getAppContext().getResources();
-        boolean lightVersion = resources.getBoolean(R.bool.syncedFolder_light);
-
-        final boolean skipCustom = bundle.getBoolean(SKIP_CUSTOM, false);
-        FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager,
-                                            userAccountManager,
-                                            connectivityService,
-                                            powerManagementService);
-        FilesSyncHelper.insertAllDBEntries(preferences, clock, skipCustom, false);
-
-        // Create all the providers we'll need
-        final ContentResolver contentResolver = context.getContentResolver();
-        final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
-        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
-
-        Locale currentLocale = context.getResources().getConfiguration().locale;
-        SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale);
-        sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
-
-        for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
-            if ((syncedFolder.isEnabled()) && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.getType())) {
-                syncFolder(context, resources, lightVersion, filesystemDataProvider, currentLocale, sFormatter,
-                           syncedFolder);
-            }
-        }
-
-        if (wakeLock != null) {
-            wakeLock.release();
-        }
-
-        return Result.SUCCESS;
-    }
-
-    private void syncFolder(
-        Context context,
-        Resources resources,
-        boolean lightVersion,
-        FilesystemDataProvider filesystemDataProvider,
-        Locale currentLocale,
-        SimpleDateFormat sFormatter,
-        SyncedFolder syncedFolder
-    ) {
-        String accountName = syncedFolder.getAccount();
-        Optional<User> optionalUser = userAccountManager.getUser(accountName);
-        if (!optionalUser.isPresent()) {
-            return;
-        }
-        User user = optionalUser.get();
-
-        ArbitraryDataProvider arbitraryDataProvider = null;
-        if (lightVersion) {
-            arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
-        }
-
-        String remotePath;
-        boolean subfolderByDate;
-        Integer uploadAction;
-        FileUploader.NameCollisionPolicy nameCollisionPolicy;
-        boolean needsCharging;
-        boolean needsWifi;
-        File file;
-        for (String path : filesystemDataProvider.getFilesForUpload(syncedFolder.getLocalPath(),
-                Long.toString(syncedFolder.getId()))) {
-            file = new File(path);
-            Long lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter);
-            String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath());
-
-            if (lightVersion) {
-                needsCharging = resources.getBoolean(R.bool.syncedFolder_light_on_charging);
-                needsWifi = arbitraryDataProvider.getBooleanValue(accountName,
-                                                                  SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI);
-                String uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour);
-                uploadAction = getUploadAction(uploadActionString);
-                nameCollisionPolicy = FileUploader.NameCollisionPolicy.ASK_USER;
-                subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders);
-                remotePath = resources.getString(R.string.syncedFolder_remote_folder);
-            } else {
-                needsCharging = syncedFolder.isChargingOnly();
-                needsWifi = syncedFolder.isWifiOnly();
-                uploadAction = syncedFolder.getUploadAction();
-                nameCollisionPolicy = FileUploader.NameCollisionPolicy.deserialize(
-                        syncedFolder.getNameCollisionPolicy());
-                subfolderByDate = syncedFolder.isSubfolderByDate();
-                remotePath = syncedFolder.getRemotePath();
-            }
-
-            FileUploader.uploadNewFile(
-                context,
-                user.toPlatformAccount(),
-                file.getAbsolutePath(),
-                FileStorageUtils.getInstantUploadFilePath(
-                        file,
-                        currentLocale,
-                        remotePath,
-                        syncedFolder.getLocalPath(),
-                        lastModificationTime,
-                        subfolderByDate),
-                uploadAction,
-                mimeType,
-                true,           // create parent folder if not existent
-                UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
-                needsWifi,
-                needsCharging,
-                nameCollisionPolicy
-            );
-
-            filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
-                                                                       Long.toString(syncedFolder.getId()));
-        }
-    }
-
-    private Long calculateLastModificationTime(File file, SyncedFolder syncedFolder, SimpleDateFormat formatter) {
-        Long lastModificationTime = file.lastModified();
-
-        if (MediaFolderType.IMAGE == syncedFolder.getType()) {
-            String mimeTypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath());
-            if (MimeType.JPEG.equalsIgnoreCase(mimeTypeString)
-                    || MimeType.TIFF.equalsIgnoreCase(mimeTypeString)) {
-                try {
-                    ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
-                    String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
-                    if (!TextUtils.isEmpty(exifDate)) {
-                        ParsePosition pos = new ParsePosition(0);
-                        Date dateTime = formatter.parse(exifDate, pos);
-                        lastModificationTime = dateTime.getTime();
-                    }
-                } catch (Exception e) {
-                    Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage());
-                }
-            }
-        }
-
-        return lastModificationTime;
-    }
-
-    private Integer getUploadAction(String action) {
-        switch (action) {
-            case "LOCAL_BEHAVIOUR_FORGET":
-                return FileUploader.LOCAL_BEHAVIOUR_FORGET;
-            case "LOCAL_BEHAVIOUR_MOVE":
-                return FileUploader.LOCAL_BEHAVIOUR_MOVE;
-            case "LOCAL_BEHAVIOUR_DELETE":
-                return FileUploader.LOCAL_BEHAVIOUR_DELETE;
-            default:
-                return FileUploader.LOCAL_BEHAVIOUR_FORGET;
-        }
-    }
-}

+ 0 - 278
src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java

@@ -1,278 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * @author Chris Narkiewicz
- * Copyright (C) 2018 Mario Danic
- * Copyright (C) 2018 Andy Scherzinger
- * 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.owncloud.android.jobs;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.BitmapFactory;
-import android.media.RingtoneManager;
-import android.text.TextUtils;
-
-import com.evernote.android.job.Job;
-import com.google.gson.Gson;
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.core.Clock;
-import com.nextcloud.client.preferences.AppPreferences;
-import com.nextcloud.client.preferences.AppPreferencesImpl;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.ArbitraryDataProvider;
-import com.owncloud.android.datamodel.MediaFolder;
-import com.owncloud.android.datamodel.MediaFoldersModel;
-import com.owncloud.android.datamodel.MediaProvider;
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.ui.activity.ManageAccountsActivity;
-import com.owncloud.android.ui.activity.SyncedFoldersActivity;
-import com.owncloud.android.ui.notifications.NotificationUtils;
-import com.owncloud.android.utils.ThemeUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-@SuppressFBWarnings(value = "PREDICTABLE_RANDOM", justification = "Only used for notification id.")
-public class MediaFoldersDetectionJob extends Job {
-    public static final String TAG = "MediaFoldersDetectionJob";
-
-    public static final String KEY_MEDIA_FOLDER_PATH = "KEY_MEDIA_FOLDER_PATH";
-    public static final String KEY_MEDIA_FOLDER_TYPE = "KEY_MEDIA_FOLDER_TYPE";
-
-    private static final String ACCOUNT_NAME_GLOBAL = "global";
-    private static final String KEY_MEDIA_FOLDERS = "media_folders";
-    public static final String NOTIFICATION_ID = "NOTIFICATION_ID";
-
-    private static final String DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK";
-
-    private final UserAccountManager userAccountManager;
-    private final Clock clock;
-    private final Random randomId = new Random();
-
-    MediaFoldersDetectionJob(UserAccountManager accountManager, Clock clock) {
-        this.userAccountManager = accountManager;
-        this.clock = clock;
-    }
-
-    @NonNull
-    @Override
-    protected Result onRunJob(@NonNull Params params) {
-        Context context = getContext();
-        ContentResolver contentResolver = context.getContentResolver();
-        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
-        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver,
-                                                                             AppPreferencesImpl.fromContext(context),
-                                                                             clock);
-        Gson gson = new Gson();
-        String arbitraryDataString;
-        MediaFoldersModel mediaFoldersModel;
-
-        List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true);
-        List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true);
-
-        List<String> imageMediaFolderPaths = new ArrayList<>();
-        List<String> videoMediaFolderPaths = new ArrayList<>();
-
-        for (MediaFolder imageMediaFolder : imageMediaFolders) {
-            imageMediaFolderPaths.add(imageMediaFolder.absolutePath);
-        }
-
-        for (MediaFolder videoMediaFolder : videoMediaFolders) {
-            imageMediaFolderPaths.add(videoMediaFolder.absolutePath);
-        }
-
-        arbitraryDataString = arbitraryDataProvider.getValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS);
-        if (!TextUtils.isEmpty(arbitraryDataString)) {
-            mediaFoldersModel = gson.fromJson(arbitraryDataString, MediaFoldersModel.class);
-
-            // merge new detected paths with already notified ones
-            for (String existingImageFolderPath : mediaFoldersModel.getImageMediaFolders()) {
-                if (!imageMediaFolderPaths.contains(existingImageFolderPath)) {
-                    imageMediaFolderPaths.add(existingImageFolderPath);
-                }
-            }
-
-            for (String existingVideoFolderPath : mediaFoldersModel.getVideoMediaFolders()) {
-                if (!videoMediaFolderPaths.contains(existingVideoFolderPath)) {
-                    videoMediaFolderPaths.add(existingVideoFolderPath);
-                }
-            }
-
-            // Store updated values
-            arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS, gson.toJson(new
-                MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths)));
-
-            final AppPreferences preferences = AppPreferencesImpl.fromContext(getContext());
-            if (preferences.isShowMediaScanNotifications()) {
-                imageMediaFolderPaths.removeAll(mediaFoldersModel.getImageMediaFolders());
-                videoMediaFolderPaths.removeAll(mediaFoldersModel.getVideoMediaFolders());
-
-                if (!imageMediaFolderPaths.isEmpty() || !videoMediaFolderPaths.isEmpty()) {
-                    List<User> allUsers = userAccountManager.getAllUsers();
-                    List<User> activeUsers = new ArrayList<>();
-                    for (User account : allUsers) {
-                        if (!arbitraryDataProvider.getBooleanValue(account.toPlatformAccount(),
-                                                                   ManageAccountsActivity.PENDING_FOR_REMOVAL)) {
-                            activeUsers.add(account);
-                        }
-                    }
-
-                    for (User user : activeUsers) {
-                        for (String imageMediaFolder : imageMediaFolderPaths) {
-                            final SyncedFolder folder = syncedFolderProvider.findByLocalPathAndAccount(imageMediaFolder,
-                                                                                                       user.toPlatformAccount());
-                            if (folder == null) {
-                                String contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
-                                                                    context.getString(R.string.new_media_folder_photos));
-                                sendNotification(contentTitle,
-                                                imageMediaFolder.substring(imageMediaFolder.lastIndexOf('/') + 1),
-                                                user,
-                                                imageMediaFolder,
-                                                 1);
-                            }
-                        }
-
-                        for (String videoMediaFolder : videoMediaFolderPaths) {
-                            final SyncedFolder folder = syncedFolderProvider.findByLocalPathAndAccount(videoMediaFolder,
-                                                                                                       user.toPlatformAccount());
-                            if (folder == null) {
-                                String contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
-                                                                    context.getString(R.string.new_media_folder_videos));
-                                sendNotification(contentTitle,
-                                                 videoMediaFolder.substring(videoMediaFolder.lastIndexOf('/') + 1),
-                                                 user,
-                                                 videoMediaFolder,
-                                                 2);
-                            }
-                        }
-                    }
-                }
-            }
-
-        } else {
-            mediaFoldersModel = new MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths);
-            arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS,
-                gson.toJson(mediaFoldersModel));
-        }
-
-        return Result.SUCCESS;
-    }
-
-    private void sendNotification(String contentTitle, String subtitle, User user, String path, int type) {
-        int notificationId = randomId.nextInt();
-
-        Context context = getContext();
-        Intent intent = new Intent(getContext(), SyncedFoldersActivity.class);
-        intent.putExtra(NOTIFICATION_ID, notificationId);
-        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        intent.putExtra(NotificationJob.KEY_NOTIFICATION_ACCOUNT, user.getAccountName());
-        intent.putExtra(KEY_MEDIA_FOLDER_PATH, path);
-        intent.putExtra(KEY_MEDIA_FOLDER_TYPE, type);
-        intent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, true);
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
-
-        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
-            context, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL)
-            .setSmallIcon(R.drawable.notification_icon)
-            .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.notification_icon))
-            .setColor(ThemeUtils.primaryColor(getContext()))
-            .setSubText(user.getAccountName())
-            .setContentTitle(contentTitle)
-            .setContentText(subtitle)
-            .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
-            .setAutoCancel(true)
-            .setContentIntent(pendingIntent);
-
-        Intent disableDetection = new Intent(context, NotificationReceiver.class);
-        disableDetection.putExtra(NOTIFICATION_ID, notificationId);
-        disableDetection.setAction(DISABLE_DETECTION_CLICK);
-
-        PendingIntent disableIntent = PendingIntent.getBroadcast(
-            context,
-            notificationId,
-            disableDetection,
-            PendingIntent.FLAG_CANCEL_CURRENT
-        );
-        notificationBuilder.addAction(
-            new NotificationCompat.Action(
-                R.drawable.ic_close,
-                context.getString(R.string.disable_new_media_folder_detection_notifications),
-                disableIntent
-            )
-        );
-
-        PendingIntent configureIntent = PendingIntent.getActivity(
-            context,
-            notificationId,
-            intent,
-            PendingIntent.FLAG_CANCEL_CURRENT
-        );
-        notificationBuilder.addAction(
-            new NotificationCompat.Action(
-                R.drawable.ic_settings,
-                context.getString(R.string.configure_new_media_folder_detection_notifications),
-                configureIntent
-            )
-        );
-
-        NotificationManager notificationManager = (NotificationManager)
-            context.getSystemService(Context.NOTIFICATION_SERVICE);
-
-        if (notificationManager != null) {
-            notificationManager.notify(notificationId, notificationBuilder.build());
-        }
-    }
-
-
-    public static class NotificationReceiver extends BroadcastReceiver {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            int notificationId = intent.getIntExtra(NOTIFICATION_ID, 0);
-            final AppPreferences preferences = AppPreferencesImpl.fromContext(context);
-
-            if (DISABLE_DETECTION_CLICK.equals(action)) {
-                Log_OC.d(this, "Disable media scan notifications");
-                preferences.setShowMediaScanNotifications(false);
-                cancel(context, notificationId);
-            }
-        }
-
-        private void cancel(Context context, int notificationId) {
-            NotificationManager notificationManager =
-                (NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
-            notificationManager.cancel(notificationId);
-        }
-    }
-}

+ 0 - 23
src/main/java/com/owncloud/android/jobs/NCJobCreator.java

@@ -30,10 +30,7 @@ import com.evernote.android.job.Job;
 import com.evernote.android.job.JobCreator;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
-import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.jobs.BackgroundJobManager;
-import com.nextcloud.client.network.ConnectivityService;
-import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 
 import org.greenrobot.eventbus.EventBus;
@@ -48,10 +45,7 @@ public class NCJobCreator implements JobCreator {
 
     private final Context context;
     private final UserAccountManager accountManager;
-    private final AppPreferences preferences;
     private final UploadsStorageManager uploadsStorageManager;
-    private final ConnectivityService connectivityService;
-    private final PowerManagementService powerManagementService;
     private final Clock clock;
     private final EventBus eventBus;
     private final BackgroundJobManager backgroundJobManager;
@@ -59,20 +53,14 @@ public class NCJobCreator implements JobCreator {
     public NCJobCreator(
         Context context,
         UserAccountManager accountManager,
-        AppPreferences preferences,
         UploadsStorageManager uploadsStorageManager,
-        ConnectivityService connectivityServices,
-        PowerManagementService powerManagementService,
         Clock clock,
         EventBus eventBus,
         BackgroundJobManager backgroundJobManager
     ) {
         this.context = context;
         this.accountManager = accountManager;
-        this.preferences = preferences;
         this.uploadsStorageManager = uploadsStorageManager;
-        this.connectivityService = connectivityServices;
-        this.powerManagementService = powerManagementService;
         this.clock = clock;
         this.eventBus = eventBus;
         this.backgroundJobManager = backgroundJobManager;
@@ -87,19 +75,8 @@ public class NCJobCreator implements JobCreator {
                                              backgroundJobManager,
                                              clock,
                                              eventBus);
-            case FilesSyncJob.TAG:
-                return new FilesSyncJob(accountManager,
-                                        preferences,
-                                        uploadsStorageManager,
-                                        connectivityService,
-                                        powerManagementService,
-                                        clock);
-            case OfflineSyncJob.TAG:
-                return new OfflineSyncJob(accountManager, connectivityService, powerManagementService);
             case NotificationJob.TAG:
                 return new NotificationJob(context, accountManager);
-            case MediaFoldersDetectionJob.TAG:
-                return new MediaFoldersDetectionJob(accountManager, clock);
             default:
                 return null;
         }

+ 0 - 185
src/main/java/com/owncloud/android/jobs/OfflineSyncJob.java

@@ -1,185 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author Chris Narkiewicz
- * Copyright (C) 2018 Mario Danic
- * 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.owncloud.android.jobs;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.PowerManager;
-
-import com.evernote.android.job.Job;
-import com.evernote.android.job.JobManager;
-import com.evernote.android.job.JobRequest;
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.device.PowerManagementService;
-import com.nextcloud.client.network.ConnectivityService;
-import com.owncloud.android.MainApp;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.files.CheckEtagRemoteOperation;
-import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.utils.FileStorageUtils;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-import androidx.annotation.NonNull;
-
-import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
-import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
-
-public class OfflineSyncJob extends Job {
-    public static final String TAG = "OfflineSyncJob";
-
-    private static final String WAKELOCK_TAG_SEPARATION = ":";
-    private final UserAccountManager userAccountManager;
-    private final ConnectivityService connectivityService;
-    private final PowerManagementService powerManagementService;
-
-    OfflineSyncJob(UserAccountManager userAccountManager, ConnectivityService connectivityService, PowerManagementService powerManagementService) {
-        this.userAccountManager = userAccountManager;
-        this.connectivityService = connectivityService;
-        this.powerManagementService = powerManagementService;
-    }
-
-    @NonNull
-    @Override
-    protected Result onRunJob(@NonNull Params params) {
-        final Context context = getContext();
-
-        PowerManager.WakeLock wakeLock = null;
-        if (!powerManagementService.isPowerSavingEnabled() &&
-                connectivityService.getActiveNetworkType() == JobRequest.NetworkType.UNMETERED &&
-                !connectivityService.isInternetWalled()) {
-            Set<Job> jobs = JobManager.instance().getAllJobsForTag(TAG);
-            for (Job job : jobs) {
-                if (!job.isFinished() && !job.equals(this)) {
-                    return Result.SUCCESS;
-                }
-            }
-
-            if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
-                PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
-                if (powerManager != null) {
-                    wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
-                        WAKELOCK_TAG_SEPARATION + TAG);
-                    wakeLock.acquire(10 * 60 * 1000);
-                }
-            }
-
-            List<User> users = userAccountManager.getAllUsers();
-
-            for (User user : users) {
-                FileDataStorageManager storageManager = new FileDataStorageManager(user.toPlatformAccount(),
-                        getContext().getContentResolver());
-
-                OCFile ocRoot = storageManager.getFileByPath(ROOT_PATH);
-
-                if (ocRoot.getStoragePath() == null) {
-                    break;
-                }
-
-                recursive(new File(ocRoot.getStoragePath()), storageManager, user);
-            }
-
-            if (wakeLock != null) {
-                wakeLock.release();
-            }
-        }
-
-        return Result.SUCCESS;
-    }
-
-    private void recursive(File folder, FileDataStorageManager storageManager, User user) {
-        String downloadFolder = FileStorageUtils.getSavePath(user.getAccountName());
-        String folderName = folder.getAbsolutePath().replaceFirst(downloadFolder, "") + PATH_SEPARATOR;
-        Log_OC.d(TAG, folderName + ": enter");
-
-        // exit
-        if (folder.listFiles() == null) {
-            return;
-        }
-
-        OCFile ocFolder = storageManager.getFileByPath(folderName);
-        Log_OC.d(TAG, folderName + ": currentEtag: " + ocFolder.getEtag());
-
-        // check for etag change, if false, skip
-        CheckEtagRemoteOperation checkEtagOperation = new CheckEtagRemoteOperation(ocFolder.getRemotePath(),
-                                                                                   ocFolder.getEtagOnServer());
-        RemoteOperationResult result = checkEtagOperation.execute(user.toPlatformAccount(), getContext());
-
-        // eTag changed, sync file
-        switch (result.getCode()) {
-            case ETAG_UNCHANGED:
-                Log_OC.d(TAG, folderName + ": eTag unchanged");
-                return;
-
-            case FILE_NOT_FOUND:
-                boolean removalResult = storageManager.removeFolder(ocFolder, true, true);
-                if (!removalResult) {
-                    Log_OC.e(TAG, "removal of " + ocFolder.getStoragePath() + " failed: file not found");
-                }
-                return;
-
-            default:
-            case ETAG_CHANGED:
-                Log_OC.d(TAG, folderName + ": eTag changed");
-                break;
-        }
-
-        // iterate over downloaded files
-        File[] files = folder.listFiles(File::isFile);
-
-        if (files != null) {
-            for (File file : files) {
-                OCFile ocFile = storageManager.getFileByLocalPath(file.getPath());
-                SynchronizeFileOperation synchronizeFileOperation = new SynchronizeFileOperation(ocFile.getRemotePath(),
-                                                                                                 user,
-                                                                                                 true,
-                                                                                                 getContext());
-                synchronizeFileOperation.execute(storageManager, getContext());
-            }
-        }
-
-        // recursive into folder
-        File[] subfolders = folder.listFiles(File::isDirectory);
-
-        if (subfolders != null) {
-            for (File subfolder : subfolders) {
-                recursive(subfolder, storageManager, user);
-            }
-        }
-
-        // update eTag
-        try {
-            String updatedEtag = (String) result.getData().get(0);
-            ocFolder.setEtagOnServer(updatedEtag);
-            storageManager.saveFile(ocFolder);
-        } catch (Exception e) {
-            Log_OC.e(TAG, "Failed to update etag on " + folder.getAbsolutePath(), e);
-        }
-    }
-}

+ 1 - 10
src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java

@@ -116,7 +116,7 @@ public class ActivitiesActivity extends FileActivity implements ActivityListInte
         // setup toolbar
         setupToolbar();
 
-        onCreateSwipeToRefresh(swipeListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
 
         // setup drawer
         setupDrawer(R.id.nav_activity);
@@ -139,15 +139,6 @@ public class ActivitiesActivity extends FileActivity implements ActivityListInte
         emptyContentHeadline.setVisibility(View.INVISIBLE);
     }
 
-    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
-        int primaryColor = ThemeUtils.primaryColor(this);
-        int darkColor = ThemeUtils.primaryDarkColor(this);
-        int accentColor = ThemeUtils.primaryAccentColor(this);
-
-        // Colors in animations
-        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
-    }
-
     @Override
     public void onDestroy() {
         super.onDestroy();

+ 6 - 10
src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java

@@ -30,7 +30,6 @@ import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.graphics.PorterDuff;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
@@ -292,6 +291,9 @@ public abstract class DrawerActivity extends ToolbarActivity
         mDrawerLayout.addDrawerListener(mDrawerToggle);
         mDrawerToggle.setDrawerIndicatorEnabled(true);
         mDrawerToggle.setDrawerSlideAnimationEnabled(true);
+        Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
+        mDrawerToggle.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.appBarPrimaryFontColor(this)));
+        mDrawerToggle.getDrawerArrowDrawable().setColor(ThemeUtils.appBarPrimaryFontColor(this));
     }
 
     /**
@@ -302,7 +304,6 @@ public abstract class DrawerActivity extends ToolbarActivity
         mAccountEndAccountAvatar = (ImageView) findNavigationViewChildById(R.id.drawer_account_end);
 
         mAccountChooserToggle = (ImageView) findNavigationViewChildById(R.id.drawer_account_chooser_toggle);
-        mAccountChooserToggle.setColorFilter(ThemeUtils.fontColor(this, true));
 
         if (getResources().getBoolean(R.bool.allow_profile_click)) {
             mAccountChooserToggle.setImageResource(R.drawable.ic_down);
@@ -773,17 +774,12 @@ public abstract class DrawerActivity extends ToolbarActivity
         super.updateActionBarTitleAndHomeButton(chosenFile);
 
         // set home button properties
-        if (mDrawerToggle != null && chosenFile != null) {
-            if (isRoot(chosenFile)) {
+        if (mDrawerToggle != null) {
+            if (chosenFile != null && isRoot(chosenFile)) {
                 mDrawerToggle.setDrawerIndicatorEnabled(true);
             } else {
                 mDrawerToggle.setDrawerIndicatorEnabled(false);
-                Drawable upArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
-                upArrow.setColorFilter(ThemeUtils.fontColor(this), PorterDuff.Mode.SRC_ATOP);
-                mDrawerToggle.setHomeAsUpIndicator(upArrow);
             }
-        } else if (mDrawerToggle != null) {
-            mDrawerToggle.setDrawerIndicatorEnabled(false);
         }
     }
 
@@ -995,7 +991,7 @@ public abstract class DrawerActivity extends ToolbarActivity
                     }
                     menuItem.setTitle(Html.fromHtml(
                         "<font color='"
-                            + ThemeUtils.colorToHexString(ContextCompat.getColor(this, R.color.text_color))
+                            + ThemeUtils.colorToHexString(ContextCompat.getColor(this, R.color.drawer_text_color))
                             + "'>" + menuItem.getTitle()
                             + "</font>"));
                 }

+ 5 - 5
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -328,7 +328,7 @@ public class FileDisplayActivity extends FileActivity
             syncAndUpdateFolder(true);
         }
 
-        setIndeterminate(mSyncInProgress);
+        showProgressBar(mSyncInProgress);
         // always AFTER setContentView(...) in onCreate(); to work around bug in its implementation
 
         upgradeNotificationForInstantUpload();
@@ -783,7 +783,7 @@ public class FileDisplayActivity extends FileActivity
         searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
         searchMenuItem.setVisible(false);
 
-        ThemeUtils.themeSearchView(searchView, true, this);
+        ThemeUtils.themeSearchView(searchView, this);
 
         // populate list of menu items to show/hide when drawer is opened/closed
         mDrawerMenuItemstoShowHideList = new ArrayList<>(4);
@@ -1394,7 +1394,7 @@ public class FileDisplayActivity extends FileActivity
                         DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
 
                         Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
-                        setIndeterminate(mSyncInProgress);
+                        showProgressBar(mSyncInProgress);
 
                         setBackgroundText();
                     }
@@ -1513,7 +1513,7 @@ public class FileDisplayActivity extends FileActivity
                     }
                 }
 
-                setIndeterminate(false);
+                showProgressBar(false);
 
             } finally {
                 if (intent != null) {
@@ -2227,7 +2227,7 @@ public class FileDisplayActivity extends FileActivity
                                         null
                                 );
 
-                                setIndeterminate(true);
+                                showProgressBar(true);
 
                                 setBackgroundText();
 

+ 4 - 6
src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java

@@ -152,7 +152,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
             ThemeUtils.setColoredTitle(getSupportActionBar(), caption, this);
         }
 
-        setIndeterminate(mSyncInProgress);
+        showProgressBar(mSyncInProgress);
         // always AFTER setContentView(...) ; to work around bug in its implementation
 
         // sets message for empty list of folders
@@ -263,7 +263,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                                                                             getApplicationContext());
 
         refreshFolderOperation.execute(getAccount(), this, null, null);
-        setIndeterminate(true);
+        showProgressBar(true);
         setBackgroundText();
     }
 
@@ -402,9 +402,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
             actionBar.setDisplayHomeAsUpEnabled(!atRoot);
             actionBar.setHomeButtonEnabled(!atRoot);
 
-            Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
-
-            actionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.fontColor(this)));
+            ThemeUtils.tintBackButton(actionBar, this);
 
             ThemeUtils.setColoredTitle(getSupportActionBar(), atRoot ? caption : currentDir.getFileName(), this);
         }
@@ -550,7 +548,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                     DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
                     Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
 
-                    setIndeterminate(mSyncInProgress);
+                    showProgressBar(mSyncInProgress);
 
                     setBackgroundText();
                 }

+ 14 - 0
src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java

@@ -3,8 +3,10 @@
  *
  * @author Andy Scherzinger
  * @author Chris Narkiewicz  <hello@ezaquarii.com>
+ * @author Chawki Chouib  <chouibc@gmail.com>
  * Copyright (C) 2016 ownCloud Inc.
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chawki Chouib  <chouibc@gmail.com>
  * <p/>
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
@@ -70,6 +72,7 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import androidx.appcompat.app.ActionBar;
 import androidx.core.content.ContextCompat;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -123,6 +126,17 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
         recyclerView = findViewById(R.id.account_list);
 
         setupToolbar();
+
+        // set the back button from action bar
+        ActionBar actionBar = getSupportActionBar();
+
+        // check if is not null
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setDisplayShowHomeEnabled(true);
+        }
+
+        // set title Action bar
         updateActionBarTitleAndHomeButtonByString(getResources().getString(R.string.prefs_manage_accounts));
 
         List<User> users = accountManager.getAllUsers();

+ 2 - 0
src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java

@@ -140,6 +140,8 @@ public class NotificationsActivity extends FileActivity implements Notifications
 
         swipeEmptyListRefreshLayout = findViewById(R.id.swipe_containing_empty);
         swipeListRefreshLayout = findViewById(R.id.swipe_containing_list);
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeEmptyListRefreshLayout);
 
         // setup drawer
         setupDrawer(R.id.nav_notifications);

+ 3 - 9
src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -780,15 +780,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
                         ThemeUtils.primaryColor(getAccount(), false, this)));
             }
 
-            ThemeUtils.colorStatusBar(this, ThemeUtils.primaryColor(getAccount(), false, this));
+            ThemeUtils.colorStatusBar(this);
 
-            ThemeUtils.colorToolbarProgressBar(this, ThemeUtils.primaryColor(getAccount(), false, this));
-
-            Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
-
-            if (actionBar != null) {
-                actionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.fontColor(this)));
-            }
+            ThemeUtils.tintBackButton(actionBar, this);
 
             Button btnNewFolder = findViewById(R.id.uploader_cancel);
             btnNewFolder.setTextColor(ThemeUtils.primaryColor(this, true));
@@ -1042,7 +1036,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
         newFolderMenuItem.setEnabled(mFile.canWrite());
 
         // hacky as no default way is provided
-        ThemeUtils.themeSearchView(searchView, true, this);
+        ThemeUtils.themeSearchView(searchView, this);
 
         return true;
     }

+ 4 - 23
src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java

@@ -746,31 +746,12 @@ public class SettingsActivity extends ThemedPreferenceActivity
         ActionBar actionBar = getDelegate().getSupportActionBar();
 
         if (actionBar != null) {
-            actionBar.setDisplayHomeAsUpEnabled(true);
             ThemeUtils.setColoredTitle(actionBar, getString(R.string.actionbar_settings), this);
-            actionBar.setBackgroundDrawable(new ColorDrawable(ThemeUtils.primaryColor(this)));
-
-            Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
-            actionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.fontColor(this,
-                                                                                                   !ThemeUtils.darkTheme(this))));
-        }
+            ThemeUtils.colorStatusBar(this);
+            actionBar.setBackgroundDrawable(new ColorDrawable(ThemeUtils.primaryAppbarColor(this)));
 
-        Window window = getWindow();
-        if (window != null) {
-            window.getDecorView().setBackgroundDrawable(new ColorDrawable(ResourcesCompat
-                    .getColor(getResources(), R.color.bg_default, null)));
-
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                window.setStatusBarColor(ThemeUtils.primaryColor(this));
-            }
-
-            // For adding content description tag to a title field in the action bar
-            int actionBarTitleId = getResources().getIdentifier("action_bar_title", "id", "android");
-            View actionBarTitle = window.getDecorView().findViewById(actionBarTitleId);
-
-            if (actionBarTitle != null) {
-                actionBarTitle.setContentDescription(getString(R.string.actionbar_settings));
-            }
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            ThemeUtils.tintBackButton(actionBar, this);
         }
     }
 

+ 9 - 8
src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java

@@ -47,6 +47,8 @@ import com.nextcloud.client.account.User;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.di.Injectable;
+import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.MediaFoldersDetectionWork;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.java.util.Optional;
 import com.owncloud.android.BuildConfig;
@@ -61,7 +63,6 @@ import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.jobs.MediaFoldersDetectionJob;
 import com.owncloud.android.jobs.NotificationJob;
 import com.owncloud.android.ui.adapter.SyncedFolderAdapter;
 import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
@@ -140,6 +141,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
     @Inject AppPreferences preferences;
     @Inject PowerManagementService powerManagementService;
     @Inject Clock clock;
+    @Inject BackgroundJobManager backgroundJobManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -163,11 +165,11 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                 }
             }
 
-            path = getIntent().getStringExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_PATH);
-            type = getIntent().getIntExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_TYPE, -1);
+            path = getIntent().getStringExtra(MediaFoldersDetectionWork.KEY_MEDIA_FOLDER_PATH);
+            type = getIntent().getIntExtra(MediaFoldersDetectionWork.KEY_MEDIA_FOLDER_TYPE, -1);
 
             // Cancel notification
-            int notificationId = getIntent().getIntExtra(MediaFoldersDetectionJob.NOTIFICATION_ID, 0);
+            int notificationId = getIntent().getIntExtra(MediaFoldersDetectionWork.NOTIFICATION_ID, 0);
             NotificationManager notificationManager =
                 (NotificationManager) getSystemService(Activity.NOTIFICATION_SERVICE);
             notificationManager.cancel(notificationId);
@@ -637,8 +639,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
         }
 
         if (syncedFolderDisplayItem.isEnabled()) {
-            FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem, true);
-
+            backgroundJobManager.startImmediateFilesSyncJob(false, false);
             showBatteryOptimizationInfo();
         }
     }
@@ -779,7 +780,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
             // existing synced folder setup to be updated
             syncedFolderProvider.updateSyncFolder(item);
             if (item.isEnabled()) {
-                FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item, true);
+                backgroundJobManager.startImmediateFilesSyncJob(false, false);
             } else {
                 String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
 
@@ -797,7 +798,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
         if (storedId != -1) {
             item.setId(storedId);
             if (item.isEnabled()) {
-                FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item, true);
+                backgroundJobManager.startImmediateFilesSyncJob(false, false);
             } else {
                 String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
                 arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);

+ 13 - 29
src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java

@@ -38,11 +38,9 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.utils.ThemeUtils;
 
-import androidx.annotation.ColorInt;
 import androidx.annotation.StringRes;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.widget.Toolbar;
-import androidx.core.content.ContextCompat;
 
 /**
  * Base class providing toolbar registration functionality, see {@link #setupToolbar()}.
@@ -64,26 +62,22 @@ public abstract class ToolbarActivity extends BaseActivity {
      * want to use the toolbar.
      */
     protected void setupToolbar(boolean useBackgroundImage) {
-        int primaryColor = ThemeUtils.primaryColor(this, false);
-        int fontColor = ThemeUtils.fontColor(this, !ThemeUtils.darkTheme(getApplicationContext()));
+        int primaryColor = ThemeUtils.primaryAppbarColor(this);
+        int fontColor = ThemeUtils.appBarPrimaryFontColor(this);
 
         Toolbar toolbar = findViewById(R.id.toolbar);
         setSupportActionBar(toolbar);
 
-        mProgressBar = findViewById(R.id.progressBar);
-        if (mProgressBar != null) {
-            mProgressBar.setIndeterminateDrawable(
-                    ContextCompat.getDrawable(this, R.drawable.actionbar_progress_indeterminate_horizontal));
+        mProgressBar = findViewById(R.id.toolbar_progressBar);
+        setProgressBarBackgroundColor();
 
-            ThemeUtils.colorToolbarProgressBar(this, primaryColor);
-        }
         mInfoBox = findViewById(R.id.info_box);
         mInfoBoxMessage = findViewById(R.id.info_box_message);
 
         mPreviewImage = findViewById(R.id.preview_image);
         mPreviewImageContainer = findViewById(R.id.preview_image_frame);
 
-        ThemeUtils.colorStatusBar(this, primaryColor);
+        ThemeUtils.colorStatusBar(this);
 
         if (toolbar.getOverflowIcon() != null) {
             ThemeUtils.tintDrawable(toolbar.getOverflowIcon(), fontColor);
@@ -166,25 +160,14 @@ public abstract class ToolbarActivity extends BaseActivity {
         mInfoBox.setVisibility(View.GONE);
     }
 
-    /**
-     * Change the indeterminate mode for the toolbar's progress bar.
-     *
-     * @param indeterminate <code>true</code> to enable the indeterminate mode
-     */
-    public void setIndeterminate(boolean indeterminate) {
-        if (mProgressBar != null) {
-            mProgressBar.setIndeterminate(indeterminate);
-        }
-    }
-
     /**
      * Change the visibility for the toolbar's progress bar.
      *
-     * @param visibility visibility of the progress bar
+     * @param isVisible visibility of the progress bar
      */
-    public void setProgressBarVisibility(int visibility) {
+    public void showProgressBar(boolean isVisible) {
         if (mProgressBar != null) {
-            mProgressBar.setVisibility(visibility);
+            mProgressBar.setVisibility(isVisible? View.VISIBLE : View.GONE);
         }
     }
 
@@ -232,10 +215,11 @@ public abstract class ToolbarActivity extends BaseActivity {
      * Set the background to to progress bar of the toolbar. The resource should refer to
      * a Drawable object or 0 to remove the background.#
      *
-     * @param color The identifier of the color.
      */
-    public void setProgressBarBackgroundColor(@ColorInt int color) {
-        mProgressBar.setBackgroundColor(color);
-        mProgressBar.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
+    private void setProgressBarBackgroundColor() {
+        if (mProgressBar != null) {
+            mProgressBar.setBackgroundColor(ThemeUtils.primaryAppbarColor(this));
+            mProgressBar.getIndeterminateDrawable().setColorFilter(ThemeUtils.primaryColor(this), PorterDuff.Mode.SRC_IN);
+        }
     }
 }

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

@@ -212,8 +212,7 @@ public class UploadFilesActivity extends FileActivity implements
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
             actionBar.setListNavigationCallbacks(mDirectories, this);
 
-            Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
-            actionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.fontColor(this)));
+            ThemeUtils.tintBackButton(actionBar, this);
         }
 
         // wait dialog

+ 6 - 14
src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -45,6 +45,7 @@ import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.jobs.BackgroundJobManager;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.java.util.Optional;
 import com.owncloud.android.R;
@@ -52,7 +53,6 @@ import com.owncloud.android.databinding.UploadListLayoutBinding;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
-import com.owncloud.android.jobs.FilesSyncJob;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -99,6 +99,9 @@ public class UploadListActivity extends FileActivity {
     @Inject
     Clock clock;
 
+    @Inject
+    BackgroundJobManager backgroundJobManager;
+
     private UploadListLayoutBinding binding;
 
     @Override
@@ -164,6 +167,7 @@ public class UploadListActivity extends FileActivity {
         binding.list.setLayoutManager(lm);
         binding.list.setAdapter(uploadListAdapter);
 
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
         swipeListRefreshLayout.setOnRefreshListener(this::refresh);
 
         loadItems();
@@ -181,19 +185,7 @@ public class UploadListActivity extends FileActivity {
     }
 
     private void refresh() {
-        // scan for missing auto uploads files
-        Set<Job> jobs = JobManager.instance().getAllJobsForTag(FilesSyncJob.TAG);
-
-        if (jobs.isEmpty()) {
-            PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat();
-            persistableBundleCompat.putBoolean(FilesSyncJob.OVERRIDE_POWER_SAVING, true);
-            new JobRequest.Builder(FilesSyncJob.TAG)
-                .setExact(1_000L)
-                .setUpdateCurrent(false)
-                .setExtras(persistableBundleCompat)
-                .build()
-                .schedule();
-        }
+        backgroundJobManager.startImmediateFilesSyncJob(false, true);
 
         // retry failed uploads
         new Thread(() -> FileUploader.retryFailedUploads(

+ 16 - 0
src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java

@@ -4,10 +4,12 @@
  * @author Mario Danic
  * @author Andy Scherzinger
  * @author Chris Narkiewicz  <hello@ezaquarii.com>
+ * @author Chawki Chouib  <chouibc@gmail.com>
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2017 Andy Scherzinger
  * Copyright (C) 2017 Nextcloud GmbH.
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chawki Chouib  <chouibc@gmail.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
@@ -79,6 +81,7 @@ import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
+import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AlertDialog;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.fragment.app.DialogFragment;
@@ -145,10 +148,23 @@ public class UserInfoActivity extends FileActivity implements Injectable {
         setContentView(R.layout.user_info_layout);
         unbinder = ButterKnife.bind(this);
 
+
         boolean useBackgroundImage = URLUtil.isValidUrl(
                 getStorageManager().getCapability(user.getAccountName()).getServerBackground());
 
         setupToolbar(useBackgroundImage);
+
+
+        // set the back button from action bar
+        ActionBar actionBar = getSupportActionBar();
+
+        // check if is not null
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setDisplayShowHomeEnabled(true);
+        }
+
+        // set title Action bar
         updateActionBarTitleAndHomeButtonByString("");
 
         mUserInfoList.setAdapter(new UserInfoAdapter(null, ThemeUtils.primaryColor(getAccount(), true, this)));

+ 1 - 4
src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java

@@ -167,10 +167,7 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
                 button.setTextColor(ThemeUtils.fontColor(notificationsActivity));
                 button.setTypeface(button.getTypeface(), Typeface.BOLD);
             } else {
-                button.setStrokeColor(ColorStateList.valueOf(resources.getColor(R.color.grey_200)));
-                button.setStrokeWidth(3);
-
-                button.setBackgroundColor(resources.getColor(R.color.transparent));
+                button.setBackgroundColor(resources.getColor(R.color.grey_200));
                 button.setTextColor(primaryColor);
                 button.setTypeface(button.getTypeface(), Typeface.BOLD);
             }

+ 3 - 0
src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -431,6 +431,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
                         if (i == 0 && sharees.size() > 3) {
                             avatar.setImageResource(R.drawable.ic_people);
+                            ThemeUtils.setIconColor(avatar.getDrawable());
                         } else {
                             if (sharee.getShareType().equals(ShareType.GROUP)) {
                                 try {
@@ -439,9 +440,11 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                                 } catch (Exception e) {
                                     Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
                                     avatar.setImageResource(R.drawable.ic_people);
+                                    ThemeUtils.setIconColor(avatar.getDrawable());
                                 }
                             } else if (sharee.getShareType().equals(ShareType.CIRCLE)) {
                                 avatar.setImageResource(R.drawable.ic_circles);
+                                ThemeUtils.setIconColor(avatar.getDrawable());
                             } else if (sharee.getUserId().contains("@")) {
                                 showFederatedShareAvatar(sharee.getUserId(), avatarRadius, resources, avatar);
                             } else {

+ 1 - 1
src/main/java/com/owncloud/android/ui/asynctasks/PhotoSearchTask.java

@@ -119,7 +119,7 @@ public class PhotoSearchTask extends AsyncTask<Void, Void, RemoteOperationResult
             final ToolbarActivity fileDisplayActivity = (ToolbarActivity) photoFragment.getActivity();
 
             if (fileDisplayActivity != null) {
-                fileDisplayActivity.setIndeterminate(false);
+                fileDisplayActivity.showProgressBar(false);
             }
 
             if (!result.isSuccess() && !isCancelled()) {

+ 6 - 3
src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java

@@ -27,7 +27,6 @@ import android.content.DialogInterface;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -51,6 +50,7 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.AppCompatCheckBox;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentTransaction;
@@ -151,8 +151,11 @@ public class ConflictsResolveDialog extends DialogFragment {
         View view = inflater.inflate(R.layout.conflict_resolve_dialog, null);
         int accentColor = ThemeUtils.primaryAccentColor(getContext());
 
-        CheckBox newFileCheckbox = view.findViewById(R.id.new_checkbox);
-        CheckBox existingFileCheckbox = view.findViewById(R.id.existing_checkbox);
+        AppCompatCheckBox newFileCheckbox = view.findViewById(R.id.new_checkbox);
+        AppCompatCheckBox existingFileCheckbox = view.findViewById(R.id.existing_checkbox);
+
+        ThemeUtils.tintCheckbox(newFileCheckbox, ThemeUtils.primaryColor(getContext()));
+        ThemeUtils.tintCheckbox(existingFileCheckbox, ThemeUtils.primaryColor(getContext()));
 
         // Build the dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

+ 3 - 23
src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java

@@ -196,7 +196,7 @@ public class ExtendedListFragment extends Fragment implements
         closeButton = searchView.findViewById(androidx.appcompat.R.id.search_close_btn);
         searchView.setOnQueryTextListener(this);
         searchView.setOnCloseListener(this);
-        ThemeUtils.themeSearchView(searchView, true, requireContext());
+        ThemeUtils.themeSearchView(searchView, requireContext());
 
         SearchView.SearchAutoComplete theTextArea = searchView.findViewById(R.id.search_src_text);
         theTextArea.setHighlightColor(ThemeUtils.primaryAccentColor(getContext()));
@@ -268,17 +268,7 @@ public class ExtendedListFragment extends Fragment implements
             }
         });
 
-        int fontColor = ThemeUtils.fontColor(getContext());
-
         LinearLayout searchBar = searchView.findViewById(R.id.search_bar);
-        TextView searchBadge = searchView.findViewById(R.id.search_badge);
-
-        searchBadge.setTextColor(fontColor);
-        searchBadge.setHintTextColor(fontColor);
-
-        ImageView searchButton = searchView.findViewById(R.id.search_button);
-        searchButton.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_search, fontColor));
-
         searchBar.setLayoutTransition(new LayoutTransition());
     }
 
@@ -381,7 +371,8 @@ public class ExtendedListFragment extends Fragment implements
 
         // Pull-down to refresh layout
         mRefreshListLayout = v.findViewById(R.id.swipe_containing_list);
-        onCreateSwipeToRefresh(mRefreshListLayout);
+        ThemeUtils.colorSwipeRefreshLayout(getContext(), mRefreshListLayout);
+        mRefreshListLayout.setOnRefreshListener(this);
 
         mFabMain = v.findViewById(R.id.fab_main);
         ThemeUtils.tintFloatingActionButton(mFabMain, R.drawable.ic_plus, getContext());
@@ -773,17 +764,6 @@ public class ExtendedListFragment extends Fragment implements
         return (mEmptyListContainer != null && mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : "";
     }
 
-    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
-        int primaryColor = ThemeUtils.primaryColor(getContext());
-        int darkColor = ThemeUtils.primaryDarkColor(getContext());
-        int accentColor = ThemeUtils.primaryAccentColor(getContext());
-
-        // Colors in animations
-        // TODO change this to use darker and lighter color, again.
-        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
-        refreshLayout.setOnRefreshListener(this);
-    }
-
     @Override
     public void onRefresh(boolean ignoreETag) {
         mRefreshListLayout.setRefreshing(false);

+ 2 - 11
src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java

@@ -175,8 +175,8 @@ public class FileDetailActivitiesFragment extends Fragment implements
 
         setupView();
 
-        onCreateSwipeToRefresh(swipeEmptyListRefreshLayout);
-        onCreateSwipeToRefresh(swipeListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(getContext(), swipeEmptyListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(getContext(), swipeListRefreshLayout);
 
         fetchAndSetData(-1);
 
@@ -448,15 +448,6 @@ public class FileDetailActivitiesFragment extends Fragment implements
         });
     }
 
-    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
-        int primaryColor = ThemeUtils.primaryColor(getContext());
-        int darkColor = ThemeUtils.primaryDarkColor(getContext());
-        int accentColor = ThemeUtils.primaryAccentColor(getContext());
-
-        // Colors in animations
-        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
-    }
-
     @Override
     public void onActivityClicked(RichObject richObject) {
         // TODO implement activity click

+ 3 - 3
src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -241,7 +241,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
     private void activatePreviewImage() {
         if (activity != null) {
             activity.setPreviewImageVisibility(View.VISIBLE);
-            activity.setProgressBarVisibility(View.GONE);
+            activity.showProgressBar(false);
             ThemeUtils.setStatusBarColor(activity, activity.getResources().getColor(R.color.background_color_inverse));
             if (activity.getSupportActionBar() != null) {
                 activity.getSupportActionBar().setTitle(null);
@@ -376,9 +376,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         leaveTransferProgress();
 
         if(activity != null) {
-            activity.setPreviewImageVisibility(View.GONE);
-            activity.setProgressBarVisibility(View.VISIBLE);
             activity.setupToolbar();
+            activity.setPreviewImageVisibility(View.GONE);
+            activity.showProgressBar(true);
         }
 
         super.onStop();

+ 1 - 1
src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java

@@ -245,7 +245,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
             FileDetailSharingFragmentHelper.setupSearchView(
                 (SearchManager) fileDisplayActivity.getSystemService(Context.SEARCH_SERVICE), searchView,
                 fileDisplayActivity.getComponentName());
-            ThemeUtils.themeSearchView(searchView, false, requireContext());
+            ThemeUtils.themeSearchView(searchView, requireContext());
         } else {
             searchView.setVisibility(View.GONE);
             shareByLinkContainer.setVisibility(View.GONE);

+ 4 - 15
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -179,11 +179,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
     protected boolean mOnlyFoldersClickable;
     protected boolean mFileSelectable;
 
-    protected int mSystemBarActionModeColor;
-    protected int mSystemBarColor;
-    protected int mProgressBarActionModeColor;
-    protected int mProgressBarColor;
-
     protected boolean mHideFab = true;
     protected ActionMode mActiveActionMode;
     protected OCFileListFragment.MultiChoiceModeListener mMultiChoiceModeListener;
@@ -209,10 +204,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setHasOptionsMenu(true);
-        mSystemBarActionModeColor = getResources().getColor(R.color.action_mode_status_bar_background);
-        mSystemBarColor = ThemeUtils.primaryColor(getContext());
-        mProgressBarActionModeColor = getResources().getColor(R.color.action_mode_background);
-        mProgressBarColor = ThemeUtils.primaryColor(getContext());
         mMultiChoiceModeListener = new MultiChoiceModeListener();
 
         if (savedInstanceState != null) {
@@ -633,9 +624,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
             inflater.inflate(R.menu.item_file, menu);
             mode.invalidate();
 
-            //set gray color
-            ThemeUtils.colorStatusBar(getActivity(), mSystemBarActionModeColor);
-            ThemeUtils.colorToolbarProgressBar(getActivity(), mProgressBarActionModeColor);
+            //set actionMode color
+            ThemeUtils.colorStatusBar(getActivity(), ThemeUtils.actionModeColor(requireContext()));
 
             // hide FAB in multi selection mode
             setFabVisible(false);
@@ -690,8 +680,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
             // reset to previous color
             final FragmentActivity activity = getActivity();
             if (activity != null) {
-                ThemeUtils.colorStatusBar(activity, mSystemBarColor);
-                ThemeUtils.colorToolbarProgressBar(activity, mProgressBarColor);
+                ThemeUtils.colorStatusBar(activity);
             }
 
             // show FAB on multi selection mode exit
@@ -1571,7 +1560,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
                                 @Override
                                 public void run() {
                                     if (fileDisplayActivity != null) {
-                                        fileDisplayActivity.setIndeterminate(false);
+                                        fileDisplayActivity.showProgressBar(false);
                                     }
                                 }
                             });

+ 2 - 3
src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java

@@ -133,10 +133,9 @@ public class ContactsBackupFragment extends FileFragment implements DatePickerDi
 
         if (actionBar != null) {
             ThemeUtils.setColoredTitle(actionBar, getString(R.string.actionbar_contacts), getContext());
-            actionBar.setDisplayHomeAsUpEnabled(true);
 
-            Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
-            actionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.fontColor(getContext())));
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            ThemeUtils.tintBackButton(actionBar, getContext());
         }
 
         arbitraryDataProvider = new ArbitraryDataProvider(getContext().getContentResolver());

+ 1 - 5
src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java

@@ -144,6 +144,7 @@ public class TrashbinActivity extends FileActivity implements
         recyclerView.setHasFooter(true);
         recyclerView.setLayoutManager(new LinearLayoutManager(this));
 
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
         swipeListRefreshLayout.setOnRefreshListener(this::loadFolder);
 
         loadFolder();
@@ -223,11 +224,6 @@ public class TrashbinActivity extends FileActivity implements
             trashbinPresenter.enterFolder(file.getRemotePath());
 
             mDrawerToggle.setDrawerIndicatorEnabled(false);
-
-            Toolbar toolbar = findViewById(R.id.toolbar);
-            if (toolbar != null && toolbar.getNavigationIcon() != null) {
-                ThemeUtils.tintDrawable(toolbar.getNavigationIcon(), ThemeUtils.fontColor(this));
-            }
         }
     }
 

+ 19 - 19
src/main/java/com/owncloud/android/utils/CsrHelper.java

@@ -1,21 +1,21 @@
 package com.owncloud.android.utils;
 
-import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.spongycastle.asn1.x500.X500Name;
-import org.spongycastle.asn1.x509.AlgorithmIdentifier;
-import org.spongycastle.asn1.x509.BasicConstraints;
-import org.spongycastle.asn1.x509.Extension;
-import org.spongycastle.asn1.x509.ExtensionsGenerator;
-import org.spongycastle.crypto.params.AsymmetricKeyParameter;
-import org.spongycastle.crypto.util.PrivateKeyFactory;
-import org.spongycastle.operator.ContentSigner;
-import org.spongycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
-import org.spongycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
-import org.spongycastle.operator.OperatorCreationException;
-import org.spongycastle.operator.bc.BcRSAContentSignerBuilder;
-import org.spongycastle.pkcs.PKCS10CertificationRequest;
-import org.spongycastle.pkcs.PKCS10CertificationRequestBuilder;
-import org.spongycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
 
 import java.io.IOException;
 import java.security.KeyPair;
@@ -25,7 +25,7 @@ import java.security.KeyPair;
  * https://github.com/awslabs/aws-sdk-android-samples/blob/master/CreateIotCertWithCSR/src/com/amazonaws/demo/csrcert/CsrHelper.java
  * accessed at 31.08.17
  * Original parts are licensed under the Apache License, Version 2.0: http://aws.amazon.com/apache2.0
- * Own parts are licensed unter GPLv3+.
+ * Own parts are licensed under GPLv3+.
  */
 
 public final class CsrHelper {
@@ -50,7 +50,7 @@ public final class CsrHelper {
         return "-----BEGIN CERTIFICATE REQUEST-----\n" + android.util.Base64.encodeToString(derCSR,
                 android.util.Base64.NO_WRAP) + "\n-----END CERTIFICATE REQUEST-----";
     }
-    
+
     /**
      * Create the certificate signing request (CSR) from private and public keys
      *
@@ -69,7 +69,7 @@ public final class CsrHelper {
         ContentSigner signer = new BcRSAContentSignerBuilder(signatureAlgorithm, digestAlgorithm).build(privateKey);
 
         PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(principal),
-                keyPair.getPublic());
+                                                                                                keyPair.getPublic());
         ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
         extensionsGenerator.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
         csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionsGenerator.generate());

+ 6 - 35
src/main/java/com/owncloud/android/utils/FilesSyncHelper.java

@@ -31,7 +31,6 @@ import android.net.Uri;
 import android.os.Build;
 import android.provider.MediaStore;
 
-import com.evernote.android.job.JobManager;
 import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
@@ -47,8 +46,6 @@ import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.jobs.FilesSyncJob;
-import com.owncloud.android.jobs.OfflineSyncJob;
 import com.owncloud.android.lib.common.utils.Log_OC;
 
 import org.lukhnos.nnio.file.FileVisitResult;
@@ -60,8 +57,6 @@ import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
 
@@ -79,7 +74,7 @@ public final class FilesSyncHelper {
         // utility class -> private constructor
     }
 
-    public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder, boolean syncNow) {
+    private static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
         final Context context = MainApp.getAppContext();
         final ContentResolver contentResolver = context.getContentResolver();
 
@@ -124,26 +119,19 @@ public final class FilesSyncHelper {
                     Log_OC.e(TAG, "Something went wrong while indexing files for auto upload", e);
                 }
             }
-
-            if (syncNow) {
-                new JobRequest.Builder(FilesSyncJob.TAG)
-                    .setExact(1_000L)
-                    .setUpdateCurrent(false)
-                    .build()
-                    .schedule();
-            }
         }
     }
 
-    public static void insertAllDBEntries(AppPreferences preferences, Clock clock, boolean skipCustom,
-                                          boolean syncNow) {
+    public static void insertAllDBEntries(AppPreferences preferences,
+                                          Clock clock,
+                                          boolean skipCustom) {
         final Context context = MainApp.getAppContext();
         final ContentResolver contentResolver = context.getContentResolver();
         SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
 
         for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
             if (syncedFolder.isEnabled() && (!skipCustom || syncedFolder.getType() != MediaFolderType.CUSTOM)) {
-                insertAllDBEntriesForSyncedFolder(syncedFolder, syncNow);
+                insertAllDBEntriesForSyncedFolder(syncedFolder);
             }
         }
     }
@@ -233,27 +221,10 @@ public final class FilesSyncHelper {
     }
 
     public static void scheduleFilesSyncIfNeeded(Context context, BackgroundJobManager jobManager) {
-        // always run this because it also allows us to perform retries of manual uploads
-        new JobRequest.Builder(FilesSyncJob.TAG)
-                .setPeriodic(900000L, 300000L)
-                .setUpdateCurrent(true)
-                .build()
-                .schedule();
-
+        jobManager.schedulePeriodicFilesSyncJob();
         if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             jobManager.scheduleContentObserverJob();
         }
     }
-
-    public static void scheduleOfflineSyncIfNeeded() {
-        Set<JobRequest> jobRequests = JobManager.instance().getAllJobRequestsForTag(OfflineSyncJob.TAG);
-        if (jobRequests.isEmpty()) {
-            new JobRequest.Builder(OfflineSyncJob.TAG)
-                .setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
-                .setUpdateCurrent(false)
-                .build()
-                .schedule();
-        }
-    }
 }
 

+ 96 - 61
src/main/java/com/owncloud/android/utils/ThemeUtils.java

@@ -53,7 +53,6 @@ import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.status.OCCapability;
-import com.owncloud.android.ui.activity.ToolbarActivity;
 
 import java.lang.reflect.Field;
 
@@ -70,7 +69,7 @@ import androidx.core.content.res.ResourcesCompat;
 import androidx.core.graphics.ColorUtils;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.core.widget.CompoundButtonCompat;
-import androidx.fragment.app.FragmentActivity;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
@@ -149,8 +148,7 @@ public final class ThemeUtils {
     }
 
     public static int getNeutralGrey(Context context) {
-        return darkTheme(context) ? context.getResources().getColor(R.color.fg_contrast) :
-                                    Color.GRAY;
+        return darkTheme(context) ? context.getResources().getColor(R.color.fg_contrast) : Color.GRAY;
     }
 
     public static int elementColor(Context context) {
@@ -187,8 +185,12 @@ public final class ThemeUtils {
     }
 
     /**
+     * returns the font color based on the server side theming and uses black/white as a fallback based on
+     * replaceWhite.
+     *
+     * @param context      the context
+     * @param replaceWhite FLAG to return white/black if server side color isn't available
      * @return int font color to use
-     * adapted from https://github.com/nextcloud/server/blob/master/apps/theming/lib/Util.php#L90-L102
      */
     public static int fontColor(Context context, boolean replaceWhite) {
         if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
@@ -216,17 +218,19 @@ public final class ThemeUtils {
 
     /**
      * Tests if light color is set
-     * @return  true if primaryColor is lighter than MAX_LIGHTNESS
+     *
+     * @param color the color
+     * @return true if primaryColor is lighter than MAX_LIGHTNESS
      */
-    public static boolean lightTheme(Context context) {
-        int primaryColor = primaryColor(context);
-        float[] hsl = colorToHSL(primaryColor);
+    public static boolean lightTheme(int color) {
+        float[] hsl = colorToHSL(color);
 
         return hsl[INDEX_LUMINATION] >= MAX_LIGHTNESS;
     }
 
     /**
      * Tests if dark color is set
+     *
      * @return true if dark theme -> e.g.use light font color, darker accent color
      */
     public static boolean darkTheme(Context context) {
@@ -236,6 +240,22 @@ public final class ThemeUtils {
         return hsl[INDEX_LUMINATION] <= 0.55;
     }
 
+    public static int primaryAppbarColor(Context context) {
+        return ContextCompat.getColor(context, R.color.appbar);
+    }
+
+    public static int appBarPrimaryFontColor(Context context) {
+        return ContextCompat.getColor(context, R.color.fontAppbar);
+    }
+
+    public static int appBarSecondaryFontColor(Context context) {
+        return ContextCompat.getColor(context, R.color.fontSecondaryAppbar);
+    }
+
+    public static int actionModeColor(Context context) {
+        return ContextCompat.getColor(context, R.color.action_mode_background);
+    }
+
     /**
      * Set color of title to white/black depending on background color
      *
@@ -248,7 +268,7 @@ public final class ThemeUtils {
                 actionBar.setTitle(title);
             } else {
                 Spannable text = new SpannableString(title);
-                text.setSpan(new ForegroundColorSpan(fontColor(context, !darkTheme(context))),
+                text.setSpan(new ForegroundColorSpan(appBarPrimaryFontColor(context)),
                              0,
                              text.length(),
                              Spannable.SPAN_INCLUSIVE_INCLUSIVE);
@@ -273,7 +293,7 @@ public final class ThemeUtils {
                 actionBar.setSubtitle(title);
             } else {
                 Spannable text = new SpannableString(title);
-                text.setSpan(new ForegroundColorSpan(fontColor(context)),
+                text.setSpan(new ForegroundColorSpan(appBarSecondaryFontColor(context)),
                              0,
                              text.length(),
                              Spannable.SPAN_INCLUSIVE_INCLUSIVE);
@@ -294,7 +314,7 @@ public final class ThemeUtils {
         }
 
         Drawable backArrow = context.getResources().getDrawable(R.drawable.ic_arrow_back);
-        supportActionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.fontColor(context)));
+        supportActionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.appBarPrimaryFontColor(context)));
     }
 
     public static Spanned getColoredTitle(String title, int color) {
@@ -332,7 +352,7 @@ public final class ThemeUtils {
      * Adjust lightness of given color
      *
      * @param lightnessDelta values -1..+1
-     * @param color original color
+     * @param color          original color
      * @param threshold      0..1 as maximum value, -1 to disable
      * @return color adjusted by lightness
      */
@@ -414,6 +434,15 @@ public final class ThemeUtils {
         seekBar.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN);
     }
 
+    public static void colorSwipeRefreshLayout(Context context, SwipeRefreshLayout swipeRefreshLayout) {
+        int primaryColor = ThemeUtils.primaryColor(context);
+        int darkColor = ThemeUtils.primaryDarkColor(context);
+        int accentColor = ThemeUtils.primaryAccentColor(context);
+
+        swipeRefreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
+        swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.bg_elevation_one);
+    }
+
     /**
      * set the Nextcloud standard colors for the snackbar.
      *
@@ -431,38 +460,33 @@ public final class ThemeUtils {
      * @param fragmentActivity fragment activity
      * @param color            the color
      */
-    public static void colorStatusBar(FragmentActivity fragmentActivity, @ColorInt int color) {
+    public static void colorStatusBar(Activity fragmentActivity, @ColorInt int color) {
         Window window = fragmentActivity.getWindow();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && window != null) {
+        boolean isLightTheme = lightTheme(color);
+        if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             window.setStatusBarColor(color);
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                 View decor = window.getDecorView();
-                if (lightTheme(fragmentActivity.getApplicationContext())) {
+                if (isLightTheme) {
                     decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                 } else {
                     decor.setSystemUiVisibility(0);
                 }
+            } else if (isLightTheme) {
+                window.setStatusBarColor(Color.BLACK);
             }
         }
     }
 
-    /**
-     * Sets the color of the progressbar to {@code color} within the given toolbar.
-     *
-     * @param activity         the toolbar activity instance
-     * @param progressBarColor the color to be used for the toolbar's progress bar
-     */
-    public static void colorToolbarProgressBar(FragmentActivity activity, int progressBarColor) {
-        if (activity instanceof ToolbarActivity) {
-            ((ToolbarActivity) activity).setProgressBarBackgroundColor(progressBarColor);
-        }
+    public static void colorStatusBar(Activity fragmentActivity) {
+        colorStatusBar(fragmentActivity, primaryAppbarColor(fragmentActivity));
     }
 
     /**
      * Sets the color of the  TextInputLayout to {@code color} for hint text and box stroke.
      *
      * @param textInputLayout the TextInputLayout instance
-     * @param color the color to be used for the hint text and box stroke
+     * @param color           the color to be used for the hint text and box stroke
      */
     public static void colorTextInputLayout(TextInputLayout textInputLayout, int color) {
         textInputLayout.setBoxStrokeColor(color);
@@ -479,7 +503,7 @@ public final class ThemeUtils {
     }
 
     public static void themeDialogActionButton(MaterialButton button) {
-        if (button == null ) {
+        if (button == null) {
             return;
         }
 
@@ -488,8 +512,8 @@ public final class ThemeUtils {
         int disabledColor = ContextCompat.getColor(context, R.color.disabled_text);
         button.setTextColor(new ColorStateList(
             new int[][]{
-                new int[] { android.R.attr.state_enabled}, // enabled
-                new int[] {-android.R.attr.state_enabled}, // disabled
+                new int[]{android.R.attr.state_enabled}, // enabled
+                new int[]{-android.R.attr.state_enabled}, // disabled
             },
             new int[]{
                 accentColor,
@@ -499,7 +523,9 @@ public final class ThemeUtils {
     }
 
     public static void themeEditText(Context context, EditText editText, boolean themedBackground) {
-        if (editText == null) { return; }
+        if (editText == null) {
+            return;
+        }
 
         int color = ContextCompat.getColor(context, R.color.text_color);
 
@@ -511,6 +537,10 @@ public final class ThemeUtils {
             }
         }
 
+        setEditTextColor(context, editText, color);
+    }
+
+    private static void setEditTextColor(Context context, EditText editText, int color) {
         editText.setTextColor(color);
         editText.setHighlightColor(context.getResources().getColor(R.color.fg_contrast));
         setEditTextCursorColor(editText, color);
@@ -521,14 +551,14 @@ public final class ThemeUtils {
      * Theme search view
      *
      * @param searchView       searchView to be changed
-     * @param themedBackground true if background is themed, e.g. on action bar; false if background is white
      * @param context          the app's context
      */
-    public static void themeSearchView(SearchView searchView, boolean themedBackground, Context context) {
+    public static void themeSearchView(SearchView searchView, Context context) {
         // hacky as no default way is provided
-        int fontColor = ThemeUtils.fontColor(context, !darkTheme(context));
+        int fontColor = appBarPrimaryFontColor(context);
         SearchView.SearchAutoComplete editText = searchView.findViewById(R.id.search_src_text);
-        themeEditText(context, editText, themedBackground);
+        setEditTextColor(context, editText, fontColor);
+        editText.setHintTextColor(appBarSecondaryFontColor(context));
 
         ImageView closeButton = searchView.findViewById(androidx.appcompat.R.id.search_close_btn);
         closeButton.setColorFilter(fontColor);
@@ -543,14 +573,14 @@ public final class ThemeUtils {
 
     public static void tintCheckbox(AppCompatCheckBox checkBox, int color) {
         CompoundButtonCompat.setButtonTintList(checkBox, new ColorStateList(
-                new int[][]{
-                        new int[]{-android.R.attr.state_checked},
-                        new int[]{android.R.attr.state_checked},
-                },
-                new int[]{
-                        Color.GRAY,
-                        color
-                }
+            new int[][]{
+                new int[]{-android.R.attr.state_checked},
+                new int[]{android.R.attr.state_checked},
+            },
+            new int[]{
+                Color.GRAY,
+                color
+            }
         ));
     }
 
@@ -567,13 +597,13 @@ public final class ThemeUtils {
 
         // setting the thumb color
         DrawableCompat.setTintList(switchView.getThumbDrawable(), new ColorStateList(
-                new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
-                new int[]{color, Color.WHITE}));
+            new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
+            new int[]{color, Color.WHITE}));
 
         // setting the track color
         DrawableCompat.setTintList(switchView.getTrackDrawable(), new ColorStateList(
-                new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
-                new int[]{trackColor, MainApp.getAppContext().getResources().getColor(R.color.switch_track_color_unchecked)}));
+            new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
+            new int[]{trackColor, MainApp.getAppContext().getResources().getColor(R.color.switch_track_color_unchecked)}));
     }
 
     public static Drawable tintDrawable(@DrawableRes int id, int color) {
@@ -599,7 +629,7 @@ public final class ThemeUtils {
     }
 
     public static void tintFloatingActionButton(FloatingActionButton button, @DrawableRes int
-            drawable, Context context) {
+        drawable, Context context) {
         button.setBackgroundTintList(ColorStateList.valueOf(ThemeUtils.primaryColor(context)));
         button.setRippleColor(ThemeUtils.primaryDarkColor(context));
         button.setImageDrawable(ThemeUtils.tintDrawable(drawable, ThemeUtils.fontColor(context)));
@@ -627,13 +657,22 @@ public final class ThemeUtils {
         }
     }
 
+    public static Drawable setIconColor(Drawable drawable) {
+        int color;
+        if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
+            color = Color.WHITE;
+        } else {
+            color = Color.BLACK;
+        }
+        return tintDrawable(drawable, color);
+    }
+
     /**
-     * Lifted from SO.
-     * FindBugs surpressed because of lack of public API to alter the cursor color.
+     * Lifted from SO. FindBugs surpressed because of lack of public API to alter the cursor color.
      *
-     * @param editText  TextView to be styled
-     * @param color     The desired cursor colour
-     * @see             <a href="https://stackoverflow.com/a/52564925">StackOverflow url</a>
+     * @param editText TextView to be styled
+     * @param color    The desired cursor colour
+     * @see <a href="https://stackoverflow.com/a/52564925">StackOverflow url</a>
      */
     @SuppressFBWarnings
     public static void setEditTextCursorColor(EditText editText, int color) {
@@ -684,15 +723,11 @@ public final class ThemeUtils {
 
 
     /**
-     * Set the color of the handles when you select text in a
-     * {@link android.widget.EditText} or other view that extends {@link TextView}.
-     * FindBugs surpressed because of lack of public API to alter the {@link TextView} handles color.
-     *
-     * @param view
-     *     The {@link TextView} or a {@link View} that extends {@link TextView}.
-     * @param color
-     *     The color to set for the text handles
+     * Set the color of the handles when you select text in a {@link android.widget.EditText} or other view that extends
+     * {@link TextView}. FindBugs surpressed because of lack of public API to alter the {@link TextView} handles color.
      *
+     * @param view  The {@link TextView} or a {@link View} that extends {@link TextView}.
+     * @param color The color to set for the text handles
      * @see <a href="https://gist.github.com/jaredrummler/2317620559d10ac39b8218a1152ec9d4">External reference</a>
      */
     @SuppressFBWarnings

二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_1.png


二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_2.png


二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_3.png


二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_4.png


二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_5.png


二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_6.png


二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_7.png


二進制
src/main/res/drawable-night-xxhdpi/owncloud_progressbar_indeterminate_8.png


+ 13 - 0
src/main/res/drawable-night/favorite.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="16dp"
+        android:viewportWidth="16"
+        android:viewportHeight="16">
+
+    <path
+        android:fillColor="#ffcc00"
+        android:strokeColor="#121212"
+        android:strokeWidth="0.46367699"
+        android:pathData="M 7.9999993,1.0934068 10.040178,6.0083827 15.418832,6.4256921 11.245738,9.9032694 12.636769,15.003716 7.9999993,12.128919 3.3632295,15.003716 4.7542604,9.9032694 0.5811676,6.4256921 5.9598206,6.0083827 Z"/>
+</vector>

二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_1.png


二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_2.png


二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_3.png


二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_4.png


二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_5.png


二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_6.png


二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_7.png


二進制
src/main/res/drawable-xxhdpi/owncloud_progressbar_indeterminate_8.png


+ 0 - 19
src/main/res/drawable/actionbar_progress_horizontal.xml

@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item
-        android:id="@android:id/background"
-        android:drawable="@color/primary" />
-    <item android:id="@android:id/secondaryProgress">
-        <scale
-            android:drawable="@color/primary"
-            android:scaleWidth="100%" />
-    </item>
-    <item android:id="@android:id/progress">
-        <scale
-            android:drawable="@color/primary"
-            android:scaleWidth="100%" />
-    </item>
-
-</layer-list>

+ 0 - 13
src/main/res/drawable/actionbar_progress_indeterminate_horizontal.xml

@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<animation-list
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:oneshot="false">
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_1" android:duration="50" />
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_2" android:duration="50" />
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_3" android:duration="50" />
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_4" android:duration="50" />
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_5" android:duration="50" />
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_6" android:duration="50" />
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_7" android:duration="50" />
-    <item android:drawable="@drawable/owncloud_progressbar_indeterminate_8" android:duration="50" />
-</animation-list>

+ 2 - 2
src/main/res/drawable/uploader_list_separator.xml

@@ -20,7 +20,7 @@
 <shape
   xmlns:android="http://schemas.android.com/apk/res/android">
     <gradient
-        android:startColor="@color/uploader_list_separator_color"
-        android:endColor="@color/uploader_list_separator_color"
+        android:startColor="@color/list_divider_background"
+        android:endColor="@color/list_divider_background"
         android:angle="0" />
 </shape>

+ 2 - 2
src/main/res/layout/conflict_resolve_dialog.xml

@@ -45,7 +45,7 @@
             android:layout_weight="1"
             android:orientation="vertical">
 
-            <CheckBox
+            <androidx.appcompat.widget.AppCompatCheckBox
                 android:id="@+id/new_checkbox"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
@@ -79,7 +79,7 @@
             android:layout_weight="1"
             android:orientation="vertical">
 
-            <CheckBox
+            <androidx.appcompat.widget.AppCompatCheckBox
                 android:id="@+id/existing_checkbox"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"

+ 1 - 1
src/main/res/layout/drawer.xml

@@ -28,7 +28,7 @@
         android:layout_gravity="start"
         android:layout_weight="1"
         android:fitsSystemWindows="true"
-        android:background="@color/bg_default"
+        android:background="@color/appbar"
         android:theme="@style/NavigationView_ItemTextAppearance"
         app:headerLayout="@layout/drawer_header"
         app:menu="@menu/partial_drawer_entries">

+ 8 - 8
src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml

@@ -127,9 +127,9 @@
         android:id="@+id/divider"
         android:layout_width="match_parent"
         android:layout_height="1dp"
-        android:layout_marginLeft="@dimen/standard_margin"
+        android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
         android:layout_marginTop="@dimen/standard_half_margin"
-        android:layout_marginRight="@dimen/standard_margin"
+        android:layout_marginEnd="@dimen/standard_margin"
         android:layout_marginBottom="@dimen/standard_half_margin"
         android:background="@color/list_divider_background"/>
 
@@ -166,9 +166,9 @@
     <View
         android:layout_width="match_parent"
         android:layout_height="1dp"
-        android:layout_marginLeft="@dimen/standard_margin"
+        android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
         android:layout_marginTop="@dimen/standard_half_margin"
-        android:layout_marginRight="@dimen/standard_margin"
+        android:layout_marginEnd="@dimen/standard_margin"
         android:layout_marginBottom="@dimen/standard_half_margin"
         android:background="@color/list_divider_background" />
 
@@ -266,9 +266,9 @@
         <View
             android:layout_width="match_parent"
             android:layout_height="1dp"
-            android:layout_marginLeft="@dimen/standard_margin"
+            android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
             android:layout_marginTop="@dimen/standard_half_margin"
-            android:layout_marginRight="@dimen/standard_margin"
+            android:layout_marginEnd="@dimen/standard_margin"
             android:layout_marginBottom="@dimen/standard_half_margin"
             android:background="@color/list_divider_background" />
     </LinearLayout>
@@ -291,9 +291,9 @@
         <View
             android:layout_width="match_parent"
             android:layout_height="1dp"
-            android:layout_marginLeft="@dimen/standard_margin"
+            android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
             android:layout_marginTop="@dimen/standard_half_margin"
-            android:layout_marginRight="@dimen/standard_margin"
+            android:layout_marginEnd="@dimen/standard_margin"
             android:layout_marginBottom="@dimen/standard_half_margin"
             android:background="@color/list_divider_background" />
     </LinearLayout>

+ 1 - 1
src/main/res/layout/fragment_etm_background_jobs.xml

@@ -3,7 +3,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".etm.pages.EtmBackgroundJobsFragment">
+    tools:context="com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment">
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/etm_background_jobs_list"

+ 1 - 11
src/main/res/layout/grid_sync_item.xml

@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="UTF-8"?><!--
   Nextcloud Android client application
 
   Copyright (C) 2017 Andy Scherzinger
@@ -56,15 +55,6 @@
             android:gravity="center_horizontal"
             android:orientation="horizontal">
 
-            <TextView
-                android:id="@+id/next"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/synced_folders_plus"
-                android:textColor="#ffffff"
-                android:textSize="@dimen/grid_sync_item_layout_next_text_size"
-                android:textStyle="bold" />
-
             <TextView
                 android:id="@+id/counter"
                 android:layout_width="wrap_content"

+ 14 - 16
src/main/res/layout/toolbar_standard.xml

@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   Nextcloud Android client application
 
   Copyright (C) 2016 Andy Scherzinger
@@ -21,12 +20,12 @@
 -->
 <com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/appbar"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:viewBindingIgnore="true"
-    android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
+    android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
+    tools:viewBindingIgnore="true">
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -52,24 +51,23 @@
         </FrameLayout>
 
         <androidx.appcompat.widget.Toolbar
-            android:id="@id/toolbar"
+            android:id="@+id/toolbar"
             android:layout_width="match_parent"
             android:layout_height="?attr/actionBarSize"
             app:popupTheme="@style/Theme.AppCompat.DayNight.NoActionBar" />
 
         <ProgressBar
-            android:id="@+id/progressBar"
-            style="@style/Widget.ownCloud.TopProgressBar"
+            android:id="@+id/toolbar_progressBar"
+            style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
             android:layout_width="match_parent"
-            android:layout_height="@dimen/standard_quarter_margin"
-            android:layout_below="@+id/toolbar"
-            android:layout_margin="@dimen/zero"
-            android:indeterminate="false"
-            android:indeterminateOnly="false"
-            android:padding="@dimen/zero"
-            android:visibility="visible" />
+            android:layout_height="wrap_content"
+            android:layout_below="@id/toolbar"
+            android:layout_marginTop="-7dp"
+            android:layout_marginBottom="-7dp"
+            android:indeterminate="true"
+            android:visibility="gone" />
     </RelativeLayout>
 
-    <include layout="@layout/info_box"/>
+    <include layout="@layout/info_box" />
 
 </com.google.android.material.appbar.AppBarLayout>

+ 6 - 0
src/main/res/menu/fragment_etm_background_jobs.xml

@@ -42,6 +42,12 @@
         app:showAsAction="never"
         android:showAsAction="never" />
 
+    <item
+        android:id="@+id/etm_background_jobs_schedule_test"
+        android:title="@string/etm_background_jobs_schedule_test_job"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+
     <item
         android:id="@+id/etm_background_jobs_cancel_test"
         android:title="@string/etm_background_jobs_stop_test_job"

+ 13 - 0
src/main/res/values-bg-rBG/strings.xml

@@ -141,6 +141,7 @@
     <string name="confirmation_remove_folders_alert">Наистина ли желаете избраните елементи и съдържанието им да бъдат премахнати?</string>
     <string name="confirmation_remove_local">Само локално</string>
     <string name="conflict_already_existing_file">Файла вече съществува</string>
+    <string name="conflict_dialog_error">Грешка при създаване на диалог за конфликт!</string>
     <string name="conflict_message_description">Ако изберете и двете версии, ще бъде добавен номер към името на локалния файл.</string>
     <string name="conflict_message_headline">Кои файлове желете да запазите?</string>
     <string name="conflict_new_file">Нов файл</string>
@@ -190,6 +191,7 @@
     <string name="disable_new_media_folder_detection_notifications">Изключване</string>
     <string name="dismiss">Отхвърляне</string>
     <string name="dismiss_notification_description">Спри известията</string>
+    <string name="download_latest_dev_version">Сваляне на най-новата dev версия</string>
     <string name="downloader_download_failed_content">%1$sне може да бъде изтеглен</string>
     <string name="downloader_download_failed_credentials_error">Неуспешно качване, нужно е да се впишете отново</string>
     <string name="downloader_download_failed_ticker">Изтеглянето е неуспешно.</string>
@@ -200,6 +202,7 @@
     <string name="downloader_download_succeeded_ticker">Изтеглено</string>
     <string name="downloader_not_downloaded_yet">Все още не е изтеглен</string>
     <string name="drawer_close">Затвори страничното меню</string>
+    <string name="drawer_community">Общност</string>
     <string name="drawer_current_account">Текущ профил</string>
     <string name="drawer_end_account">Последен профил</string>
     <string name="drawer_item_activities">Активност</string>
@@ -225,11 +228,21 @@
     <string name="edit_permission_label">редактиране</string>
     <string name="edit_rich_workspace">редактирай информацията за папката</string>
     <string name="encrypted">Включване на криптиране</string>
+    <string name="end_to_end_encryption_confirm_button">Настройки за криптиране</string>
+    <string name="end_to_end_encryption_decrypting">Декриптиране...</string>
     <string name="end_to_end_encryption_dialog_close">Затваряне</string>
+    <string name="end_to_end_encryption_enter_password">Моля въведете парола за декриптиране на личния ключ.</string>
     <string name="end_to_end_encryption_folder_not_empty">Папката не е празна.</string>
     <string name="end_to_end_encryption_generating_keys">Генериране на нови ключове...</string>
+    <string name="end_to_end_encryption_keywords_description">Всички 12 думи заедно правят много силна парола, позволявайки само Вие да преглеждате и използвате вашите криптирани файлове. Моля запишете я и я пазете на сигурно място.</string>
+    <string name="end_to_end_encryption_not_enabled">Криптиране End-to-end е забранено на сървъра.</string>
+    <string name="end_to_end_encryption_not_supported">Криптирането работи само на KitKat(4.4) или по-висока версия.</string>
     <string name="end_to_end_encryption_password">Парола…</string>
+    <string name="end_to_end_encryption_retrieving_keys">Извличане на ключове...</string>
     <string name="end_to_end_encryption_storing_keys">Записване на ключовете</string>
+    <string name="end_to_end_encryption_title">Настройки за криптиране</string>
+    <string name="end_to_end_encryption_unsuccessful">Ключовете не могат да бъдат запазени, моля опитайте отново.</string>
+    <string name="end_to_end_encryption_wrong_password">Грешка при декриптиране. Грешна парола?</string>
     <string name="enter_filename">Въведете име на файла</string>
     <string name="error__upload__local_file_not_copied">%1$s не може да бъде копиран в локалната папка %2$s</string>
     <string name="error_cant_bind_to_operations_service">Критична грешка: операциите не могат да бъдат изпълнени</string>

+ 44 - 0
src/main/res/values-ca/strings.xml

@@ -141,7 +141,11 @@
     <string name="confirmation_remove_folder_alert">Esteu segur que voleu suprimir %1$s i els seus continguts?</string>
     <string name="confirmation_remove_folders_alert">Esteu segur que voleu suprimir els elements seleccionats i el seu contingut?</string>
     <string name="confirmation_remove_local">Només local</string>
+    <string name="conflict_already_existing_file">Aquest fitxer ja existeix</string>
+    <string name="conflict_dialog_error">Error creant diàleg de conflicte!</string>
+    <string name="conflict_message_description">Si seleccioneu ambdues versions, s\'afegirà un numero al nom de la versió local.</string>
     <string name="conflict_message_headline">Quins fitxers voleu conservar?</string>
+    <string name="conflict_new_file">Nou fitxer</string>
     <string name="contaclist_restore_selected">Restaura els contactes seleccionats</string>
     <string name="contactlist_account_chooser_title">Escolliu un compte per a importar</string>
     <string name="contactlist_item_icon">Icona d\'usuari per a la llista de contactes</string>
@@ -165,11 +169,15 @@
     <string name="copy_move_to_encrypted_folder_not_supported">No hi ha suport actualment per copiar o moure a una carpeta xifrada.</string>
     <string name="copy_to">Copia a…</string>
     <string name="could_not_download_image">No s\'ha pogut descarregar la imatge completa</string>
+    <string name="could_not_retrieve_url">No s\'ha pogut recuperar la URL</string>
     <string name="create_dir_fail_msg">No s\'ha pogut crear la carpeta</string>
+    <string name="create_new">Crear nou</string>
     <string name="create_new_document">Crea un nou document</string>
     <string name="create_new_folder">Crea una nova carpeta</string>
     <string name="create_new_presentation">Crea una nova presentació</string>
     <string name="create_new_spreadsheet">Crea un nou full de càlcul</string>
+    <string name="create_rich_workspace">Afegir informació de carpeta</string>
+    <string name="creates_rich_workspace">crea informació de carpeta</string>
     <string name="credentials_disabled">Credencials desactivades</string>
     <string name="date_unknown">Desconegut</string>
     <string name="default_credentials_wrong">Credencials incorrectes</string>
@@ -220,6 +228,7 @@
     <string name="drawer_quota_unlimited">%1$s utilitzat</string>
     <string name="drawer_synced_folders">Càrrega automàtica</string>
     <string name="edit_permission_label">edita</string>
+    <string name="edit_rich_workspace">modificar informació de carpeta</string>
     <string name="encrypted">Activa el xifrat</string>
     <string name="end_to_end_encryption_confirm_button">Arranja el xifrat</string>
     <string name="end_to_end_encryption_decrypting">S\'està desxifrant…</string>
@@ -248,13 +257,24 @@
     <string name="error_retrieving_templates">S\'ha produït un error recuperant les plantilles</string>
     <string name="error_starting_direct_camera_upload">S\'ha produït un error iniciant la càmera</string>
     <string name="etm_accounts">Comptes</string>
+    <string name="etm_background_job_name">Nom de la tasca</string>
     <string name="etm_background_job_progress">Progrés</string>
+    <string name="etm_background_job_started">Iniciat</string>
+    <string name="etm_background_job_state">Estat</string>
+    <string name="etm_background_job_user">Usuari</string>
+    <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">Tasques en segon pla</string>
+    <string name="etm_background_jobs_cancel_all">Cancel·lar totes les tasques</string>
+    <string name="etm_background_jobs_prune">Tallar les tasques inactives</string>
+    <string name="etm_background_jobs_start_test_job">Iniciar tasca de prova</string>
+    <string name="etm_background_jobs_stop_test_job">Aturar tasca de prova</string>
+    <string name="etm_migrations">Migracions (actualització d\'aplicació)</string>
     <string name="etm_preferences">Preferències</string>
     <string name="etm_title">Mode de proves d\'enginyeria</string>
     <string name="fab_label">Afegeix o carrega</string>
     <string name="failed_to_download">Ha fallat el lliurament del fitxer al gestor de descàrregues</string>
     <string name="failed_to_print">Ha fallat imprimir el fitxer</string>
+    <string name="failed_to_start_editor">Ha fallat l\'arrancada de l\'editor</string>
     <string name="fallback_weblogin_back">Enrere</string>
     <string name="fallback_weblogin_text">Revertiu l\'antic mètode d\'inici de sessió</string>
     <string name="favorite">Afegix a preferits</string>
@@ -352,8 +372,10 @@
     <string name="hint_password">Contrasenya</string>
     <string name="host_not_available">Els servidor no està disponible</string>
     <string name="host_your_own_server">Allotgeu el vostre propi servidor</string>
+    <string name="instant_upload_existing">També pujar fitxers existents</string>
     <string name="instant_upload_on_charging">Carrega només durant la càrrega de bateria</string>
     <string name="instant_upload_path">/CàrregaInstantània</string>
+    <string name="invalid_url">URL no vàlida</string>
     <string name="learn_more">Més informació</string>
     <string name="link">Enllaç</string>
     <string name="list_layout">Disposició llistada</string>
@@ -456,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">suprimit</string>
     <string name="pref_behaviour_entries_keep_file">mantingut a la carpeta original</string>
     <string name="pref_behaviour_entries_move">mogut a la carpeta de l\'aplicació</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Què s\'ha de fer si el fitxer ja existeix?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Pregunta\'m cada cop</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Sobreescriure la versió remota</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Reanomenar la nova versió</string>
+    <string name="pref_instant_name_collision_policy_title">Què s\'ha de fer si el fitxer ja existeix?</string>
     <string name="prefs_add_account">Afegeix compte</string>
     <string name="prefs_calendar_contacts">Sincronitza el calendari i els contactes</string>
     <string name="prefs_calendar_contacts_address_resolve_error">L\'adreça del servidor per al compte no s\'ha pogut resoldre per DAVx5 (conegut anteriorment com a DAVdroid)</string>
@@ -497,6 +524,7 @@
     <string name="prefs_theme_title">Tema</string>
     <string name="prefs_value_theme_dark">Fosc</string>
     <string name="prefs_value_theme_light">Clar</string>
+    <string name="prefs_value_theme_system">Seguir al sistema</string>
     <string name="preview_image_description">Visualització prèvia d\'imatge</string>
     <string name="preview_image_error_no_local_file">No hi ha cap fitxer local per fer una vista prèvia</string>
     <string name="preview_image_error_unknown_format">No es pot mostrar la imatge</string>
@@ -528,6 +556,7 @@
     <string name="saml_authentication_wrong_pass">Contrasenya incorrecta</string>
     <string name="scanQR_description">Inicia la sessió via un codi QR</string>
     <string name="screenshot_01_gridView_heading">Protegint les vostres dades </string>
+    <string name="screenshot_01_gridView_subline">productivitat auto-hostatjada </string>
     <string name="screenshot_02_listView_heading">Explorar i compartir</string>
     <string name="screenshot_02_listView_subline">totes les accions als vostres dits</string>
     <string name="screenshot_03_drawer_heading">Activitat, comparticions, …</string>
@@ -556,6 +585,8 @@
     <string name="share_get_public_link_button">Obtén l\'enllaç</string>
     <string name="share_group_clarification">%1$s (grup)</string>
     <string name="share_internal_link">Compartir l\'enllaç intern</string>
+    <string name="share_internal_link_to_file_text">L\'enllaç de compartició intern només funciona per a usuaris amb permís d\'accés a aquest fitxer</string>
+    <string name="share_internal_link_to_folder_text">L\'enllaç de compartició intern només funciona per a usuaris amb permís d\'accés a aquesta carpeta</string>
     <string name="share_known_remote_clarification">%1$s (a %2$s)</string>
     <string name="share_link_empty_password">Heu d\'escriure una contrasenya</string>
     <string name="share_link_file_error">S\'ha produït un error mentre s\'intentava compartir aquest fitxer o carpeta</string>
@@ -656,15 +687,21 @@
     <string name="sync_fail_ticker">La sincronització ha fallat</string>
     <string name="sync_fail_ticker_unauthorized">La Sincronització ha fallat, torneu a iniciar la sessió</string>
     <string name="sync_file_nothing_to_do_msg">Contingut del fitxer ja sincronitzat</string>
+    <string name="sync_folder_failed_content">La sincronització de la carpeta %1$s podria no ser completa</string>
     <string name="sync_foreign_files_forgotten_explanation">A partir de la versió 1.3.16, els fitxers carregats des d\'un dispositiu es copien a la carpeta local %1$s per evitar la pèrdua de dades quan un únic fitxer es sincronitza entre múltiples comptes.\n\nA causa d\'aquest canvi, tots els fitxers carregats amb versions anteriors d\'aquesta aplicació s\'han copiat a la carpeta %2$s. Tot i així, un error ha impedit completar aquesta operació durant la sincronització de comptes. Podeu deixar el(s) fitxer(s) tal com està(n) i suprimir l\'enllaç a %3$s, o moure\'l(s) a la carpeta %1$s i conservar l\'enllaç cap a %4$s.\n\nA continuació es mostren el(s) fitxer(s) local(s) i el(s) remot(s) a %5$s, on hi estaven enllaçats.</string>
     <string name="sync_foreign_files_forgotten_ticker">S\'han oblidat alguns fitxers locals</string>
     <string name="sync_in_progress">S\'està obtenint la versió més recent del fitxer.</string>
+    <string name="sync_not_enough_space_dialog_action_choose">Escollir què sincronitzar</string>
+    <string name="sync_not_enough_space_dialog_action_free_space">Alliberar espai</string>
+    <string name="sync_not_enough_space_dialog_placeholder">%1$s és %2$s, però només hi ha %3$s disponible al dispositiu.</string>
+    <string name="sync_not_enough_space_dialog_title">No hi ha suficient espai</string>
     <string name="sync_status_button">Botó de l\'estat de la sincronització</string>
     <string name="sync_string_files">Fitxers</string>
     <string name="synced_folder_settings_button">Botó de paràmetres</string>
     <string name="synced_folders_configure_folders">Configureu les carpetes</string>
     <string name="synced_folders_loading_folders">S\'està carregant les carpetes…</string>
     <string name="synced_folders_new_info">La càrrega instantània s\'ha renovat completament. Torneu a configurar la vostra càrrega automàtica des de dins del menú principal.\n\nGaudiu de la nova càrrega automàtica amb més funcionalitats.</string>
+    <string name="synced_folders_no_results">No s\'han trobat carpetes multimèdia </string>
     <string name="synced_folders_preferences">Preferències de la càrrega automàtica</string>
     <string name="synced_folders_preferences_folder_path">Per %1$s</string>
     <string name="synced_folders_type">Tipus</string>
@@ -672,6 +709,8 @@
     <string name="tags">Etiquetes</string>
     <string name="test_server_button">Proveu la connexió amb el servidor</string>
     <string name="thumbnail">Miniatura</string>
+    <string name="thumbnail_for_existing_file_description">Miniatura pel fitxer existent</string>
+    <string name="thumbnail_for_new_file_desc">Miniatura pel nou fitxer</string>
     <string name="timeout_richDocuments">La càrrega triga molt…</string>
     <string name="trashbin_activity_title">Fitxers suprimits</string>
     <string name="trashbin_empty_headline">No hi ha cap fitxer suprimit</string>
@@ -707,9 +746,11 @@
     <string name="upload_list_delete">Eliminar</string>
     <string name="upload_list_empty_headline">No hi ha càrregues disponibles</string>
     <string name="upload_list_empty_text_auto_upload">Carregueu algun contingut o activeu la càrrega automàtica.</string>
+    <string name="upload_list_resolve_conflict">Resoldre conflicte</string>
     <string name="upload_local_storage_full">L\'emmagatzematge local és ple</string>
     <string name="upload_local_storage_not_copied">El fitxer no es pot copiar a l\'emmagatzematge local</string>
     <string name="upload_lock_failed">Ha fallat el blocatge de la carpeta</string>
+    <string name="upload_old_android">El xifratge només és possible amb &gt;= Android 5.0</string>
     <string name="upload_query_move_foreign_files">L\'espai insuficient evita la còpia dels fitxers seleccionats a la carpeta %1$s. Els hi voleu moure?</string>
     <string name="upload_sync_conflict">Conflicte de sincronització. Si us plau, resoleu manualment.</string>
     <string name="upload_unknown_error">Error desconegut</string>
@@ -725,6 +766,8 @@
     <string name="uploader_top_message">Escolliu la carpeta de càrrega</string>
     <string name="uploader_upload_failed_content_single">No s\'ha pogut pujar %1$s</string>
     <string name="uploader_upload_failed_credentials_error">La càrrega ha fallat, torneu a iniciar la sessió</string>
+    <string name="uploader_upload_failed_sync_conflict_error">Conflicte en pujar fitxer</string>
+    <string name="uploader_upload_failed_sync_conflict_error_content">Escollir quina versió conservar de %1$s</string>
     <string name="uploader_upload_failed_ticker">Ha fallat la pujada</string>
     <string name="uploader_upload_files_behaviour">Opció de càrrega:</string>
     <string name="uploader_upload_files_behaviour_move_to_nextcloud_folder">Mou al fitxer a la carpeta %1$s</string>
@@ -753,6 +796,7 @@
     <string name="uploads_view_upload_status_failed_localfile_error">No s\'ha trobat el fitxer local</string>
     <string name="uploads_view_upload_status_failed_permission_error">Error de permisos</string>
     <string name="uploads_view_upload_status_failed_ssl_certificate_not_trusted">Certificat de servidor no fiable</string>
+    <string name="uploads_view_upload_status_fetching_server_version">Obtenint la versió del servidor...</string>
     <string name="uploads_view_upload_status_service_interrupted">L\'aplicació s\'ha tancat</string>
     <string name="uploads_view_upload_status_succeeded">Completat</string>
     <string name="uploads_view_upload_status_unknown_fail">Error desconegut</string>

+ 5 - 0
src/main/res/values-da/strings.xml

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">Slettet</string>
     <string name="pref_behaviour_entries_keep_file">forblevet i oprindelig mappe</string>
     <string name="pref_behaviour_entries_move">Overført til app mappe</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Hvad skal ske, hvis filen allerede findes?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Spørg mig altid</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Overskriv fjerntliggende version</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Omdøb ny version</string>
+    <string name="pref_instant_name_collision_policy_title">Hvad skal ske, hvis filen allerede findes?</string>
     <string name="prefs_add_account">Tilføj konto</string>
     <string name="prefs_calendar_contacts">Synkroniser kalender &amp; kontakter</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Server adressen for kontoen kunne ikke findes for DAVx5 (før kendt som DAVdroid)</string>

+ 4 - 0
src/main/res/values-fa/strings.xml

@@ -142,6 +142,7 @@
     <string name="confirmation_remove_folders_alert">آیا واقعاً می خواهید موارد انتخاب شده و محتوای آنها حذف شود؟</string>
     <string name="confirmation_remove_local">فقط محلی</string>
     <string name="conflict_message_headline">کدام فایل ها را می خواهید نگه دارید ؟</string>
+    <string name="conflict_new_file">فایل جدید</string>
     <string name="contaclist_restore_selected">بازیابی مخاطبین انتخاب شده</string>
     <string name="contactlist_account_chooser_title">حساب را برای وارد کردن انتخاب کنید</string>
     <string name="contactlist_item_icon">آیکون کاربر برای لیست تماس</string>
@@ -253,6 +254,8 @@
     <string name="error_starting_direct_camera_upload">خطا در شروع دوربین</string>
     <string name="etm_accounts">حساب‌ها</string>
     <string name="etm_background_job_progress">پیش رفتن</string>
+    <string name="etm_background_job_state">وضعیت</string>
+    <string name="etm_background_job_user">کاربر</string>
     <string name="etm_background_jobs">کارهای پس زمینه</string>
     <string name="etm_preferences">اولویت ها</string>
     <string name="etm_title">حالت تست مهندسی</string>
@@ -359,6 +362,7 @@
     <string name="host_your_own_server">میزبان سرور خود</string>
     <string name="instant_upload_on_charging">فقط هنگامی که گوشی شارژ می شود آپلود کنید</string>
     <string name="instant_upload_path">آپلود فوری</string>
+    <string name="invalid_url">آدرس اینترنتی اشتباه</string>
     <string name="learn_more">بیشتر بدانید</string>
     <string name="link">Link</string>
     <string name="list_layout">Listed layout</string>

+ 1 - 1
src/main/res/values-gl/strings.xml

@@ -37,7 +37,7 @@
     <string name="auth_access_failed">Acceso fallado: %1$s</string>
     <string name="auth_account_does_not_exist">A conta aínda non existe no dispositivo</string>
     <string name="auth_account_not_new">Xa existe unha conta neste dispositivo cos mesmos datos de usuario e servidor</string>
-    <string name="auth_account_not_the_same">O usuario que inseriu non coincide co usuario desta conta</string>
+    <string name="auth_account_not_the_same">O usuario que introduciu non coincide co usuario desta conta</string>
     <string name="auth_bad_oc_version_title">Versión do servidor non recoñecida</string>
     <string name="auth_can_not_auth_against_server">Non é posíbel autenticarse neste servidor</string>
     <string name="auth_check_server">Comprobar o servidor</string>

+ 5 - 0
src/main/res/values-hu-rHU/strings.xml

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">törölt</string>
     <string name="pref_behaviour_entries_keep_file">megtartva az eredeti mappában</string>
     <string name="pref_behaviour_entries_move">áthelyezve az alkalmazásmappába</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Mit tegyen, ha a fájl már létezik?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Kérdezzen rá mindig</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Távoli verzió felülírása</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Új verzió átnevezése</string>
+    <string name="pref_instant_name_collision_policy_title">Mit tegyen, ha a fájl már létezik?</string>
     <string name="prefs_add_account">Fiók hozzáadása</string>
     <string name="prefs_calendar_contacts">Naptár és névjegyek szinkronizálása</string>
     <string name="prefs_calendar_contacts_address_resolve_error">A fiók kiszolgálócíme nem oldható fel a DAVx5 (régi nevén DAVdroid) számára</string>

+ 1 - 1
src/main/res/values-lt-rLT/strings.xml

@@ -385,7 +385,7 @@
     <string name="notification_icon">Pranešimo piktograma</string>
     <string name="notifications_loading_activity">Įkeliami pranešimai…</string>
     <string name="notifications_no_results_headline">Pranešimų nėra</string>
-    <string name="notifications_no_results_message">Prašome patikrinti vėliau</string>
+    <string name="notifications_no_results_message">Prašome patikrinti vėliau.</string>
     <string name="offline_mode">Nėra interneto ryšio</string>
     <string name="overflow_menu">Daugiau meniu</string>
     <string name="pass_code_configure_your_pass_code">Įveskite užraktą</string>

+ 30 - 0
src/main/res/values-lv/strings.xml

@@ -43,6 +43,7 @@
     <string name="auth_check_server">Pārbaudīt serveri</string>
     <string name="auth_connection_established">Savienojums ir izveidots</string>
     <string name="auth_expired_basic_auth_toast">Lūdzu, ievadiet pašreizējo paroli</string>
+    <string name="auth_fail_get_user_name">Jūsu serveris neatgriež pareizu lietotāja ID, lūdzu, sazinieties ar administratoru</string>
     <string name="auth_host_address">Servera adrese</string>
     <string name="auth_host_url">Servera adrese https://…</string>
     <string name="auth_illegal_login_used">Nepareizs pierakstīšanās datu URL izmantots</string>
@@ -71,13 +72,22 @@
     <string name="auth_username">Lietotājvārds</string>
     <string name="auth_wrong_connection_title">Neizdevās izveidot savienojumu</string>
     <string name="auth_wtf_reenter_URL">Neparedzēts stāvoklis, lūdzu ievadiet servera adresi no jauna</string>
+    <string name="auto_upload_file_behaviour_kept_in_folder">glabāts oriģinālajā mapē, jo tā ir tikai lasāma</string>
     <string name="auto_upload_on_wifi">Augšupielādēt tikai neirobežotā Wi-Fi</string>
+    <string name="autoupload_configure">Konfigurē</string>
+    <string name="autoupload_create_new_custom_folder">Izveidot jaunu pielāgotu mapes iestatījumu</string>
+    <string name="autoupload_custom_folder">Pielāgotas mapes iestatīšana</string>
+    <string name="autoupload_disable_power_save_check">Atspējot enerģijas taupīšanas pārbaudi</string>
+    <string name="autoupload_hide_folder">Paslēpt mapi</string>
     <string name="avatar">Profila attēls</string>
     <string name="battery_optimization_close">Aizvērt</string>
     <string name="battery_optimization_disable">Deaktivēt</string>
+    <string name="battery_optimization_message">Jūsu ierīcē ir iespējota akumulatora optimizācija. Auto augšupielāde pareizi darbosies tikai tad, kad jūs lietotni izņemsiet no šīs opcijas.</string>
+    <string name="battery_optimization_title">Akumulatora optimizācija</string>
     <string name="certificate_load_problem">Radusies problēma, ielādējot sertifikātu.</string>
     <string name="choose_local_folder">Izvēlies lokālo mapi…</string>
     <string name="choose_remote_folder">Izvēlies attālināto mapi…</string>
+    <string name="clear_notifications_failed">Neizdevās notīrīt paziņojumus.</string>
     <string name="clipboard_label">Teksts kopēts no %1$s</string>
     <string name="clipboard_no_text_to_copy">Nav teksta, ko saglabāt starpliktuvē</string>
     <string name="clipboard_text_copied">Saite nokopēta</string>
@@ -116,14 +126,19 @@
     <string name="community_testing_headline">Palīdzi testējot</string>
     <string name="community_testing_report_text">Ziņojiet par problēmu Github</string>
     <string name="community_testing_version_text">Interesēts mums palīdzēt izmēģinot jauno testēšanas versiju?</string>
+    <string name="configure_new_media_folder_detection_notifications">Konfigurēt</string>
     <string name="confirmation_remove_file_alert">Vai tiešām vēlaties izdzēst %1$s?</string>
     <string name="confirmation_remove_files_alert">Vai tiešām vēlies dzēst izvēlētos objektus?</string>
     <string name="confirmation_remove_folder_alert">Vai tiešām vēlaties izdzēst %1$s un tā saturu?</string>
     <string name="confirmation_remove_folders_alert">Vai tiešām vēlies dzēst izvēlētos objektus un to saturu?</string>
     <string name="confirmation_remove_local">Tikai lokālos</string>
+    <string name="conflict_already_existing_file">Jau esošs fails</string>
+    <string name="conflict_dialog_error">Kļūda veidojot konfliktu dialogu!</string>
     <string name="conflict_message_headline">Kuras datnes vēlies paturēt?</string>
+    <string name="conflict_new_file">Jauns fails</string>
     <string name="contaclist_restore_selected">Atjaunot izvēlētos kontaktus</string>
     <string name="contactlist_account_chooser_title">Izvēlieties kontu importēšanai</string>
+    <string name="contactlist_item_icon">Lietotāja ikona kontaktpersonu sarakstam</string>
     <string name="contactlist_no_permission">Nav dota atļauja, importēšana neizdevās</string>
     <string name="contacts_automatic_backup">Automātiskā dublēšana</string>
     <string name="contacts_backup_button">Dublēt tagad</string>
@@ -133,23 +148,37 @@
     <string name="contacts_preferences_no_file_found">Datne nav atrasta</string>
     <string name="contacts_preferences_something_strange_happened">Nevarēja atrast pēdējo dublējumu!</string>
     <string name="contacts_read_permission">Vajadzīga atļauja lasīt kontaktu sarakstu</string>
+    <string name="copied_to_clipboard">Nokopēts starpliktuvē</string>
     <string name="copy_file_error">Radās kļūda, mēģinot kopēt šo datni vai mapi</string>
     <string name="copy_file_invalid_into_descendent">Nav iespējams kopēt mapi tās apakšmapē</string>
     <string name="copy_file_invalid_overwrite">Datne jau pastāv mērķa mapē</string>
     <string name="copy_file_not_found">Nevar nokopēt. Lūdzu, pārbaudiet, vai datne pastāv</string>
     <string name="copy_link">Kopēt saiti</string>
     <string name="copy_to">Kopēt uz…</string>
+    <string name="could_not_download_image">Nevarēja lejupielādēt pilnu attēlu</string>
+    <string name="could_not_retrieve_url">Neizdevās iegūt URL </string>
     <string name="create_dir_fail_msg">Nevarēja izveidot mapi</string>
+    <string name="create_new">Izveidot jaunu</string>
+    <string name="create_new_document">Izveidot jaunu dokumentu </string>
     <string name="create_new_folder">Izveidot jaunu mapi</string>
+    <string name="create_new_presentation">Izveidot jaunu prezentāciju</string>
+    <string name="create_new_spreadsheet">Izveidot jaunu izklājlapu</string>
+    <string name="create_rich_workspace">Pievienot mapes informāciju</string>
+    <string name="creates_rich_workspace">pievieno mapes informāciju</string>
     <string name="credentials_disabled">Akreditācijas dati atspējoti</string>
     <string name="date_unknown">Nezināms</string>
     <string name="default_credentials_wrong">Nepareizi akreditācijas dati</string>
     <string name="delete_account">Noņemt kontu</string>
     <string name="delete_account_warning">Noņemt kontu %s un izdzēst visas vietēji saglabātās datnes?\n\nDzēšanu nevar atsaukt.</string>
+    <string name="delete_entries">Dzēst ierakstus</string>
     <string name="deselect_all">Atcelt atzīmi</string>
+    <string name="dev_version_new_version_available">Pieejama jauna versija  </string>
+    <string name="dev_version_no_information_available">Informācija nav pieejama.</string>
+    <string name="dev_version_no_new_version_available">Jauna versija nav pieejama.</string>
     <string name="digest_algorithm_not_available">Šis algoritms nav pieejams jūsu tālrunī.</string>
     <string name="disable_new_media_folder_detection_notifications">Deaktivēt</string>
     <string name="dismiss">Atmest</string>
+    <string name="dismiss_notification_description">Noraidīt paziņojumu</string>
     <string name="downloader_download_failed_content">Nevarēja lejupielādēt %1$s</string>
     <string name="downloader_download_failed_credentials_error">Lejupielāde neizdevās, piesakieties vēlreiz.</string>
     <string name="downloader_download_failed_ticker">Neizdevās lejupielādēt</string>
@@ -160,6 +189,7 @@
     <string name="downloader_download_succeeded_ticker">Lejupielādēts</string>
     <string name="downloader_not_downloaded_yet">Vēl nav lejupielādēts</string>
     <string name="drawer_close">Aizvērt malu</string>
+    <string name="drawer_community">Kopiena</string>
     <string name="drawer_current_account">Pašreizējais konts</string>
     <string name="drawer_end_account">Pēdējais konts</string>
     <string name="drawer_item_activities">Darbības</string>

+ 8 - 0
src/main/res/values-nb-rNO/strings.xml

@@ -466,6 +466,11 @@
     <string name="pref_behaviour_entries_delete_file">slettet</string>
     <string name="pref_behaviour_entries_keep_file">beholdt i opprinnelig mappe</string>
     <string name="pref_behaviour_entries_move">flyttet til app-mappe</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Hva skal gjøres om filen finnes?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Spør hver gang</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Skriv over versjon på tjener</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Endre navn på ny versjon</string>
+    <string name="pref_instant_name_collision_policy_title">Hva skal gjøres om filen finnes?</string>
     <string name="prefs_add_account">Legg til en konto</string>
     <string name="prefs_calendar_contacts">Synkroniser kalender &amp; kontakter</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Adresse til server kan ikke nås for DAVx5 (tidligere DAVdroid)</string>
@@ -490,6 +495,7 @@
     <string name="prefs_instant_upload_path_use_subfolders_summary">Lagre filer i undermapper basert på år og måned</string>
     <string name="prefs_instant_upload_path_use_subfolders_title">Bruk undermapper</string>
     <string name="prefs_license">Lisens</string>
+    <string name="prefs_lock">App tilgangskode</string>
     <string name="prefs_lock_device_credentials_enabled">Enhetens legitimasjon er aktivert</string>
     <string name="prefs_lock_device_credentials_not_setup">Ingen legitimasjon for enhet er satt opp.</string>
     <string name="prefs_lock_none">Ingen</string>
@@ -538,7 +544,9 @@
     <string name="saml_authentication_wrong_pass">Feil passord</string>
     <string name="scanQR_description">Legg inn med QR-kode</string>
     <string name="screenshot_01_gridView_heading">Beskytter din data</string>
+    <string name="screenshot_01_gridView_subline">produktivitet på egen tjener</string>
     <string name="screenshot_02_listView_heading">Bla gjennom og del</string>
+    <string name="screenshot_03_drawer_heading">Aktivitet, delinger...</string>
     <string name="screenshot_04_accounts_heading">Alle kontoene dine</string>
     <string name="screenshot_04_accounts_subline">på et plass</string>
     <string name="screenshot_05_autoUpload_heading">Automatisk opplastning</string>

+ 10 - 6
src/main/res/values-night/colors.xml

@@ -20,19 +20,20 @@
 -->
 <resources>
 
-    <color name="text_color">#ffffff</color>
+    <color name="text_color">#E3E3E3</color>
     <color name="text_color_inverse">#000000</color>
+    <color name="secondary_text_color">#A5A5A5</color>
+    <color name="list_divider_background">#222222</color>
 
     <!-- Colors -->
-    <color name="bg_default">#151515</color>
+    <color name="bg_default">#121212</color>
     <color name="fg_contrast">#717171</color>
     <color name="primary_button_text_color">#000000</color>
-
-    <color name="uploader_list_separator_color">#2a2a2a</color>
+    <color name="bg_elevation_one">#2D2D2D</color>
 
     <!-- Multiselect backgrounds -->
-    <color name="action_mode_status_bar_background">#ECECEC</color>
-    <color name="selected_item_background">#757575</color>
+    <color name="action_mode_background">@color/appbar</color>
+    <color name="selected_item_background">#373535</color>
 
     <color name="filelist_icon_background">#222222</color>
 
@@ -42,4 +43,7 @@
     <color name="switch_track_color_unchecked">#B3FFFFFF</color>
     <color name="drawer_active_item_background">@color/white</color>
 
+    <!-- App bar -->
+    <color name="appbar">#1E1E1E</color>
+    <color name="fontAppbar">@android:color/white</color>
 </resources>

+ 16 - 11
src/main/res/values-nl/strings.xml

@@ -142,8 +142,8 @@
     <string name="confirmation_remove_folders_alert">Wil je de geselecteerde objecten en hun inhoud echt verwijderen?</string>
     <string name="confirmation_remove_local">Alleen lokaal</string>
     <string name="conflict_already_existing_file">Al bestaand bestand</string>
-    <string name="conflict_dialog_error">Fout bij het maken van conflict dialoog!</string>
-    <string name="conflict_message_description">Als je beide versies selecteerde, zal het lokale bestand een nummer aan de naam toegevoegd krijgen.</string>
+    <string name="conflict_dialog_error">Fout bij het maken van conflictdialoog!</string>
+    <string name="conflict_message_description">Als je beide versies selecteert, zal het lokale bestand een nummer aan de naam toegevoegd krijgen.</string>
     <string name="conflict_message_headline">Welke bestanden wil je bewaren?</string>
     <string name="conflict_new_file">Nieuw bestand</string>
     <string name="contaclist_restore_selected">Herstel de geselecteerde contactpersonen</string>
@@ -152,7 +152,7 @@
     <string name="contactlist_no_permission">Geen permissies, niets geïnporteerd!</string>
     <string name="contacts_automatic_backup">Automatische backup</string>
     <string name="contacts_backup_button">Maak nu een backup</string>
-    <string name="contacts_last_backup">Laatsts backup</string>
+    <string name="contacts_last_backup">Laatste backup</string>
     <string name="contacts_preference_backup_never">nooit</string>
     <string name="contacts_preference_choose_date">Kies datum</string>
     <string name="contacts_preferences_backup_scheduled">Backup ingepland en zal zo starten</string>
@@ -171,13 +171,13 @@
     <string name="could_not_download_image">Kan de volledige afbeelding niet downloaden</string>
     <string name="could_not_retrieve_url">Kon URL niet ophalen</string>
     <string name="create_dir_fail_msg">Kan map niet aanmaken</string>
-    <string name="create_new">Creëer nieuw</string>
+    <string name="create_new">Maak nieuw</string>
     <string name="create_new_document">Creëer nieuw document</string>
     <string name="create_new_folder">Nieuwe map aanmaken</string>
     <string name="create_new_presentation">Creëer nieuwe presentatie</string>
     <string name="create_new_spreadsheet">Creëren nieuw werkblad</string>
     <string name="create_rich_workspace">Toevoegen mapinfo</string>
-    <string name="creates_rich_workspace">creëert mapinfo</string>
+    <string name="creates_rich_workspace">maakt mapinfo</string>
     <string name="credentials_disabled">Inloggegevens uitgeschakeld</string>
     <string name="date_unknown">Onbekend</string>
     <string name="default_credentials_wrong">Ongeldige inloggegevens</string>
@@ -257,17 +257,17 @@
     <string name="error_retrieving_templates">Fout bij ophalen sjablonen</string>
     <string name="error_starting_direct_camera_upload">Fout bij starten camera</string>
     <string name="etm_accounts">Accounts</string>
-    <string name="etm_background_job_name">Job naam</string>
+    <string name="etm_background_job_name">Taaknaam</string>
     <string name="etm_background_job_progress">Voortgang</string>
     <string name="etm_background_job_started">Gestart</string>
-    <string name="etm_background_job_state">Staat</string>
+    <string name="etm_background_job_state">Status</string>
     <string name="etm_background_job_user">Gebruiker</string>
     <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">Achtergrondtaken</string>
-    <string name="etm_background_jobs_cancel_all">Annuleer alle jobs</string>
+    <string name="etm_background_jobs_cancel_all">Annuleer alle taken</string>
     <string name="etm_background_jobs_prune">Inactieve taken opruimen</string>
-    <string name="etm_background_jobs_start_test_job">Start Test Job</string>
-    <string name="etm_background_jobs_stop_test_job">Stop Test Job</string>
+    <string name="etm_background_jobs_start_test_job">Start testtaak</string>
+    <string name="etm_background_jobs_stop_test_job">Stop testtaak</string>
     <string name="etm_migrations">Migratie (app upgrade)</string>
     <string name="etm_preferences">Voorkeuren</string>
     <string name="etm_title">Engineering Testmodus</string>
@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">verwijderd worden</string>
     <string name="pref_behaviour_entries_keep_file">bewaard worden in originele map</string>
     <string name="pref_behaviour_entries_move">verplaatst worden naar app-map</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Wat als het bestand al bestaat?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Vraag me elke keer</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Overschrijven </string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Hernoem nieuwe versie</string>
+    <string name="pref_instant_name_collision_policy_title">Wat als het bestand al bestaat?</string>
     <string name="prefs_add_account">Account toevoegen</string>
     <string name="prefs_calendar_contacts">Synchroniseren agenda &amp; contactpersonen</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Serveradres voor deze account kon nicht gevonden worden voor DAVx5 (vroeger bekend als DAVdroid)</string>
@@ -551,7 +556,7 @@
     <string name="saml_authentication_wrong_pass">Onjuist wachtwoord</string>
     <string name="scanQR_description">Inloggen via QR code</string>
     <string name="screenshot_01_gridView_heading">Beschermt jouw gegevens</string>
-    <string name="screenshot_01_gridView_subline">eigen-hosting productiviteit</string>
+    <string name="screenshot_01_gridView_subline">self-hosting productiviteit</string>
     <string name="screenshot_02_listView_heading">Blader en deel</string>
     <string name="screenshot_02_listView_subline">alle taken onder je vindertoppen</string>
     <string name="screenshot_03_drawer_heading">Activiteiten, delen, ...</string>

+ 14 - 0
src/main/res/values-pt-rPT/strings.xml

@@ -79,6 +79,7 @@
     <string name="autoupload_create_new_custom_folder">Criar nova estrutura de pasta personalizada</string>
     <string name="autoupload_custom_folder">Definir uma pasta personalizada</string>
     <string name="autoupload_disable_power_save_check">Desabilitar a verificação de poupança de energia</string>
+    <string name="autoupload_hide_folder">Esconder pasta</string>
     <string name="avatar">Avatar</string>
     <string name="battery_optimization_close">Fechar</string>
     <string name="battery_optimization_disable">Desativar</string>
@@ -139,6 +140,7 @@
     <string name="confirmation_remove_folder_alert">Deseja realmente apagar %1$s e o seu conteúdo?</string>
     <string name="confirmation_remove_folders_alert">Quer realmente apagar os itens seleccionados e os seus conteúdos?</string>
     <string name="confirmation_remove_local">Apenas localmente</string>
+    <string name="conflict_already_existing_file">Ficheiro já existente</string>
     <string name="conflict_message_headline">Quais os ficheiros que pretende manter?</string>
     <string name="conflict_new_file">Ficheiro novo</string>
     <string name="contaclist_restore_selected">Restaurar contactos seleccionados</string>
@@ -169,6 +171,7 @@
     <string name="create_new_folder">Criar nova pasta</string>
     <string name="create_new_presentation">Criar nova apresentação</string>
     <string name="create_new_spreadsheet">Criar nova folha de cálculo</string>
+    <string name="create_rich_workspace">Adicionar info da pasta</string>
     <string name="credentials_disabled">Credenciais desativadas</string>
     <string name="date_unknown">Desconhecido</string>
     <string name="default_credentials_wrong">Credenciais incorretas</string>
@@ -247,10 +250,12 @@
     <string name="etm_background_job_started">Iniciado</string>
     <string name="etm_background_job_state">Estado</string>
     <string name="etm_background_job_user">Utilizador</string>
+    <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">Tarefas de segundo plano</string>
     <string name="etm_preferences">Preferências</string>
     <string name="fab_label">Adicionar ou enviar</string>
     <string name="failed_to_download">Falhou a passagem do ficheiro ao gestor de transferências</string>
+    <string name="failed_to_print">Falhou a impressão do ficheiro</string>
     <string name="fallback_weblogin_back">Anterior</string>
     <string name="fallback_weblogin_text">Reverter para método de início de sessão anterior</string>
     <string name="favorite">Adicionar aos favoritos</string>
@@ -445,6 +450,7 @@
     <string name="pref_behaviour_entries_delete_file">eliminado</string>
     <string name="pref_behaviour_entries_keep_file">mantido na pasta original</string>
     <string name="pref_behaviour_entries_move">movido para pasta da aplicação</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Perguntar-me todas as vezes</string>
     <string name="prefs_add_account">Adicionar conta</string>
     <string name="prefs_calendar_contacts">Sincronizar calendário e contactos</string>
     <string name="prefs_calendar_contacts_address_resolve_error">O endereço do servidor da conta não pôde ser resolvido pelo DAVx5 (anteriormente conhecido como DAVdroid)</string>
@@ -515,6 +521,8 @@
     <string name="saml_authentication_required_text">Necessário palavra-passe</string>
     <string name="saml_authentication_wrong_pass">Palavra-passe errada</string>
     <string name="scanQR_description">acesso através de código QR</string>
+    <string name="screenshot_04_accounts_subline">Num único lugar</string>
+    <string name="screenshot_06_davdroid_subline">Sincronizar com DAVx5</string>
     <string name="search_users_and_groups_hint">Pesquisar utilizadores e grupos</string>
     <string name="select_all">Selecionar tudo</string>
     <string name="select_template">Seleccionar modelo</string>
@@ -635,6 +643,7 @@
     <string name="sync_foreign_files_forgotten_explanation">Para a versão 1.3.16, os ficheiros enviados deste dispositivo são copiados para a seguinte pasta %1$s para evitar a falha de dados quando um só ficheiro é sincronizado em múltiplas contas.\n\nDevido a esta alteração, todos os ficheiros enviados com as versões mais antigas desta aplicação serão copiadas para a seguinte pasta %2$s. Contudo, um erro evitou a conclusão desta operação durante a sincronização da conta. Pode deixar o(s) ficheiro(s) como estão e eliminar a associação para %3$s, ou mover o(s) ficheiro(s) para a pasta %1$s e manter a associação para %4$s.\n\nEm baixo está a lista dos ficheiros locais e dos ficheiros remotos na pasta %5$s em que foram associados.</string>
     <string name="sync_foreign_files_forgotten_ticker">Alguns ficheiros locais foram esquecidos</string>
     <string name="sync_in_progress">Obter a versão mais recente do ficheiro.</string>
+    <string name="sync_not_enough_space_dialog_action_choose">Escolher o que sincronizar</string>
     <string name="sync_not_enough_space_dialog_action_free_space">Libertar espaço</string>
     <string name="sync_not_enough_space_dialog_title">Espaço insuficiente</string>
     <string name="sync_status_button">Botão de estado de sincronização</string>
@@ -686,6 +695,7 @@ Aproveite o novo e melhorado envio automático.</string>
     <string name="upload_list_delete">Apagar</string>
     <string name="upload_list_empty_headline">Sem envios disponíveis</string>
     <string name="upload_list_empty_text_auto_upload">Envie algum conteúdo ou ative o envio automático.</string>
+    <string name="upload_list_resolve_conflict">Resolver conflito</string>
     <string name="upload_local_storage_full">Armazenamento local cheio</string>
     <string name="upload_local_storage_not_copied">O ficheiro não pôde ser copiado para o armazenamento local</string>
     <string name="upload_lock_failed">Falha ao trancar pasta</string>
@@ -772,6 +782,10 @@ Aproveite o novo e melhorado envio automático.</string>
         <item quantity="one">%1$d ficheiro</item>
         <item quantity="other">%1$d ficheiros</item>
     </plurals>
+    <plurals name="synced_folders_show_hidden_folders">
+        <item quantity="one">Mostrar %1$d pasta oculta</item>
+        <item quantity="other">Show %1$d pastas ocultas</item>
+    </plurals>
     <plurals name="items_selected_count">
         <item quantity="one">%d selecionado</item>
         <item quantity="other">%d selecionados</item>

+ 5 - 0
src/main/res/values-ru/strings.xml

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">удалён</string>
     <string name="pref_behaviour_entries_keep_file">оставлен в исходном каталоге</string>
     <string name="pref_behaviour_entries_move">перемещен в каталог приложения</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Действие в случае, если файл уже существует</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Всегда спрашивать</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Перезаписать удалённую версию</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Переименовать новую версию</string>
+    <string name="pref_instant_name_collision_policy_title">Действие в случае, если файл уже существует</string>
     <string name="prefs_add_account">Добавить аккаунт</string>
     <string name="prefs_calendar_contacts">Синхронизировать календарь и контакты</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Не удалось определить адрес сервера этой учётной записи для его передачи в приложение  DAVx5 (ранее называвшееся DAVdroid)</string>

+ 5 - 0
src/main/res/values-sk-rSK/strings.xml

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">vymazané</string>
     <string name="pref_behaviour_entries_keep_file">ponechané v pôvodnom priečinku</string>
     <string name="pref_behaviour_entries_move">presunuté do aplikačného priečinka</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Čo urobiť, ak súbor už existuje?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Spýtať sa zakaždým</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Prepísať vzdialenú verziu</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Premenovať novú verziu</string>
+    <string name="pref_instant_name_collision_policy_title">Čo urobiť, ak súbor už existuje?</string>
     <string name="prefs_add_account">Pridať účet</string>
     <string name="prefs_calendar_contacts">Synchronizovať kalendár &amp; kontakty</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Nepodarilo sa zistiť adresu účtu pre DAVx5 (predtým známy ako DAVdroid)</string>

+ 38 - 0
src/main/res/values-uk/strings.xml

@@ -285,6 +285,7 @@
     <string name="file_list_empty_headline_server_search_photos">Жодного фото</string>
     <string name="file_list_empty_headline_server_search_videos">Жодного відеофайлу</string>
     <string name="file_list_empty_moving">Нічого немає. Ви можете додати теку.</string>
+    <string name="file_list_empty_recently_added">Не знайдено нещодавно доданих файлів</string>
     <string name="file_list_empty_recently_added_filter">Нещодавно додані файли відсутні</string>
     <string name="file_list_empty_recently_modified">Відсутні зміни серед файлів за останні 7 днів</string>
     <string name="file_list_empty_recently_modified_filter">Жодного зміненого файлу за останні 7 днів.</string>
@@ -293,21 +294,37 @@
     <string name="file_list_empty_shared_headline">Жодного спільного файлу</string>
     <string name="file_list_empty_text_photos">Завантажити фотографії чи активувати автозавантаження</string>
     <string name="file_list_empty_text_photos_filter">Немає фотографій.</string>
+    <string name="file_list_empty_text_videos">Додайте файли з відео або увімкніть автоматичне завантаження.</string>
     <string name="file_list_empty_text_videos_filter">Немає відеозаписів.</string>
     <string name="file_list_folder">тека</string>
     <string name="file_list_loading">Завантаження…</string>
     <string name="file_list_no_app_for_file_type">Відсутній застосунок для відкриття файлу такого типу.</string>
     <string name="file_list_seconds_ago">секунди тому</string>
+    <string name="file_migration_checking_destination">Перевірка місця призначення...</string>
     <string name="file_migration_cleaning">Очищення...</string>
+    <string name="file_migration_dialog_title">Оновлення місця збереження</string>
+    <string name="file_migration_directory_already_exists">Тека з даними вже існує. Виберіть одну з таких:</string>
+    <string name="file_migration_failed_dir_already_exists">Тека Nextcloud вже існує</string>
     <string name="file_migration_failed_not_enough_space">Потрібно більше місця</string>
+    <string name="file_migration_failed_not_readable">Неможливо прочитати вихідний файл</string>
+    <string name="file_migration_failed_not_writable">Неможливо записати до файлу призначення</string>
+    <string name="file_migration_failed_while_coping">Помилка під час міграції</string>
+    <string name="file_migration_failed_while_updating_index">Неможливо оновити індекс</string>
     <string name="file_migration_migrating">Переміщення даних…</string>
     <string name="file_migration_ok_finished">Завершено</string>
     <string name="file_migration_override_data_folder">Замінити</string>
+    <string name="file_migration_preparing">Підготовка до міграції</string>
+    <string name="file_migration_restoring_accounts_configuration">Відновлення налаштувань облікового запису...</string>
+    <string name="file_migration_saving_accounts_configuration">Збереження налаштувань облікового запису...</string>
+    <string name="file_migration_source_not_readable_title">Неможливо прочитати вихідну теку!</string>
     <string name="file_migration_updating_index">Оновлення індексу...</string>
     <string name="file_migration_use_data_folder">Використовувати</string>
     <string name="file_migration_waiting_for_unfinished_sync">Очікування повної синхронізації…</string>
     <string name="file_not_found">Файл не знайдено</string>
+    <string name="file_not_synced">Неможливо синхронізувати файл. Буде показано останню доступну версію.</string>
     <string name="file_rename">Перейменувати</string>
+    <string name="file_version_restored_error">Помилка під час відновлення версії файлу!</string>
+    <string name="file_version_restored_successfully">Успішно відновлено версію файлу.</string>
     <string name="filedetails_download">Завантажити</string>
     <string name="filedetails_renamed_in_upload_msg">Під час завантаження назву файлу було змінено %1$s</string>
     <string name="filedetails_sync_file">Синхронізація</string>
@@ -316,15 +333,20 @@
     <string name="filename_forbidden_characters">Заборонені символи: / \\ &lt; &gt; : \" | ? *</string>
     <string name="filename_forbidden_charaters_from_server">У назві файлу міститься щонайменше один неправильний символ</string>
     <string name="filename_hint">Ім\'я файлу</string>
+    <string name="first_run_1_text">Зберігайте ваші дані у безпечному місці, яке ви можете самі контролювати</string>
     <string name="folder_already_exists">Тека вже існує</string>
     <string name="folder_confirm_create">Створити</string>
+    <string name="folder_icon">Іконка теки</string>
     <string name="folder_list_empty_headline">Тут немає папок</string>
     <string name="folder_picker_choose_button_text">Обрати</string>
     <string name="forbidden_permissions">У вас відсутні повноваження %s</string>
     <string name="forbidden_permissions_copy">скопіювати даний файл</string>
+    <string name="forbidden_permissions_create">щоб створити цей файл</string>
     <string name="forbidden_permissions_delete">на видалення цього файла</string>
     <string name="forbidden_permissions_move">перемістити цей файл</string>
     <string name="forbidden_permissions_rename">на перейменування цього файла</string>
+    <string name="foreground_service_download">Звантаження файлів...</string>
+    <string name="foreground_service_upload">Завантаження файлів...</string>
     <string name="foreign_files_fail">Окремі файли неможливо перемістити</string>
     <string name="foreign_files_local_text">Локально: %1$s</string>
     <string name="foreign_files_move">Перемістити все</string>
@@ -334,20 +356,31 @@
     <string name="hint_note">Нотатка</string>
     <string name="hint_password">Пароль</string>
     <string name="host_not_available">Сервер недоступний</string>
+    <string name="host_your_own_server">Розмістити на власному сервері</string>
+    <string name="instant_upload_existing">Також завантажити існуючі файли</string>
     <string name="instant_upload_on_charging">Завантаження тільки під час заряджання</string>
     <string name="instant_upload_path">/InstantUpload</string>
+    <string name="invalid_url">Неправильний URL</string>
+    <string name="learn_more">Дізнатися більше</string>
     <string name="link">Посилання</string>
     <string name="local_file_list_empty">В цій теці немає файлів.</string>
+    <string name="local_file_not_found_message">Файл не знайдено у локальній файловій системі</string>
     <string name="local_folder_list_empty">Тут відсутні інші каталоги.</string>
     <string name="log_send_mail_subject">%1$s Android лог додатку</string>
     <string name="login">Увійти</string>
+    <string name="logs_menu_delete">Вилучити журнал</string>
     <string name="logs_menu_refresh">Оновити</string>
+    <string name="logs_menu_search">Шукати у журналі</string>
+    <string name="logs_menu_send">Надіслати журнал ел.поштою</string>
     <string name="logs_status_loading">Завантаження…</string>
     <string name="logs_title">Журнали</string>
+    <string name="maintenance_mode">Сервер у режимі обслуговування</string>
     <string name="manage_space_clear_data">Очистити дані</string>
     <string name="manage_space_title">Керувати простором</string>
+    <string name="media_err_invalid_progressive_playback">Неможливо транслювати мультимедійний файл</string>
     <string name="media_err_io">Неможливо відкрити мультимедійний файл</string>
     <string name="media_err_malformed">Мультимедійний файл має неправильно кодування</string>
+    <string name="media_err_timeout">Час на відтворення файлу вичерпано</string>
     <string name="media_err_unsupported">Кодек не підтримується</string>
     <string name="media_forward_description">Перемотка вперед</string>
     <string name="media_notif_ticker">%1$s музичний програвач</string>
@@ -364,8 +397,12 @@
     <string name="move_file_invalid_overwrite">Файл уже присутній в теці призначення</string>
     <string name="move_to">Перемістити до...</string>
     <string name="network_error_socket_exception">Помилка під час з\'єднання з сервером</string>
+    <string name="new_comment">Новий коментар...</string>
     <string name="new_media_folder_photos">photo</string>
     <string name="new_media_folder_videos">відео</string>
+    <string name="new_notification">Нове сповіщення</string>
+    <string name="new_version_was_created">Створено нову версію</string>
+    <string name="no_browser_available">Відсутні застосунки для обробки посилань</string>
     <string name="note_confirm">Надіслати</string>
     <string name="notification_channel_file_sync_name">Синхронізація файлів</string>
     <string name="notifications_no_results_headline">Немає сповіщень</string>
@@ -405,6 +442,7 @@
     <string name="prefs_manage_accounts">Управління обліковими записами</string>
     <string name="prefs_recommend">Порадити друзям</string>
     <string name="prefs_show_hidden_files">Відобразити сховані файли</string>
+    <string name="prefs_storage_path">Шлях до місця збереження</string>
     <string name="prefs_synced_folders_local_path_title">Локальний каталог</string>
     <string name="prefs_synced_folders_remote_path_title">Віддалений каталог</string>
     <string name="prefs_theme_title">Тема</string>

+ 6 - 19
src/main/res/values-v21/styles.xml

@@ -18,18 +18,14 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- General ownCloud app style -->
-    <style name="Theme.ownCloud" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
-        <item name="android:textColorSecondary">@color/secondary_text_color</item>
-        <item name="colorSecondary">@color/secondary_text_color</item>
+    <style name="Theme.ownCloud" parent="BaseTheme.ownCloud">
+        <item name="android:actionModeBackground">@color/action_mode_background</item>
         <item name="android:datePickerDialogTheme">@style/FallbackDatePickerDialogTheme</item>
     </style>
 
-    <style name="FallbackThemingTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+    <style name="FallbackThemingTheme" parent="FallbackThemingThemeBase">
+        <item name="android:actionModeBackground">@color/action_mode_background</item>
         <item name="android:datePickerDialogTheme">@style/FallbackDatePickerDialogTheme</item>
-        <item name="android:textColor">@color/text_color</item>
-        <item name="android:textColorSecondary">@color/secondary_text_color</item>
-        <item name="colorSecondary">@color/secondary_text_color</item>
-        <item name="android:alertDialogTheme">@style/FallbackTheming.Dialog</item>
     </style>
 
     <style name="FallbackDatePickerDialogTheme" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
@@ -55,18 +51,9 @@
         <item name="android:windowBackground">@color/bg_default</item>
     </style>
 
-    <style name="Theme.ownCloud.Toolbar">
+    <style name="Theme.ownCloud.Toolbar" parent="BaseTheme.ownCloud.Toolbar">
+        <item name="android:actionModeBackground">@color/action_mode_background</item>
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
-        <item name="windowNoTitle">true</item>
-        <item name="windowActionBar">false</item>
-        <item name="colorPrimary">@color/primary</item>
-        <item name="colorPrimaryDark">@color/primary_dark</item>
-        <item name="colorAccent">@color/color_accent</item>
-        <item name="android:alertDialogTheme">@style/Theme.ownCloud.Dialog</item>
-        <item name="alertDialogTheme">@style/ownCloud.AlertDialog</item>
-        <item name="searchViewStyle">@style/ownCloud.SearchView</item>
-        <item name="windowActionModeOverlay">true</item>
-        <item name="android:windowBackground">@color/bg_default</item>
     </style>
 
     <style name="Theme.ownCloud.Toolbar.Drawer">

+ 1 - 0
src/main/res/values-zh-rCN/strings.xml

@@ -478,6 +478,7 @@
     <string name="pref_behaviour_entries_delete_file">已删除</string>
     <string name="pref_behaviour_entries_keep_file">保留在原始文件夹</string>
     <string name="pref_behaviour_entries_move">移动到应用文件夹</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">每次问我</string>
     <string name="prefs_add_account">添加账号</string>
     <string name="prefs_calendar_contacts">同步日历和联系人</string>
     <string name="prefs_calendar_contacts_address_resolve_error">DAVx5(前身为 DAVdroid)无法解析该账号的服务器地址</string>

+ 3 - 0
src/main/res/values-zh-rTW/strings.xml

@@ -142,6 +142,8 @@
     <string name="confirmation_remove_folders_alert">您真的想要刪除所選項目和裡面的內容嗎?</string>
     <string name="confirmation_remove_local">只有本地</string>
     <string name="conflict_already_existing_file">檔案已存在</string>
+    <string name="conflict_dialog_error">建立衝突訊息時發生錯誤!</string>
+    <string name="conflict_message_description">若您同時選擇兩個版本,本地的檔案會在檔名後附加編號。</string>
     <string name="conflict_message_headline">您要保留哪一個檔案?</string>
     <string name="conflict_new_file">新增檔案</string>
     <string name="contaclist_restore_selected">還原所選的聯絡人</string>
@@ -259,6 +261,7 @@
     <string name="etm_background_job_progress">進度</string>
     <string name="etm_background_job_started">開始於</string>
     <string name="etm_background_job_state">狀態</string>
+    <string name="etm_background_job_user">使用者</string>
     <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">背景工作</string>
     <string name="etm_background_jobs_cancel_all">取消所有作業</string>

+ 11 - 7
src/main/res/values/colors.xml

@@ -24,16 +24,17 @@
     <color name="list_item_lastmod_and_filesize_text">@color/secondary_text_color</color>
     <color name="black">#000000</color>
     <color name="white">#ffffff</color>
-    <color name="text_color">#000000</color>
+    <color name="text_color">#333333</color>
+    <color name="drawer_text_color">@color/secondary_text_color</color>
     <color name="text_color_inverse">#ffffff</color>
     <color name="disabled_text">#ff888888</color>
-    <color name="list_divider_background">#eee</color>
+    <color name="list_divider_background">#eeeeee</color>
     <color name="fg_contrast">#656565</color>
     <color name="fg_inverse">#FFFFFF</color>
     <color name="filelist_icon_background">#DDDDDD</color>
     <color name="dark_background_text_color">#EEEEEE</color>
     <color name="transparent">#00000000</color>
-    <color name="secondary_text_color">#a0a0a0</color>
+    <color name="secondary_text_color">#666666</color>
     <color name="highlight_textColor_Warning">#e53935</color>
 
     <!-- Colors -->
@@ -52,6 +53,7 @@
     <color name="login_btn_tint">#ffffff</color>
     <color name="login_btn_stroke">#ffffff</color>
     <color name="bg_default">#FFFFFF</color>
+    <color name="bg_elevation_one">#FFFFFF</color>
     <color name="background_color_inverse">#000000</color>
     <color name="primary_button_background_color">@color/color_accent</color>
     <color name="primary_button_text_color">#ffffff</color>
@@ -64,15 +66,12 @@
 
     <color name="background_color_png">#FFFFFF</color>
 
-    <color name="uploader_list_separator_color">#ededed</color>
-
     <!-- special transparent action bar colors for image preview -->
     <color name="color_transparent">#201D2D44</color>
     <color name="color_dark_transparent">#40162233</color>
 
     <!-- Multiselect backgrounds -->
-    <color name="action_mode_background">#757575</color>
-    <color name="action_mode_status_bar_background">#616161</color>
+    <color name="action_mode_background">@color/fontAppbar</color>
     <color name="selected_item_background">#ECECEC</color>
 
     <color name="drawer_menu_icon">#757575</color>
@@ -85,4 +84,9 @@
     <color name="themed_fg">#FFFFFF</color>
     <color name="themed_fg_inverse">#000000</color>
     <color name="themed_bg">#222222</color>
+
+    <!-- App bar -->
+    <color name="appbar">@android:color/white</color>
+    <color name="fontAppbar">#666666</color>
+    <color name="fontSecondaryAppbar">#A5A5A5</color>
 </resources>

部分文件因文件數量過多而無法顯示