Skip to content

Commit f296ed2

Browse files
committed
refactor: unified auto upload architecture with subfolder support
- Replace separate picture/video upload fragments with unified SettingsAutoUploadFragment - Add AutoUploadPathBuilder for dynamic upload paths with subfolders (YEAR, YEAR_MONTH, YEAR_MONTH_DAY) - Refactor AutomaticUploadsWorker to support multiple folder backup configurations - Update dependency injection modules for unified architecture - Add UseSubfoldersBehaviour enum and integrate into FolderBackUpConfiguration - Fix database migration: replace broken MIGRATION_43_44 with defensive MIGRATION_49_50 - Set legacy migration default to NONE to preserve existing user behavior - Update tests and copyright headers
1 parent f4bc15c commit f296ed2

47 files changed

Lines changed: 2434 additions & 1561 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

opencloudApp/src/main/java/eu/opencloud/android/dependecyinjection/UseCaseModule.kt

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,18 @@ import eu.opencloud.android.domain.authentication.usecases.GetBaseUrlUseCase
3535
import eu.opencloud.android.domain.authentication.usecases.LoginBasicAsyncUseCase
3636
import eu.opencloud.android.domain.authentication.usecases.LoginOAuthAsyncUseCase
3737
import eu.opencloud.android.domain.authentication.usecases.SupportsOAuth2UseCase
38+
import eu.opencloud.android.domain.automaticuploads.usecases.GetAutomaticUploadsConfigurationUseCase
39+
import eu.opencloud.android.domain.automaticuploads.usecases.GetFolderBackupConfigurationStreamUseCase
40+
import eu.opencloud.android.domain.automaticuploads.usecases.ResetFolderBackupConfigurationUseCase
41+
import eu.opencloud.android.domain.automaticuploads.usecases.SaveFolderBackupConfigurationUseCase
3842
import eu.opencloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromAccountAsStreamUseCase
3943
import eu.opencloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromAccountUseCase
4044
import eu.opencloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromEveryAccountUseCase
4145
import eu.opencloud.android.domain.availableoffline.usecases.SetFilesAsAvailableOfflineUseCase
4246
import eu.opencloud.android.domain.availableoffline.usecases.UnsetFilesAsAvailableOfflineUseCase
43-
import eu.opencloud.android.domain.automaticuploads.usecases.GetAutomaticUploadsConfigurationUseCase
44-
import eu.opencloud.android.domain.automaticuploads.usecases.GetPictureUploadsConfigurationStreamUseCase
45-
import eu.opencloud.android.domain.automaticuploads.usecases.GetVideoUploadsConfigurationStreamUseCase
46-
import eu.opencloud.android.domain.automaticuploads.usecases.ResetPictureUploadsUseCase
47-
import eu.opencloud.android.domain.automaticuploads.usecases.ResetVideoUploadsUseCase
48-
import eu.opencloud.android.domain.automaticuploads.usecases.SavePictureUploadsConfigurationUseCase
49-
import eu.opencloud.android.domain.automaticuploads.usecases.SaveVideoUploadsConfigurationUseCase
5047
import eu.opencloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase
5148
import eu.opencloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
5249
import eu.opencloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase
53-
import eu.opencloud.android.domain.files.usecases.IsAnyFileAvailableLocallyAndNotAvailableOfflineUseCase
5450
import eu.opencloud.android.domain.files.usecases.CleanConflictUseCase
5551
import eu.opencloud.android.domain.files.usecases.CleanWorkersUUIDUseCase
5652
import eu.opencloud.android.domain.files.usecases.CopyFileUseCase
@@ -68,6 +64,7 @@ import eu.opencloud.android.domain.files.usecases.GetSearchFolderContentUseCase
6864
import eu.opencloud.android.domain.files.usecases.GetSharedByLinkForAccountAsStreamUseCase
6965
import eu.opencloud.android.domain.files.usecases.GetSharesRootFolderForAccount
7066
import eu.opencloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase
67+
import eu.opencloud.android.domain.files.usecases.IsAnyFileAvailableLocallyAndNotAvailableOfflineUseCase
7168
import eu.opencloud.android.domain.files.usecases.ManageDeepLinkUseCase
7269
import eu.opencloud.android.domain.files.usecases.MoveFileUseCase
7370
import eu.opencloud.android.domain.files.usecases.RemoveFileUseCase
@@ -102,12 +99,12 @@ import eu.opencloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUs
10299
import eu.opencloud.android.domain.transfers.usecases.GetAllTransfersAsStreamUseCase
103100
import eu.opencloud.android.domain.transfers.usecases.GetAllTransfersUseCase
104101
import eu.opencloud.android.domain.transfers.usecases.UpdatePendingUploadsPathUseCase
105-
import eu.opencloud.android.domain.user.usecases.GetStoredQuotaUseCase
106102
import eu.opencloud.android.domain.user.usecases.GetStoredQuotaAsStreamUseCase
103+
import eu.opencloud.android.domain.user.usecases.GetStoredQuotaUseCase
107104
import eu.opencloud.android.domain.user.usecases.GetUserAvatarAsyncUseCase
108105
import eu.opencloud.android.domain.user.usecases.GetUserInfoAsyncUseCase
109-
import eu.opencloud.android.domain.user.usecases.GetUserQuotasUseCase
110106
import eu.opencloud.android.domain.user.usecases.GetUserQuotasAsStreamUseCase
107+
import eu.opencloud.android.domain.user.usecases.GetUserQuotasUseCase
111108
import eu.opencloud.android.domain.user.usecases.RefreshUserQuotaFromServerAsyncUseCase
112109
import eu.opencloud.android.domain.webfinger.usecases.GetOpenCloudInstanceFromWebFingerUseCase
113110
import eu.opencloud.android.domain.webfinger.usecases.GetOpenCloudInstancesFromAuthenticatedWebFingerUseCase
@@ -269,13 +266,9 @@ val useCaseModule = module {
269266

270267
// Camera Uploads
271268
factoryOf(::GetAutomaticUploadsConfigurationUseCase)
272-
factoryOf(::GetPictureUploadsConfigurationStreamUseCase)
273-
factoryOf(::GetVideoUploadsConfigurationStreamUseCase)
274-
factoryOf(::ResetPictureUploadsUseCase)
275-
factoryOf(::ResetVideoUploadsUseCase)
276-
factoryOf(::SavePictureUploadsConfigurationUseCase)
277-
factoryOf(::SaveVideoUploadsConfigurationUseCase)
278-
269+
factoryOf(::GetFolderBackupConfigurationStreamUseCase)
270+
factoryOf(::SaveFolderBackupConfigurationUseCase)
271+
factoryOf(::ResetFolderBackupConfigurationUseCase)
279272
// Accounts
280273
factoryOf(::RemoveAccountUseCase)
281274
}

opencloudApp/src/main/java/eu/opencloud/android/dependecyinjection/ViewModelModule.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ import eu.opencloud.android.presentation.security.passcode.PasscodeAction
4747
import eu.opencloud.android.presentation.security.pattern.PatternViewModel
4848
import eu.opencloud.android.presentation.settings.SettingsViewModel
4949
import eu.opencloud.android.presentation.settings.advanced.SettingsAdvancedViewModel
50-
import eu.opencloud.android.presentation.settings.automaticuploads.SettingsPictureUploadsViewModel
51-
import eu.opencloud.android.presentation.settings.automaticuploads.SettingsVideoUploadsViewModel
50+
import eu.opencloud.android.presentation.settings.automaticuploads.SettingsAutoUploadViewModel
5251
import eu.opencloud.android.presentation.settings.logging.SettingsLogsViewModel
5352
import eu.opencloud.android.presentation.settings.more.SettingsMoreViewModel
5453
import eu.opencloud.android.presentation.settings.security.SettingsSecurityViewModel
@@ -79,9 +78,7 @@ val viewModelModule = module {
7978
viewModelOf(::SettingsAdvancedViewModel)
8079
viewModelOf(::SettingsLogsViewModel)
8180
viewModelOf(::SettingsMoreViewModel)
82-
viewModelOf(::SettingsPictureUploadsViewModel)
8381
viewModelOf(::SettingsSecurityViewModel)
84-
viewModelOf(::SettingsVideoUploadsViewModel)
8582
viewModelOf(::SettingsViewModel)
8683
viewModelOf(::FileOperationsViewModel)
8784

@@ -103,4 +100,7 @@ val viewModelModule = module {
103100
viewModel { (accountName: String, showPersonalSpace: Boolean) ->
104101
SpacesListViewModel(get(), get(), get(), get(), get(), get(), get(), accountName, showPersonalSpace)
105102
}
103+
viewModel { (configName: String) ->
104+
SettingsAutoUploadViewModel(configName, get(), get(), get(), get(), get(), get(), get(), get(), get())
105+
}
106106
}

opencloudApp/src/main/java/eu/opencloud/android/extensions/FragmentExt.kt

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,30 @@ fun Fragment.showMessageInSnackbar(
5353

5454
fun Fragment.showAlertDialog(
5555
title: String,
56-
message: String,
56+
message: String?,
5757
positiveButtonText: String = getString(android.R.string.ok),
58-
positiveButtonListener: ((DialogInterface, Int) -> Unit)? = null,
58+
positiveButtonAction: (() -> Unit)? = null,
5959
negativeButtonText: String = "",
60-
negativeButtonListener: ((DialogInterface, Int) -> Unit)? = null
60+
negativeButtonAction: (() -> Unit)? = null,
61+
cancelable: Boolean = true,
62+
singleChoiceItems: Array<String>? = null,
63+
checkedItem: Int = -1,
64+
onSingleChoiceItemSelected: ((DialogInterface, Int) -> Unit)? = null
6165
) {
6266
val requiredActivity = activity ?: return
6367
AlertDialog.Builder(requiredActivity)
6468
.setTitle(title)
6569
.setMessage(message)
66-
.setPositiveButton(positiveButtonText, positiveButtonListener)
67-
.setNegativeButton(negativeButtonText, negativeButtonListener)
70+
.setPositiveButton(positiveButtonText) { _, _ -> positiveButtonAction?.invoke() }
71+
.apply {
72+
if (negativeButtonText.isNotEmpty()) {
73+
setNegativeButton(negativeButtonText) { _, _ -> negativeButtonAction?.invoke() }
74+
}
75+
if (singleChoiceItems != null) {
76+
setSingleChoiceItems(singleChoiceItems, checkedItem, onSingleChoiceItemSelected)
77+
}
78+
}
79+
.setCancelable(cancelable)
6880
.show()
6981
.avoidScreenshotsIfNeeded()
7082
}

opencloudApp/src/main/java/eu/opencloud/android/presentation/accounts/ManageAccountsViewModel.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ package eu.opencloud.android.presentation.accounts
2626
import android.accounts.Account
2727
import androidx.lifecycle.ViewModel
2828
import androidx.lifecycle.viewModelScope
29-
import eu.opencloud.android.domain.user.model.UserQuota
3029
import eu.opencloud.android.domain.automaticuploads.model.AutomaticUploadsConfiguration
3130
import eu.opencloud.android.domain.automaticuploads.usecases.GetAutomaticUploadsConfigurationUseCase
31+
import eu.opencloud.android.domain.user.model.UserQuota
3232
import eu.opencloud.android.domain.user.usecases.GetStoredQuotaUseCase
3333
import eu.opencloud.android.domain.user.usecases.GetUserQuotasAsStreamUseCase
3434
import eu.opencloud.android.domain.utils.Event
@@ -83,8 +83,7 @@ class ManageAccountsViewModel(
8383
}
8484

8585
fun hasAutomaticUploadsAttached(accountName: String): Boolean =
86-
accountName == automaticUploadsConfiguration?.pictureUploadsConfiguration?.accountName ||
87-
accountName == automaticUploadsConfiguration?.videoUploadsConfiguration?.accountName
86+
automaticUploadsConfiguration?.folderBackUpConfigurations?.any { it.accountName == accountName } ?: false
8887

8988
fun checkUserLight(accountName: String): Boolean = runBlocking(CoroutinesDispatcherProvider().io) {
9089
val quota = withContext(CoroutinesDispatcherProvider().io) {

opencloudApp/src/main/java/eu/opencloud/android/presentation/settings/SettingsActivity.kt

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,21 @@ import androidx.appcompat.widget.Toolbar
3131
import androidx.constraintlayout.widget.ConstraintLayout
3232
import androidx.core.view.isVisible
3333
import androidx.core.view.updatePadding
34+
import androidx.preference.Preference
35+
import androidx.preference.PreferenceFragmentCompat
3436
import eu.opencloud.android.R
37+
import eu.opencloud.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.pictureUploadsName
38+
import eu.opencloud.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.videoUploadsName
3539
import eu.opencloud.android.presentation.settings.advanced.SettingsAdvancedFragment
36-
import eu.opencloud.android.presentation.settings.automaticuploads.SettingsPictureUploadsFragment
37-
import eu.opencloud.android.presentation.settings.automaticuploads.SettingsVideoUploadsFragment
40+
import eu.opencloud.android.presentation.settings.automaticuploads.SettingsAutoUploadFragment
3841
import eu.opencloud.android.presentation.settings.logging.SettingsLogsFragment
3942
import eu.opencloud.android.presentation.settings.more.SettingsMoreFragment
4043
import eu.opencloud.android.presentation.settings.security.SettingsSecurityFragment
4144
import eu.opencloud.android.ui.activity.FileDisplayActivity
4245
import eu.opencloud.android.ui.activity.enableEdgeToEdgePostSetContentView
4346
import eu.opencloud.android.ui.activity.enableEdgeToEdgePreSetContentView
4447

45-
class SettingsActivity : AppCompatActivity() {
48+
class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
4649

4750
override fun onCreate(savedInstanceState: Bundle?) {
4851
super.onCreate(savedInstanceState)
@@ -75,15 +78,53 @@ class SettingsActivity : AppCompatActivity() {
7578
redirectToSubsection(intent)
7679
}
7780

81+
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference): Boolean {
82+
val args = pref.extras
83+
val fragment = supportFragmentManager.fragmentFactory.instantiate(
84+
classLoader,
85+
pref.fragment!!
86+
).apply {
87+
arguments = args
88+
}
89+
supportFragmentManager.beginTransaction()
90+
.replace(R.id.settings_container, fragment)
91+
.addToBackStack(null)
92+
.commit()
93+
return true
94+
}
95+
7896
private fun updateToolbarTitle() {
7997
val titleId = when (supportFragmentManager.fragments.lastOrNull()) {
80-
is SettingsSecurityFragment -> R.string.prefs_subsection_security
81-
is SettingsLogsFragment -> R.string.prefs_subsection_logging
82-
is SettingsPictureUploadsFragment -> R.string.prefs_subsection_picture_uploads
83-
is SettingsVideoUploadsFragment -> R.string.prefs_subsection_video_uploads
84-
is SettingsAdvancedFragment -> R.string.prefs_subsection_advanced
85-
is SettingsMoreFragment -> R.string.prefs_subsection_more
86-
else -> R.string.actionbar_settings
98+
is SettingsSecurityFragment -> {
99+
R.string.prefs_subsection_security
100+
}
101+
102+
is SettingsLogsFragment -> {
103+
R.string.prefs_subsection_logging
104+
}
105+
106+
is SettingsAutoUploadFragment -> {
107+
val fragment = supportFragmentManager.fragments.lastOrNull()
108+
val configName = fragment?.arguments?.getString(SettingsAutoUploadFragment.ARG_CONFIG_NAME)
109+
if (configName == pictureUploadsName) {
110+
R.string.prefs_subsection_picture_uploads
111+
} else {
112+
R.string.prefs_subsection_video_uploads
113+
}
114+
}
115+
116+
is SettingsAdvancedFragment -> {
117+
R.string.prefs_subsection_advanced
118+
}
119+
120+
is SettingsMoreFragment -> {
121+
R.string.prefs_subsection_more
122+
}
123+
124+
else -> {
125+
R.string.actionbar_settings
126+
}
127+
87128
}
88129
setTitle(titleId)
89130
supportActionBar?.setTitle(titleId)
@@ -107,8 +148,8 @@ class SettingsActivity : AppCompatActivity() {
107148

108149
private fun redirectToSubsection(intent: Intent?) {
109150
val fragment = when (intent?.getStringExtra(KEY_NOTIFICATION_INTENT)) {
110-
NOTIFICATION_INTENT_PICTURES -> SettingsPictureUploadsFragment()
111-
NOTIFICATION_INTENT_VIDEOS -> SettingsVideoUploadsFragment()
151+
NOTIFICATION_INTENT_PICTURES -> SettingsAutoUploadFragment.newInstance(pictureUploadsName)
152+
NOTIFICATION_INTENT_VIDEOS -> SettingsAutoUploadFragment.newInstance(videoUploadsName)
112153
else -> SettingsFragment()
113154
}
114155

0 commit comments

Comments
 (0)