mirror of
https://github.com/ProtonDriveApps/android-drive.git
synced 2026-05-15 09:50:34 +00:00
2.36.0
This commit is contained in:
+4
-2
@@ -19,13 +19,14 @@
|
||||
package me.proton.drive.android.settings.domain.usecase
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.drive.android.settings.domain.entity.LayoutType
|
||||
import javax.inject.Inject
|
||||
|
||||
class ToggleLayoutType @Inject constructor(
|
||||
private val updateLayoutType: UpdateLayoutType
|
||||
) {
|
||||
suspend operator fun invoke(userId: UserId, currentLayoutType: LayoutType) =
|
||||
suspend operator fun invoke(userId: UserId, currentLayoutType: LayoutType) = coRunCatching {
|
||||
updateLayoutType(
|
||||
userId = userId,
|
||||
layoutType = if (currentLayoutType == LayoutType.GRID) {
|
||||
@@ -33,5 +34,6 @@ class ToggleLayoutType @Inject constructor(
|
||||
} else {
|
||||
LayoutType.GRID
|
||||
}
|
||||
)
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -19,6 +19,7 @@
|
||||
package me.proton.drive.android.settings.domain.usecase
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.drive.android.settings.domain.UiSettingsRepository
|
||||
import me.proton.drive.android.settings.domain.entity.LayoutType
|
||||
import javax.inject.Inject
|
||||
@@ -26,6 +27,7 @@ import javax.inject.Inject
|
||||
class UpdateLayoutType @Inject constructor(
|
||||
private val repository: UiSettingsRepository
|
||||
) {
|
||||
suspend operator fun invoke(userId: UserId, layoutType: LayoutType) =
|
||||
suspend operator fun invoke(userId: UserId, layoutType: LayoutType) = coRunCatching {
|
||||
repository.updateLayoutType(userId, layoutType)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -19,6 +19,7 @@
|
||||
package me.proton.drive.android.settings.domain.usecase
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.drive.android.settings.domain.UiSettingsRepository
|
||||
import me.proton.drive.android.settings.domain.entity.ThemeStyle
|
||||
import javax.inject.Inject
|
||||
@@ -26,6 +27,7 @@ import javax.inject.Inject
|
||||
class UpdateThemeStyle @Inject constructor(
|
||||
private val repository: UiSettingsRepository
|
||||
) {
|
||||
suspend operator fun invoke(userId: UserId, themeStyle: ThemeStyle) =
|
||||
suspend operator fun invoke(userId: UserId, themeStyle: ThemeStyle) = coRunCatching {
|
||||
repository.updateThemeStyle(userId, themeStyle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ plugins {
|
||||
id("dagger.hilt.android.plugin")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("me.proton.core.gradle-plugins.environment-config") version "1.3.0"
|
||||
}
|
||||
|
||||
@@ -82,8 +81,7 @@ driveModule(
|
||||
implementation(libs.treessence)
|
||||
|
||||
androidTestImplementation(libs.dagger.hilt.android.testing)
|
||||
kapt(libs.dagger.hilt.android.compiler)
|
||||
kaptAndroidTest(libs.dagger.hilt.android.compiler)
|
||||
add("kspAndroidTest", libs.dagger.hilt.android.compiler)
|
||||
|
||||
androidTestUtil(libs.androidx.test.orchestrator)
|
||||
androidTestUtil(libs.androidx.test.services)
|
||||
|
||||
@@ -48,6 +48,7 @@ import me.proton.android.drive.provider.AppBuildConfigFieldsProvider
|
||||
import me.proton.android.drive.provider.BuildConfigurationProvider
|
||||
import me.proton.android.drive.provider.AppProtonDriveClientProvider
|
||||
import me.proton.android.drive.provider.AppProtonPhotosClientProvider
|
||||
import me.proton.android.drive.provider.AppProtonSdkClientProvider
|
||||
import me.proton.android.drive.repository.BridgeFindDuplicatesRepository
|
||||
import me.proton.android.drive.repository.ClientUidRepositoryImpl
|
||||
import me.proton.android.drive.settings.DebugSettings
|
||||
@@ -75,6 +76,7 @@ import me.proton.core.drive.base.domain.usecase.DriveUrlBuilder
|
||||
import me.proton.core.drive.documentsprovider.domain.usecase.GetDocumentsProviderRoots
|
||||
import me.proton.core.drive.folder.create.domain.provider.OpenFolderActionProvider
|
||||
import me.proton.core.drive.key.domain.handler.PublicKeyEventHandler
|
||||
import me.proton.core.drive.link.domain.provider.ProtonSdkClientProvider
|
||||
import me.proton.core.drive.log.domain.handler.LogEventHandler
|
||||
import me.proton.core.drive.messagequeue.domain.ActionProvider
|
||||
import me.proton.core.drive.notification.data.provider.NotificationBuilderProvider
|
||||
@@ -250,6 +252,10 @@ abstract class ApplicationBindsModule {
|
||||
@Singleton
|
||||
abstract fun bindsAppProtonPhotosClientProvider(impl: AppProtonPhotosClientProvider): ProtonPhotosClientProvider
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindsAppProtonSdkClientProvider(impl: AppProtonSdkClientProvider): ProtonSdkClientProvider
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindsOpenFolderActionProvider(impl: OpenFolderActionProvider): ActionProvider
|
||||
|
||||
@@ -107,7 +107,9 @@ class DownloadInitializer : Initializer<Unit> {
|
||||
downloadErrorHandlers.forEach { handler ->
|
||||
coRunCatching {
|
||||
handler.onError(downloadError)
|
||||
}.getOrNull(LogTag.DOWNLOAD, "Failed to handle download error")
|
||||
}.onFailure { error ->
|
||||
error.log(LogTag.DOWNLOAD, "Failed to handle download error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -28,8 +28,7 @@ import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.provider.AppProtonDriveClientProvider
|
||||
import me.proton.android.drive.provider.AppProtonPhotosClientProvider
|
||||
import me.proton.android.drive.provider.AppProtonSdkClientProvider
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.accountmanager.presentation.observe
|
||||
import me.proton.core.accountmanager.presentation.onAccountRemoved
|
||||
@@ -49,8 +48,7 @@ class ProtonDriveSdkInitializer : Initializer<Unit> {
|
||||
).run {
|
||||
accountManager.observe(appLifecycleProvider.lifecycle, Lifecycle.State.RESUMED)
|
||||
.onAccountRemoved { account ->
|
||||
appProtonDriveClientProvider.remove(account.userId)
|
||||
appProtonPhotosClientProvider.remove(account.userId)
|
||||
appProtonSdkClientProvider.remove(account.userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +64,5 @@ interface SdkInitializerEntryPoint {
|
||||
val configurationProvider: ConfigurationProvider
|
||||
val accountManager: AccountManager
|
||||
val appLifecycleProvider: AppLifecycleProvider
|
||||
val appProtonDriveClientProvider: AppProtonDriveClientProvider
|
||||
val appProtonPhotosClientProvider: AppProtonPhotosClientProvider
|
||||
val appProtonSdkClientProvider: AppProtonSdkClientProvider
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@ class ShortcutInitializer : Initializer<Unit> {
|
||||
}
|
||||
.onAccountRemoved {
|
||||
updateDynamicShortcuts(emptyList())
|
||||
.onFailure { error ->
|
||||
error.log(
|
||||
tag = LogTag.DEFAULT,
|
||||
message = "Failed to clear dynamic shortcuts",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ class TagsMigrationInitializer : Initializer<Unit> {
|
||||
driveLink = getDriveLink(fileId).toResult().getOrThrow(),
|
||||
retryable = true,
|
||||
networkType = NetworkType.UNMETERED,
|
||||
)
|
||||
).getOrThrow()
|
||||
}.onFailure { error ->
|
||||
error.log(
|
||||
PHOTO,
|
||||
|
||||
@@ -29,6 +29,7 @@ import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.presentation.app.AppLifecycleProvider
|
||||
@@ -51,7 +52,9 @@ class WebViewInitializer : Initializer<Unit> {
|
||||
CoreLogger.d(LogTag.WEBVIEW, "Start safe browsing: $isSuccess")
|
||||
}
|
||||
}
|
||||
}.getOrNull(LogTag.WEBVIEW, "startSafeBrowsing failed")
|
||||
}.onFailure { error ->
|
||||
error.log(LogTag.WEBVIEW, "startSafeBrowsing failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-2
@@ -21,6 +21,7 @@ package me.proton.android.drive.notification
|
||||
import androidx.core.app.NotificationCompat
|
||||
import me.proton.android.drive.usecase.notification.BackupNotificationBuilder
|
||||
import me.proton.android.drive.usecase.notification.DownloadNotificationBuilder
|
||||
import me.proton.android.drive.usecase.notification.DownloadFileProgressNotificationBuilder
|
||||
import me.proton.android.drive.usecase.notification.ForcedSignOutNotificationBuilder
|
||||
import me.proton.android.drive.usecase.notification.NoSpaceLeftOnDeviceNotificationBuilder
|
||||
import me.proton.android.drive.usecase.notification.StorageFullNotificationBuilder
|
||||
@@ -35,6 +36,7 @@ class AppNotificationBuilderProvider @Inject constructor(
|
||||
private val storageFullBuilder: StorageFullNotificationBuilder,
|
||||
private val uploadNotificationBuilder: UploadNotificationBuilder,
|
||||
private val downloadNotificationBuilder: DownloadNotificationBuilder,
|
||||
private val downloadFileProgressNotificationBuilder: DownloadFileProgressNotificationBuilder,
|
||||
private val forcedSignOutNotificationBuilder: ForcedSignOutNotificationBuilder,
|
||||
private val noSpaceLeftOnDeviceNotificationBuilder: NoSpaceLeftOnDeviceNotificationBuilder,
|
||||
private val backupNotificationBuilder: BackupNotificationBuilder,
|
||||
@@ -55,10 +57,15 @@ class AppNotificationBuilderProvider @Inject constructor(
|
||||
notificationId = requireIsInstance(notificationId),
|
||||
events = events as List<Event.Upload>,
|
||||
)
|
||||
events.size == 1 && events.first() is Event.Download ->
|
||||
events.isNotEmpty() && events.all { it is Event.Download } ->
|
||||
downloadNotificationBuilder(
|
||||
notificationId = requireIsInstance(notificationId),
|
||||
event = events.first() as Event.Download,
|
||||
events = events as List<Event.Download>,
|
||||
)
|
||||
events.isNotEmpty() && events.all { it is Event.DownloadFileProgress } ->
|
||||
downloadFileProgressNotificationBuilder(
|
||||
notificationId = requireIsInstance(notificationId),
|
||||
events = events as List<Event.DownloadFileProgress>,
|
||||
)
|
||||
events.size == 1 && events.first() is Event.ForcedSignOut ->
|
||||
forcedSignOutNotificationBuilder(
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.provider
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.domain.extension.toResult
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.core.drive.drivelink.domain.usecase.GetVolumeType
|
||||
import me.proton.core.drive.link.domain.entity.LinkId
|
||||
import me.proton.core.drive.link.domain.extension.userId
|
||||
import me.proton.core.drive.link.domain.provider.ProtonSdkClientProvider
|
||||
import me.proton.core.drive.volume.domain.entity.Volume
|
||||
import me.proton.core.drive.volume.domain.entity.VolumeId
|
||||
import me.proton.core.drive.volume.domain.usecase.GetVolume
|
||||
import me.proton.drive.sdk.ProtonSdkClient
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AppProtonSdkClientProvider @Inject constructor(
|
||||
private val getVolumeType: GetVolumeType,
|
||||
private val getVolume: GetVolume,
|
||||
private val driveClientProvider: AppProtonDriveClientProvider,
|
||||
private val photosClientProvider: AppProtonPhotosClientProvider,
|
||||
) : ProtonSdkClientProvider {
|
||||
|
||||
override suspend fun getOrCreate(
|
||||
linkId: LinkId,
|
||||
): Result<ProtonSdkClient> = coRunCatching {
|
||||
getOrCreate(
|
||||
userId = linkId.userId,
|
||||
volumeType = getVolumeType(linkId).getOrThrow(),
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun getOrCreate(
|
||||
userId: UserId,
|
||||
volumeId: VolumeId
|
||||
): Result<ProtonSdkClient> = coRunCatching {
|
||||
getOrCreate(
|
||||
userId = userId,
|
||||
volumeType = getVolume(userId, volumeId).toResult().getOrThrow().type,
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun getOrCreate(
|
||||
userId: UserId,
|
||||
volumeType: Volume.Type?,
|
||||
): Result<ProtonSdkClient> = coRunCatching {
|
||||
when (volumeType) {
|
||||
null -> error("Cannot create sdk client for null volume type")
|
||||
Volume.Type.UNKNOWN -> error("Cannot create sdk client for unknown volume type")
|
||||
Volume.Type.REGULAR -> driveClientProvider.getOrCreate(userId).getOrThrow()
|
||||
Volume.Type.PHOTO -> photosClientProvider.getOrCreate(userId).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(userId: UserId) {
|
||||
driveClientProvider.remove(userId)
|
||||
photosClientProvider.remove(userId)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -47,5 +47,6 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
||||
private const val BASE_ACTION = "proton.android.intent.action"
|
||||
const val ACTION_DELETE = "$BASE_ACTION.DELETE"
|
||||
const val ACTION_CANCEL_ALL = "$BASE_ACTION.CANCEL_ALL"
|
||||
const val ACTION_CANCEL_ALL_DOWNLOADS = "$BASE_ACTION.CANCEL_ALL_DOWNLOADS"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,7 @@ class DebugSettings(
|
||||
override val preferSdkForDownload: Boolean = true
|
||||
override val preferSdkForThumbnail: Boolean = true
|
||||
override val preferSdkForNodeOperation: Boolean = true
|
||||
override val preferSdkForTrash: Boolean = true
|
||||
|
||||
fun reset(coroutineScope: CoroutineScope) {
|
||||
coroutineScope.launch {
|
||||
|
||||
@@ -650,7 +650,7 @@ fun Iterable<Option>.filterPermissions(
|
||||
Option.ShareMultiplePhotos -> permissions.isAdmin
|
||||
Option.TagPhotoFile -> permissions.isAdmin
|
||||
Option.TakeAPhoto -> permissions.canWrite
|
||||
Option.Trash -> permissions.isAdmin
|
||||
Option.Trash -> permissions.canWrite
|
||||
Option.UploadFile -> permissions.canWrite
|
||||
Option.UploadFolder -> permissions.canWrite
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import me.proton.core.compose.theme.ProtonTheme
|
||||
import me.proton.core.drive.base.presentation.component.TopAppBar
|
||||
import me.proton.core.accountmanager.presentation.R as AccountPresentation
|
||||
import me.proton.core.presentation.R as CorePresentation
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
|
||||
|
||||
@Composable
|
||||
@@ -44,6 +45,7 @@ fun AccountSettingsScreen(
|
||||
Column(modifier = modifier.fillMaxSize()) {
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_arrow_back),
|
||||
navigationContentDescription = stringResource(I18N.string.common_back_action),
|
||||
onNavigationIcon = navigateBack,
|
||||
title = stringResource(AccountPresentation.string.account_settings_header),
|
||||
modifier = Modifier.statusBarsPadding()
|
||||
|
||||
@@ -77,6 +77,7 @@ fun AppAccess(
|
||||
Column(modifier = modifier) {
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_arrow_back),
|
||||
navigationContentDescription = stringResource(I18N.string.common_back_action),
|
||||
onNavigationIcon = navigateBack,
|
||||
title = viewState.title,
|
||||
modifier = Modifier.statusBarsPadding()
|
||||
|
||||
@@ -179,6 +179,7 @@ private fun TopAppBar(
|
||||
navigationIcon = if (viewState.navigationIconResId != 0) {
|
||||
painterResource(id = viewState.navigationIconResId)
|
||||
} else null,
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
notificationDotVisible = viewState.notificationDotVisible,
|
||||
onNavigationIcon = viewEvent.onTopAppBarNavigation,
|
||||
title = viewState.title,
|
||||
|
||||
@@ -124,6 +124,7 @@ fun TopAppBar(
|
||||
val focusManager = LocalFocusManager.current
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_arrow_back),
|
||||
navigationContentDescription = stringResource(I18N.string.common_back_action),
|
||||
onNavigationIcon = viewEvent.onBackPressed,
|
||||
title = "",
|
||||
modifier = modifier.statusBarsPadding(),
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -35,6 +36,7 @@ import me.proton.android.drive.ui.viewstate.FileInfoViewState
|
||||
import me.proton.core.compose.theme.ProtonDimens.DefaultSpacing
|
||||
import me.proton.core.drive.base.presentation.component.TopAppBar
|
||||
import me.proton.core.drive.file.info.presentation.FileInfoContent
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
import me.proton.core.presentation.R as CorePresentation
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@@ -65,6 +67,7 @@ fun FileInfo(
|
||||
) {
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_arrow_back),
|
||||
navigationContentDescription = stringResource(I18N.string.common_back_action),
|
||||
onNavigationIcon = navigateBack,
|
||||
title = "",
|
||||
modifier = Modifier.statusBarsPadding()
|
||||
|
||||
@@ -116,6 +116,7 @@ private fun TopAppBar(
|
||||
) {
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_proton_close),
|
||||
navigationContentDescription = stringResource(I18N.string.common_close_action),
|
||||
onNavigationIcon = navigateBack,
|
||||
title = {},
|
||||
modifier = modifier.statusBarsPadding(),
|
||||
|
||||
@@ -90,6 +90,7 @@ private fun LogScreen(
|
||||
TopAppBar(
|
||||
title = title,
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_arrow_back),
|
||||
navigationContentDescription = stringResource(I18N.string.common_back_action),
|
||||
onNavigationIcon = navigateBack,
|
||||
) {
|
||||
ActionButton(
|
||||
|
||||
@@ -111,6 +111,7 @@ fun MoveToFolder(
|
||||
Column {
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = viewState.navigationIconResId),
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
onNavigationIcon = viewEvent.onTopAppBarNavigation,
|
||||
title = { modifier ->
|
||||
Title(
|
||||
|
||||
@@ -195,6 +195,7 @@ fun AlbumsTab(
|
||||
navigationIcon = if (viewState.navigationIconResId != 0) {
|
||||
painterResource(id = viewState.navigationIconResId)
|
||||
} else null,
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
onNavigationIcon = viewEvent.onTopAppBarNavigation,
|
||||
title = defaultTitle,
|
||||
notificationDotVisible = false,
|
||||
@@ -269,7 +270,7 @@ fun PhotosTab(
|
||||
|
||||
viewModel.HandleHomeEffect(homeScaffoldState)
|
||||
|
||||
val photos = rememberFlowWithLifecycle(flow = viewModel.driveLinks)
|
||||
val photos = rememberFlowWithLifecycle(flow = viewModel.photoItems)
|
||||
val listEffect = rememberFlowWithLifecycle(flow = viewModel.listEffect)
|
||||
|
||||
LaunchedEffect(viewModel, LocalContext.current) {
|
||||
@@ -339,6 +340,7 @@ private fun PhotosTab(
|
||||
navigationIcon = if (viewState.navigationIconResId != 0) {
|
||||
painterResource(id = viewState.navigationIconResId)
|
||||
} else null,
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
onNavigationIcon = viewEvent.onTopAppBarNavigation,
|
||||
title = if (viewState.inMultiselect) {
|
||||
{ Title(viewState.title, false) }
|
||||
|
||||
@@ -122,6 +122,7 @@ fun PhotosBackup(
|
||||
Column(modifier = modifier) {
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_arrow_back),
|
||||
navigationContentDescription = stringResource(I18N.string.common_back_action),
|
||||
onNavigationIcon = navigateBack,
|
||||
title = viewState.title,
|
||||
)
|
||||
|
||||
@@ -98,7 +98,7 @@ fun PhotosScreen(
|
||||
lifecycle = lifecycle,
|
||||
)
|
||||
}
|
||||
val photos = rememberFlowWithLifecycle(flow = viewModel.driveLinks)
|
||||
val photos = rememberFlowWithLifecycle(flow = viewModel.photoItems)
|
||||
val listEffect = rememberFlowWithLifecycle(flow = viewModel.listEffect)
|
||||
|
||||
LaunchedEffect(viewModel, LocalContext.current) {
|
||||
@@ -203,6 +203,7 @@ fun TopAppBar(
|
||||
navigationIcon = if (viewState.navigationIconResId != 0) {
|
||||
painterResource(id = viewState.navigationIconResId)
|
||||
} else null,
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
notificationDotVisible = viewState.notificationDotVisible,
|
||||
onNavigationIcon = viewEvent.onTopAppBarNavigation,
|
||||
title = viewState.title,
|
||||
|
||||
@@ -184,6 +184,7 @@ fun TopAppBar(
|
||||
) {
|
||||
BaseTopAppBar(
|
||||
navigationIcon = painterResource(CorePresentation.drawable.ic_proton_close),
|
||||
navigationContentDescription = stringResource(I18N.string.common_close_action),
|
||||
onNavigationIcon = onNavigationIcon,
|
||||
title = title,
|
||||
modifier = modifier.statusBarsPadding(),
|
||||
|
||||
@@ -45,6 +45,7 @@ import me.proton.core.compose.theme.ProtonTheme
|
||||
import me.proton.core.compose.theme.headlineSmallNorm
|
||||
import me.proton.core.compose.theme.interactionNorm
|
||||
import me.proton.core.drive.base.presentation.component.TopAppBar
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
import me.proton.core.presentation.R as CorePresentation
|
||||
|
||||
@Composable
|
||||
@@ -101,6 +102,7 @@ fun TopAppBar(
|
||||
val focusManager = LocalFocusManager.current
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = CorePresentation.drawable.ic_proton_cross),
|
||||
navigationContentDescription = stringResource(I18N.string.common_close_action),
|
||||
onNavigationIcon = viewEvent.onBackPressed,
|
||||
title = stringResource(id = viewState.titleResId),
|
||||
modifier = modifier.statusBarsPadding(),
|
||||
|
||||
@@ -73,6 +73,7 @@ fun SharedTabsScreen(
|
||||
TopAppBar(
|
||||
titleResId = viewState.titleResId,
|
||||
navigationIconResId = viewState.navigationIconResId,
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
onTopAppBarNavigation = viewEvent.onTopAppBarNavigation,
|
||||
)
|
||||
}
|
||||
@@ -147,11 +148,13 @@ fun SharedTabs(
|
||||
private fun TopAppBar(
|
||||
@StringRes titleResId: Int,
|
||||
@DrawableRes navigationIconResId: Int,
|
||||
navigationContentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
onTopAppBarNavigation: () -> Unit,
|
||||
) {
|
||||
BaseTopAppBar(
|
||||
navigationIcon = painterResource(id = navigationIconResId),
|
||||
navigationContentDescription = navigationContentDescription,
|
||||
onNavigationIcon = onTopAppBarNavigation,
|
||||
title = stringResource(id = titleResId),
|
||||
modifier = modifier,
|
||||
|
||||
@@ -306,6 +306,7 @@ private fun TopAppBar(
|
||||
if (isLandscape) {
|
||||
BaseTopAppBar(
|
||||
navigationIcon = painterResource((closeAction as Action.Icon).iconResId),
|
||||
navigationContentDescription = stringResource((closeAction as Action.Icon).contentDescriptionResId),
|
||||
onNavigationIcon = closeAction.onAction,
|
||||
title = "",
|
||||
backgroundColor = backgroundColor,
|
||||
|
||||
@@ -114,6 +114,7 @@ private fun TopAppBar(
|
||||
navigationIcon = if (viewState.navigationIconResId != 0) {
|
||||
painterResource(id = viewState.navigationIconResId)
|
||||
} else null,
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
onNavigationIcon = viewEvent.onTopAppBarNavigation,
|
||||
title = viewState.title ?: "",
|
||||
isTitleEncrypted = viewState.isTitleEncrypted,
|
||||
|
||||
@@ -147,6 +147,7 @@ fun UploadTo(
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
TopAppBar(
|
||||
navigationIcon = painterResource(id = viewState.navigationIconResId),
|
||||
navigationContentDescription = viewState.navigationContentDescription,
|
||||
onNavigationIcon = viewEvent.onTopAppBarNavigation,
|
||||
title = viewState.title,
|
||||
isTitleEncrypted = viewState.isTitleEncrypted,
|
||||
|
||||
+6
-2
@@ -23,11 +23,13 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.usecase.AddPhotosToAlbum
|
||||
import me.proton.android.drive.photos.domain.usecase.AddToAlbumInfo
|
||||
import me.proton.android.drive.ui.viewevent.AddToAlbumsOptionsViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.AddToAlbumsOptionsViewState
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.component.RunAction
|
||||
@@ -47,8 +49,8 @@ import me.proton.core.drive.photo.domain.entity.PhotoListing
|
||||
import me.proton.core.drive.share.crypto.domain.usecase.GetPhotoShare
|
||||
import me.proton.core.drive.share.domain.entity.Share
|
||||
import javax.inject.Inject
|
||||
import me.proton.core.presentation.R as CorePresentation
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
import me.proton.core.presentation.R as CorePresentation
|
||||
|
||||
@HiltViewModel
|
||||
class AddToAlbumsOptionsViewModel @Inject constructor(
|
||||
@@ -123,7 +125,9 @@ class AddToAlbumsOptionsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
override suspend fun onSuccessfullyAdded(photoListings: List<PhotoListing.Volume>) {
|
||||
deselectLinks(selectionId)
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAlbumDetails(album: DriveLink.Album?): String? = album.details()
|
||||
|
||||
@@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.options.Option
|
||||
import me.proton.android.drive.ui.options.filter
|
||||
import me.proton.android.drive.ui.options.filterPermissions
|
||||
@@ -41,6 +42,7 @@ import me.proton.android.drive.ui.options.filterShareMember
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.domain.entity.Permissions
|
||||
import me.proton.core.drive.base.domain.extension.mapWithPrevious
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.component.RunAction
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
@@ -115,7 +117,9 @@ class AlbumOptionsViewModel @Inject constructor(
|
||||
when (option) {
|
||||
is Option.OfflineToggle -> option.build(runAction) { driveLink ->
|
||||
viewModelScope.launch {
|
||||
toggleOffline(driveLink)
|
||||
toggleOffline(driveLink).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to toggle offline for ${driveLink.id.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
is Option.ShareViaInvitations -> option.build(runAction, navigateToShareViaInvitations)
|
||||
|
||||
@@ -48,6 +48,7 @@ import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.getDefaultMessage
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.extension.thumbnailVO
|
||||
import me.proton.android.drive.photos.domain.usecase.AddPhotosToStream
|
||||
import me.proton.android.drive.photos.domain.usecase.AddToAlbumInfo
|
||||
@@ -63,13 +64,12 @@ import me.proton.android.drive.ui.common.onClick
|
||||
import me.proton.android.drive.usecase.OnFilesDriveLinkError
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.domain.arch.onSuccess
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.domain.entity.Permissions
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
import me.proton.core.drive.base.domain.extension.isViewerOrEditorOnly
|
||||
import me.proton.core.drive.base.domain.extension.mapWithPrevious
|
||||
import me.proton.core.drive.base.domain.extension.onFailure
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.log.logId
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
@@ -203,7 +203,7 @@ class AlbumViewModel @Inject constructor(
|
||||
contentState = listContentState,
|
||||
shareType = Share.Type.PHOTO,
|
||||
)
|
||||
error.log(VIEW_MODEL)
|
||||
error.logResult(VIEW_MODEL)
|
||||
}
|
||||
return@mapWithPrevious null
|
||||
}
|
||||
@@ -260,6 +260,11 @@ class AlbumViewModel @Inject constructor(
|
||||
} else {
|
||||
CorePresentation.drawable.ic_proton_close
|
||||
},
|
||||
navigationContentDescription = if (selected.isEmpty() || inPickerMode) {
|
||||
appContext.getString(I18N.string.common_back_action)
|
||||
} else {
|
||||
appContext.getString(I18N.string.common_close_action)
|
||||
},
|
||||
title = takeIf { selected.isNotEmpty() && !inPickerMode }
|
||||
?.let {
|
||||
appContext.quantityString(
|
||||
@@ -412,7 +417,7 @@ class AlbumViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
saveAllLoading.value = true
|
||||
addPhotosToStream(albumId).onFailure { error ->
|
||||
error.log(LogTag.ALBUM, "Cannot copy photo to stream: ${albumId.id}")
|
||||
error.log(VIEW_MODEL, "Cannot copy photo to stream: ${albumId.id}")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
@@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.usecase.GetPhotosDriveLink
|
||||
import me.proton.android.drive.photos.presentation.state.AlbumsItem
|
||||
import me.proton.android.drive.photos.presentation.viewevent.AlbumsViewEvent
|
||||
@@ -56,7 +57,7 @@ import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.domain.arch.onSuccess
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.isRetryable
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
import me.proton.core.drive.base.domain.extension.mapWithPrevious
|
||||
import me.proton.core.drive.base.domain.extension.onFailure
|
||||
@@ -137,6 +138,7 @@ class AlbumsViewModel @Inject constructor(
|
||||
isRefreshEnabled = listContentState.value != ListContentState.Loading,
|
||||
placeholderImageResId = emptyStateImageResId,
|
||||
navigationIconResId = CorePresentation.drawable.ic_proton_hamburger,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_open_side_menu_action),
|
||||
filters = listOf(
|
||||
AlbumsFilter(
|
||||
AlbumListing.Filter.ALL,
|
||||
@@ -205,7 +207,7 @@ class AlbumsViewModel @Inject constructor(
|
||||
contentState = listContentState,
|
||||
shareType = Share.Type.PHOTO,
|
||||
)
|
||||
error.log(VIEW_MODEL, "Cannot get drive link")
|
||||
error.logResult(VIEW_MODEL, "Cannot get drive link")
|
||||
}
|
||||
return@mapWithPrevious null
|
||||
}
|
||||
@@ -347,7 +349,7 @@ class AlbumsViewModel @Inject constructor(
|
||||
)
|
||||
.onFailure { error ->
|
||||
isRefreshing.value = false
|
||||
error.log(VIEW_MODEL)
|
||||
error.log(VIEW_MODEL, "Cannot get all album listings")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+5
-1
@@ -26,12 +26,14 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.lock.domain.extension.autoLockDefaultDuration
|
||||
import me.proton.android.drive.lock.domain.usecase.GetAutoLockDuration
|
||||
import me.proton.android.drive.lock.domain.usecase.UpdateAutoLockDuration
|
||||
import me.proton.android.drive.ui.viewevent.AutoLockDurationsViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.AutoLockDurationsViewState
|
||||
import me.proton.core.compose.component.bottomsheet.RunAction
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration
|
||||
@@ -62,7 +64,9 @@ class AutoLockDurationsViewModel @Inject constructor(
|
||||
override val onDuration: (Duration) -> Unit = { duration ->
|
||||
runAction {
|
||||
viewModelScope.launch {
|
||||
updateAutoLockDuration(duration)
|
||||
updateAutoLockDuration(duration).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Cannot update auto lock duration")
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.android.drive.ui.viewevent.BackupIssuesViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.BackupIssuesViewState
|
||||
import me.proton.core.drive.backup.domain.usecase.GetAllFailedFiles
|
||||
import me.proton.core.drive.backup.domain.usecase.RetryBackup
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.BACKUP
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -70,7 +70,7 @@ class BackupIssuesViewModel @Inject constructor(
|
||||
backupFile.uriString.toUri()
|
||||
})
|
||||
}.catch { error ->
|
||||
error.log(BACKUP, "Cannot get all failed files")
|
||||
error.log(VIEW_MODEL, "Cannot get all failed files")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
@@ -95,7 +95,7 @@ class BackupIssuesViewModel @Inject constructor(
|
||||
retryBackup(folderId).onSuccess {
|
||||
onSuccess()
|
||||
}.onFailure { error ->
|
||||
error.log(BACKUP, "Cannot retry on backup")
|
||||
error.log(VIEW_MODEL, "Cannot retry on backup")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
@@ -39,12 +39,13 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.common.onClick
|
||||
import me.proton.android.drive.ui.effect.HomeEffect
|
||||
import me.proton.android.drive.ui.effect.HomeTabViewModel
|
||||
import me.proton.core.drive.base.domain.entity.TimestampMs
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.drive.base.domain.log.LogTag.SHARING
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.presentation.effect.ListEffect
|
||||
import me.proton.core.drive.base.presentation.state.ListContentAppendingState
|
||||
@@ -193,9 +194,10 @@ abstract class CommonSharedViewModel(
|
||||
private fun onRefresh() {
|
||||
viewModelScope.launch {
|
||||
_listEffect.emit(ListEffect.REFRESH)
|
||||
val linkIds = getAllIds().getOrNull(SHARING, "Cannot get ids")
|
||||
val linkIds = getAllIds().getOrNull(VIEW_MODEL, "Cannot get ids")
|
||||
.orEmpty().map { it.linkId }.toSet()
|
||||
sharedDriveLinks.refresh(linkIds).getOrNull(SHARING, "Cannot refresh drive links")
|
||||
sharedDriveLinks.refresh(linkIds)
|
||||
.onFailure { error -> error.log(VIEW_MODEL, "Cannot refresh drive links") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.effect.HomeEffect
|
||||
import me.proton.android.drive.ui.effect.HomeTabViewModel
|
||||
import me.proton.android.drive.ui.viewevent.ComputersViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.ComputersViewState
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.logDefaultMessage
|
||||
import me.proton.core.drive.base.domain.entity.CryptoProperty
|
||||
import me.proton.core.drive.base.domain.entity.TimestampMs
|
||||
@@ -48,19 +48,20 @@ import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.common.getThemeDrawableId
|
||||
import me.proton.core.drive.base.presentation.state.ListContentState
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.device.domain.entity.Device
|
||||
import me.proton.core.drive.device.domain.entity.DeviceId
|
||||
import me.proton.core.drive.device.domain.extension.name
|
||||
import me.proton.core.drive.device.domain.usecase.RefreshDevices
|
||||
import me.proton.core.drive.drivelink.device.domain.usecase.GetDecryptedDevicesSortedByName
|
||||
import me.proton.core.drive.base.presentation.state.ListContentState
|
||||
import me.proton.core.drive.files.domain.usecase.ToFirstItemMetricsNotifier
|
||||
import me.proton.core.drive.link.domain.entity.FolderId
|
||||
import me.proton.core.drive.messagequeue.domain.entity.BroadcastMessage
|
||||
import me.proton.core.drive.observability.domain.metrics.common.mobile.performance.PageType
|
||||
import me.proton.core.plan.presentation.compose.usecase.ShouldUpgradeStorage
|
||||
import javax.inject.Inject
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.drivelink.device.presentation.R as DriveLinkDevicePresentation
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
|
||||
@@ -107,6 +108,7 @@ class ComputersViewModel @Inject constructor(
|
||||
val initialViewState = ComputersViewState(
|
||||
title = appContext.getString(I18N.string.computers_title),
|
||||
navigationIconResId = me.proton.core.presentation.R.drawable.ic_proton_hamburger,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_open_side_menu_action),
|
||||
notificationDotVisible = false,
|
||||
listContentState = listContentState.value,
|
||||
isRefreshEnabled = listContentState.value != ListContentState.Loading
|
||||
@@ -136,7 +138,7 @@ class ComputersViewModel @Inject constructor(
|
||||
when (result) {
|
||||
is DataResult.Processing -> listContentState.value = ListContentState.Loading
|
||||
is DataResult.Error -> {
|
||||
result.log(VIEW_MODEL)
|
||||
result.logResult(VIEW_MODEL)
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = result.logDefaultMessage(
|
||||
|
||||
+5
-1
@@ -27,11 +27,13 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.android.drive.ui.viewevent.ConfirmDeletionViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.ConfirmDeletionViewState
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.drivelink.crypto.domain.usecase.GetDecryptedDriveLink
|
||||
@@ -66,7 +68,9 @@ class ConfirmDeletionDialogViewModel @Inject constructor(
|
||||
fun viewEvent(onDismiss: () -> Unit) = object : ConfirmDeletionViewEvent {
|
||||
override val onConfirm = {
|
||||
viewModelScope.launch {
|
||||
deleteFromTrash(userId, linkId)
|
||||
deleteFromTrash(userId, linkId).onFailure{ error ->
|
||||
error.log(VIEW_MODEL, "Failed to delete from trash ${linkId.id}")
|
||||
}
|
||||
onDismiss()
|
||||
}
|
||||
Unit
|
||||
|
||||
+3
-3
@@ -24,9 +24,9 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.viewevent.ConfirmEmptyTrashViewEvent
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.TRASH
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.trash.domain.usecase.EmptyTrash
|
||||
@@ -46,7 +46,7 @@ class ConfirmEmptyTrashDialogViewModel @Inject constructor(
|
||||
override val onConfirm = {
|
||||
viewModelScope.launch {
|
||||
emptyTrash(userId, volumeId).onFailure { error ->
|
||||
error.log(TRASH, "Cannot empty trash volumeId: ${volumeId.id}")
|
||||
error.log(VIEW_MODEL, "Cannot empty trash volumeId: ${volumeId.id}")
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
+9
-8
@@ -33,15 +33,15 @@ import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.getDefaultMessage
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.usecase.AddPhotosToStream
|
||||
import me.proton.android.drive.photos.presentation.extension.processAddToStream
|
||||
import me.proton.android.drive.photos.presentation.viewevent.ConfirmLeaveAlbumDialogViewEvent
|
||||
import me.proton.android.drive.photos.presentation.viewstate.ConfirmLeaveAlbumDialogViewState
|
||||
import me.proton.android.drive.usecase.LeaveShare
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
@@ -107,12 +107,13 @@ class ConfirmLeaveAlbumDialogViewModel @Inject constructor(
|
||||
|
||||
private fun leaveAlbumWithoutSaving(dismiss: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
album.value?.let {
|
||||
album.value?.let { album ->
|
||||
isWithoutSavingOperationInProgress.value = true
|
||||
leaveShare(it)
|
||||
.onSuccess {
|
||||
dismiss()
|
||||
}
|
||||
leaveShare(album).onSuccess {
|
||||
dismiss()
|
||||
}.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to leave share for ${album.id.id}")
|
||||
}
|
||||
isWithoutSavingOperationInProgress.value = false
|
||||
}
|
||||
}
|
||||
@@ -123,7 +124,7 @@ class ConfirmLeaveAlbumDialogViewModel @Inject constructor(
|
||||
album.value?.let {
|
||||
isSavingOperationInProgress.value = true
|
||||
addPhotosToStream(albumId).onFailure { error ->
|
||||
error.log(LogTag.ALBUM, "Cannot copy photo to stream: ${albumId.id}")
|
||||
error.log(VIEW_MODEL, "Cannot copy photo to stream: ${albumId.id}")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+3
-3
@@ -25,12 +25,12 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.android.drive.ui.viewevent.ConfirmSkipIssuesViewEvent
|
||||
import me.proton.core.drive.backup.domain.usecase.DeleteAllFailedFiles
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.BACKUP
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -65,7 +65,7 @@ class ConfirmSkipIssuesDialogViewModel @Inject constructor(
|
||||
deleteAllFailedFiles(folderId).onSuccess {
|
||||
onSuccess()
|
||||
}.onFailure { error ->
|
||||
error.log(BACKUP, "Cannot delete failed files")
|
||||
error.log(VIEW_MODEL, "Cannot delete failed files")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+1
-3
@@ -26,13 +26,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.viewevent.ConfirmStopSharingViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.ConfirmStopSharingViewState
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
|
||||
+2
-2
@@ -28,10 +28,10 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.android.drive.ui.viewevent.ConfirmStopSharingViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.ConfirmStopSharingViewState
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -96,7 +96,7 @@ class ConfirmStopLinkSharingDialogViewModel @Inject constructor(
|
||||
confirm()
|
||||
}.onFailure { error ->
|
||||
errorMessage.emit(context.getString(I18N.string.description_files_stop_sharing_action_error))
|
||||
error.log(VIEW_MODEL)
|
||||
error.log(VIEW_MODEL, "Failed to delete share url for ${linkId.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,6 +27,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.android.drive.ui.viewevent.ConfirmStopSyncFolderViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.ConfirmStopSyncFolderViewState
|
||||
@@ -34,8 +35,7 @@ import me.proton.core.drive.backup.domain.entity.BackupFolder
|
||||
import me.proton.core.drive.backup.domain.usecase.DisableBackupForFolder
|
||||
import me.proton.core.drive.backup.domain.usecase.GetAllBuckets
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.BACKUP
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -79,7 +79,7 @@ class ConfirmStopSyncFolderDialogViewModel @Inject constructor(
|
||||
disableBackupForFolder(BackupFolder(id, folderId)).onSuccess {
|
||||
onSuccess()
|
||||
}.onFailure { error ->
|
||||
error.log(BACKUP, "Cannot stop sync for folder: $id")
|
||||
error.log(VIEW_MODEL, "Cannot stop sync for folder: $id")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+27
-20
@@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.getDefaultMessage
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.usecase.AddPhotosToStream
|
||||
import me.proton.android.drive.photos.domain.usecase.RemovePhotosFromAlbum
|
||||
import me.proton.android.drive.photos.presentation.extension.processAddToStream
|
||||
@@ -49,10 +50,8 @@ import me.proton.android.drive.usecase.LeaveShare
|
||||
import me.proton.android.drive.usecase.NotifyActivityNotFound
|
||||
import me.proton.android.drive.usecase.OpenProtonDocumentInBrowser
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.entity.Permissions
|
||||
import me.proton.core.drive.base.domain.extension.mapWithPrevious
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
@@ -189,7 +188,9 @@ class FileOrFolderOptionsViewModel @Inject constructor(
|
||||
}
|
||||
is Option.OfflineToggle -> option.build(runAction) { driveLink ->
|
||||
viewModelScope.launch {
|
||||
toggleOffline(driveLink)
|
||||
toggleOffline(driveLink).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to toggle offline for ${driveLink.id.id}")
|
||||
}
|
||||
deselectLinks()
|
||||
}
|
||||
}
|
||||
@@ -224,13 +225,17 @@ class FileOrFolderOptionsViewModel @Inject constructor(
|
||||
}
|
||||
is Option.RemoveMe -> option.build(runAction) { driveLink ->
|
||||
viewModelScope.launch {
|
||||
leaveShare(driveLink)
|
||||
leaveShare(driveLink).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to leave share for ${driveLink.id.id}")
|
||||
}
|
||||
deselectLinks()
|
||||
}
|
||||
}
|
||||
is Option.OpenInBrowser -> option.build(runAction) { driveLink ->
|
||||
viewModelScope.launch {
|
||||
openProtonDocumentInBrowser(driveLink)
|
||||
openProtonDocumentInBrowser(driveLink).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to open proton document in browser")
|
||||
}
|
||||
deselectLinks()
|
||||
}
|
||||
}
|
||||
@@ -259,19 +264,19 @@ class FileOrFolderOptionsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
is Option.AddToAlbums -> option.build(runAction) { driveLink ->
|
||||
if (selectionId != null) {
|
||||
navigateToAddToAlbumsOptions(selectionId)
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
selectLinks(listOf(driveLink.id))
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to select links")
|
||||
}
|
||||
.onSuccess { selectionId ->
|
||||
navigateToAddToAlbumsOptions(selectionId)
|
||||
}
|
||||
}
|
||||
if (selectionId != null) {
|
||||
navigateToAddToAlbumsOptions(selectionId)
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
selectLinks(listOf(driveLink.id))
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to select links")
|
||||
}
|
||||
.onSuccess { selectionId ->
|
||||
navigateToAddToAlbumsOptions(selectionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw IllegalStateException(
|
||||
"Option ${option.javaClass.simpleName} is not found. Did you forget to add it?"
|
||||
@@ -291,7 +296,9 @@ class FileOrFolderOptionsViewModel @Inject constructor(
|
||||
private fun deselectLinks() {
|
||||
viewModelScope.launch {
|
||||
if (selectionId != null) {
|
||||
deselectLinks(selectionId)
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,7 +340,7 @@ class FileOrFolderOptionsViewModel @Inject constructor(
|
||||
albumId = requireNotNull(albumId),
|
||||
newCoverFileId = driveLink.id
|
||||
).onFailure { error ->
|
||||
error.log(LogTag.ALBUM, "Cannot update album cover: ${driveLink.id.id}")
|
||||
error.log(VIEW_MODEL, "Cannot update album cover: ${driveLink.id.id}")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
@@ -359,7 +366,7 @@ class FileOrFolderOptionsViewModel @Inject constructor(
|
||||
photoIds = listOf(fileId),
|
||||
albumId = requireNotNull(albumId) { "album id is required to save shared photo"},
|
||||
).onFailure { error ->
|
||||
error.log(LogTag.ALBUM, "Cannot copy photo to stream: ${fileId.id}")
|
||||
error.log(VIEW_MODEL, "Cannot copy photo to stream: ${fileId.id}")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
@@ -48,6 +48,7 @@ import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.document.scanner.domain.usecase.IsScannerAvailable
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.common.onClick
|
||||
import me.proton.android.drive.ui.effect.HomeEffect
|
||||
import me.proton.android.drive.ui.effect.HomeTabViewModel
|
||||
@@ -60,7 +61,7 @@ import me.proton.android.drive.usecase.OpenProtonDocumentInBrowser
|
||||
import me.proton.core.domain.arch.onSuccess
|
||||
import me.proton.core.drive.base.data.datastore.GetUserDataStore
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.domain.entity.Percentage
|
||||
import me.proton.core.drive.base.domain.entity.Permissions
|
||||
import me.proton.core.drive.base.domain.entity.TimestampMs
|
||||
@@ -104,7 +105,6 @@ import me.proton.core.drive.observability.domain.metrics.common.mobile.performan
|
||||
import me.proton.core.drive.share.domain.entity.ShareId
|
||||
import me.proton.core.drive.sorting.domain.entity.Sorting
|
||||
import me.proton.core.drive.sorting.domain.usecase.GetSorting
|
||||
import me.proton.core.drive.upload.data.extension.logTag
|
||||
import me.proton.core.drive.upload.domain.usecase.CancelUploadFile
|
||||
import me.proton.core.drive.upload.domain.usecase.GetUploadProgress
|
||||
import me.proton.core.drive.user.domain.extension.isFree
|
||||
@@ -179,7 +179,7 @@ class FilesViewModel @Inject constructor(
|
||||
}
|
||||
.onFailure { error ->
|
||||
onFilesDriveLinkError(userId, previous, error, listContentState)
|
||||
error.log(VIEW_MODEL, "Cannot get drive link for ${folderId?.id}")
|
||||
error.logResult(VIEW_MODEL, "Cannot get drive link for ${folderId?.id}")
|
||||
toFirstItemMetricsNotifier.reset()
|
||||
}
|
||||
return@mapWithPrevious null
|
||||
@@ -245,6 +245,11 @@ class FilesViewModel @Inject constructor(
|
||||
} else {
|
||||
CorePresentation.drawable.ic_arrow_back
|
||||
},
|
||||
navigationContentDescription = if (isRootFolder && selected.value.isEmpty()) {
|
||||
appContext.getString(I18N.string.common_open_side_menu_action)
|
||||
} else {
|
||||
appContext.getString(I18N.string.common_back_action)
|
||||
},
|
||||
drawerGesturesEnabled = isRootFolder,
|
||||
listContentState = listContentState.value,
|
||||
listContentAppendingState = listContentAppendingState.value,
|
||||
@@ -311,6 +316,13 @@ class FilesViewModel @Inject constructor(
|
||||
} else {
|
||||
CorePresentation.drawable.ic_arrow_back
|
||||
},
|
||||
navigationContentDescription = if (showHamburgerMenuIcon) {
|
||||
appContext.getString(I18N.string.common_open_side_menu_action)
|
||||
} else if (selected.isNotEmpty()) {
|
||||
appContext.getString(I18N.string.common_close_action)
|
||||
} else {
|
||||
appContext.getString(I18N.string.common_back_action)
|
||||
},
|
||||
sorting = sorting,
|
||||
listContentState = listContentState,
|
||||
listContentAppendingState = appendingState,
|
||||
@@ -510,7 +522,7 @@ class FilesViewModel @Inject constructor(
|
||||
private fun onCancelUpload(uploadFileLink: UploadFileLink) {
|
||||
viewModelScope.launch {
|
||||
cancelUploadFile(uploadFileLink).onFailure { error ->
|
||||
error.log(uploadFileLink.logTag(), "Cannot cancel upload")
|
||||
error.log(VIEW_MODEL, "Cannot cancel upload")
|
||||
_homeEffect.emit(
|
||||
HomeEffect.ShowSnackbar(
|
||||
error.getDefaultMessage(
|
||||
|
||||
-2
@@ -38,7 +38,6 @@ import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.asHumanReadableString
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.feature.flag.domain.usecase.GetFeatureFlagFlow
|
||||
import me.proton.core.drive.messagequeue.domain.entity.BroadcastMessage
|
||||
import javax.inject.Inject
|
||||
import me.proton.core.drive.base.presentation.R as BasePresentation
|
||||
@@ -50,7 +49,6 @@ class GetMoreFreeStorageViewModel @Inject constructor(
|
||||
@ApplicationContext private val appContext: Context,
|
||||
configurationProvider: ConfigurationProvider,
|
||||
private val broadcastMessages: BroadcastMessages,
|
||||
getFeatureFlag: GetFeatureFlagFlow,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), UserViewModel by UserViewModel(savedStateHandle) {
|
||||
private val uploadAFile = GetMoreFreeStorageViewState.Action(
|
||||
|
||||
@@ -80,6 +80,7 @@ abstract class HostFilesViewModel(
|
||||
titleResId = I18N.string.title_my_files,
|
||||
sorting = Sorting.DEFAULT,
|
||||
navigationIconResId = 0,
|
||||
navigationContentDescription = null,
|
||||
drawerGesturesEnabled = false,
|
||||
listContentState = listContentState.value,
|
||||
listContentAppendingState = listContentAppendingState.value,
|
||||
|
||||
@@ -36,13 +36,15 @@ import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.handler.FolderCreatedHandler
|
||||
import me.proton.android.drive.ui.handler.FolderCreatedHandlerDelegate
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.android.drive.ui.viewevent.MoveToFolderViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.MoveFileViewState
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.android.drive.ui.handler.FolderCreatedHandler
|
||||
import me.proton.android.drive.ui.handler.FolderCreatedHandlerDelegate
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.presentation.common.Action
|
||||
import me.proton.core.drive.drivelink.crypto.domain.usecase.DecryptDriveLinks
|
||||
@@ -163,6 +165,11 @@ class MoveToFolderViewModel @Inject constructor(
|
||||
} else {
|
||||
CorePresentation.drawable.ic_arrow_back
|
||||
},
|
||||
navigationContentDescription = if (parentLink == null || isRoot) {
|
||||
appContext.getString(I18N.string.common_close_action)
|
||||
} else {
|
||||
appContext.getString(I18N.string.common_back_action)
|
||||
},
|
||||
driveLinks = decryptDriveLinks(driveLinksToMove)
|
||||
.map { driveLink -> if (driveLink.isNameEncrypted) "" else driveLink.name },
|
||||
)
|
||||
@@ -203,8 +210,19 @@ class MoveToFolderViewModel @Inject constructor(
|
||||
} else if (folder.id == parentId) {
|
||||
CoreLogger.w(LogTag.MOVE, "folder same as parent, move aborted")
|
||||
} else {
|
||||
moveFile(userId, driveLinksToMove.value.map { driveLink -> driveLink.id }, folder.id)
|
||||
selectionId?.let { deselectLinks(selectionId) }
|
||||
val linkIds = driveLinksToMove.value.map { driveLink -> driveLink.id }
|
||||
moveFile(
|
||||
userId = userId,
|
||||
linkIds = linkIds,
|
||||
parentId = folder.id,
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to move files for ${linkIds.map { it.id }}")
|
||||
}
|
||||
selectionId?.let {
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
+22
-6
@@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.getDefaultMessage
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.usecase.RemovePhotosFromAlbum
|
||||
import me.proton.android.drive.photos.presentation.extension.processRemove
|
||||
import me.proton.android.drive.ui.options.Option
|
||||
@@ -36,7 +37,6 @@ import me.proton.android.drive.ui.options.filterAll
|
||||
import me.proton.android.drive.ui.options.filterPermissions
|
||||
import me.proton.android.drive.ui.options.filterPhotoTag
|
||||
import me.proton.android.drive.ui.options.filterShare
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
@@ -105,8 +105,15 @@ class MultipleFileOrFolderOptionsViewModel @Inject constructor(
|
||||
runAction = runAction,
|
||||
moveToTrash = {
|
||||
viewModelScope.launch {
|
||||
sendToTrash(userId, driveLinks)
|
||||
deselectLinks(selectionId)
|
||||
sendToTrash(userId, driveLinks).onFailure { error ->
|
||||
error.log(
|
||||
VIEW_MODEL,
|
||||
"Failed to send to trash for ${driveLinks.map { it.id.id }}"
|
||||
)
|
||||
}
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -121,7 +128,9 @@ class MultipleFileOrFolderOptionsViewModel @Inject constructor(
|
||||
tagPhotos = {
|
||||
viewModelScope.launch {
|
||||
scanPhotoForTags(driveLinks)
|
||||
deselectLinks(selectionId)
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -131,7 +140,12 @@ class MultipleFileOrFolderOptionsViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
exportToDownload(
|
||||
driveLinks.filterIsInstance<DriveLink.File>().map { driveLink -> driveLink.id }
|
||||
)
|
||||
).onFailure { error ->
|
||||
error.log(
|
||||
VIEW_MODEL,
|
||||
"Failed to export to download for ${driveLinks.map { it.id.id }}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -149,7 +163,9 @@ class MultipleFileOrFolderOptionsViewModel @Inject constructor(
|
||||
driveLinks
|
||||
.filterIsInstance<DriveLink.File>()
|
||||
)
|
||||
deselectLinks(selectionId)
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
+2
-2
@@ -155,7 +155,7 @@ abstract class MultiplePhotosOptionsViewModel(
|
||||
.let { photoListings ->
|
||||
addToAlbumInfo(albumId, photoListings.toSet())
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to add photos to album info")
|
||||
error.log(VIEW_MODEL, "Failed to add photos to album info for ${albumId.id}")
|
||||
error.broadcast()
|
||||
}
|
||||
.onSuccess {
|
||||
@@ -163,7 +163,7 @@ abstract class MultiplePhotosOptionsViewModel(
|
||||
showAddToAlbumStartMessage()
|
||||
addPhotosToAlbum(albumId)
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to add photos to album")
|
||||
error.log(VIEW_MODEL, "Failed to add photos to album for ${albumId.id}")
|
||||
error.broadcast()
|
||||
}
|
||||
.onSuccess { result ->
|
||||
|
||||
@@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.common.onClick
|
||||
import me.proton.android.drive.ui.navigation.PagerType
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
@@ -51,9 +52,8 @@ import me.proton.android.drive.ui.viewstate.OfflineViewState
|
||||
import me.proton.android.drive.usecase.OpenProtonDocumentInBrowser
|
||||
import me.proton.core.domain.arch.onSuccess
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.domain.entity.Percentage
|
||||
import me.proton.core.drive.base.domain.entity.onProcessing
|
||||
import me.proton.core.drive.base.domain.extension.onFailure
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
@@ -78,7 +78,6 @@ import me.proton.core.drive.messagequeue.domain.entity.BroadcastMessage
|
||||
import me.proton.core.drive.share.domain.entity.ShareId
|
||||
import me.proton.core.drive.sorting.domain.entity.Sorting
|
||||
import me.proton.core.drive.sorting.domain.usecase.GetSorting
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import me.proton.drive.android.settings.domain.entity.LayoutType
|
||||
import me.proton.drive.android.settings.domain.usecase.GetLayoutType
|
||||
import me.proton.drive.android.settings.domain.usecase.ToggleLayoutType
|
||||
@@ -113,7 +112,7 @@ class OfflineViewModel @Inject constructor(
|
||||
.onSuccess { driveLink ->
|
||||
return@map driveLink
|
||||
}
|
||||
.onFailure { error -> error.log(VIEW_MODEL) }
|
||||
.onFailure { error -> error.logResult(VIEW_MODEL, "Cannot get drive link for ${folderId?.id}") }
|
||||
return@map null
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
val driveLinks: Flow<PagingData<DriveLink>> = flowOf(folderId).flatMapLatest { folderId ->
|
||||
@@ -135,6 +134,7 @@ class OfflineViewModel @Inject constructor(
|
||||
title = savedStateHandle.get(Screen.Files.FOLDER_NAME),
|
||||
titleResId = I18N.string.title_offline_available,
|
||||
navigationIconResId = CorePresentation.drawable.ic_arrow_back,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_close_action),
|
||||
drawerGesturesEnabled = false,
|
||||
sorting = Sorting.DEFAULT,
|
||||
listContentState = listContentState.value,
|
||||
@@ -267,7 +267,14 @@ class OfflineViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onToggleLayout() {
|
||||
viewModelScope.launch { toggleLayoutType(userId = userId, currentLayoutType = layoutType.value) }
|
||||
viewModelScope.launch {
|
||||
toggleLayoutType(
|
||||
userId = userId,
|
||||
currentLayoutType = layoutType.value
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to toggle layout type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun retryList() {
|
||||
|
||||
@@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.entity.PhotoBackupState
|
||||
import me.proton.android.drive.photos.domain.usecase.EnablePhotosBackup
|
||||
import me.proton.android.drive.photos.domain.usecase.GetPhotosDriveLink
|
||||
@@ -39,19 +40,16 @@ import me.proton.android.drive.photos.presentation.viewstate.BackupPermissionsVi
|
||||
import me.proton.android.drive.usecase.MarkOnboardingAsShown
|
||||
import me.proton.core.drive.backup.domain.entity.BackupPermissions
|
||||
import me.proton.core.drive.backup.domain.manager.BackupPermissionsManager
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.entity.Bytes
|
||||
import me.proton.core.drive.base.domain.extension.firstSuccessOrError
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.drive.base.domain.extension.toResult
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.viewevent.OnboardingViewEvent
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.base.presentation.viewstate.OnboardingViewState
|
||||
import me.proton.core.drive.user.domain.extension.isFree
|
||||
import me.proton.core.user.domain.usecase.GetUser
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import javax.inject.Inject
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
|
||||
@@ -147,7 +145,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
getPhotosDriveLink(userId)
|
||||
.firstSuccessOrError()
|
||||
.toResult()
|
||||
.getOrNull(LogTag.BACKUP, "Getting photos drive link during onboarding failed")
|
||||
.getOrNull(VIEW_MODEL, "Getting photos drive link during onboarding failed")
|
||||
?.id
|
||||
?.let { photoRootId ->
|
||||
enablePhotosBackup(photoRootId)
|
||||
@@ -158,7 +156,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
.onFailure { error ->
|
||||
error.log(LogTag.BACKUP, "Enabling backup during onboarding failed")
|
||||
error.log(VIEW_MODEL, "Enabling backup during onboarding failed")
|
||||
dismiss?.invoke()
|
||||
}
|
||||
}
|
||||
@@ -171,8 +169,9 @@ class OnboardingViewModel @Inject constructor(
|
||||
|
||||
private fun onboardingShown() {
|
||||
viewModelScope.launch {
|
||||
markOnboardingAsShown()
|
||||
.getOrNull(VIEW_MODEL, "Marking onboarding as shown failed")
|
||||
markOnboardingAsShown().onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Marking onboarding as shown failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-19
@@ -208,14 +208,13 @@ class ParentFolderOptionsViewModel @Inject constructor(
|
||||
folder = folder,
|
||||
uploadFileDescriptions = uriStrings.map { uri -> UploadFileDescription(uri) },
|
||||
priority = UploadFileLink.USER_PRIORITY,
|
||||
)
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Upload files failed")
|
||||
if (error is NotEnoughSpaceException) {
|
||||
navigateToStorageFull()
|
||||
return@launch
|
||||
}
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Upload files failed in ${folder.id.id}")
|
||||
if (error is NotEnoughSpaceException) {
|
||||
navigateToStorageFull()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
@@ -228,10 +227,9 @@ class ParentFolderOptionsViewModel @Inject constructor(
|
||||
folderId = folder.id,
|
||||
uriString = uriString,
|
||||
shouldBroadcastMessage = true,
|
||||
)
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Upload folder failed")
|
||||
}
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Upload folder failed in ${folder.id.id}")
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
@@ -247,15 +245,14 @@ class ParentFolderOptionsViewModel @Inject constructor(
|
||||
uploadFileDescriptions = listOf(UploadFileDescription(uri.toString())),
|
||||
shouldDeleteSource = true,
|
||||
priority = UploadFileLink.USER_PRIORITY,
|
||||
)
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Upload file failed")
|
||||
if (error is NotEnoughSpaceException) {
|
||||
navigateToStorageFull()
|
||||
updatePhotoUri(null)
|
||||
return@launch
|
||||
}
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Upload file failed in ${folder.id.id}")
|
||||
if (error is NotEnoughSpaceException) {
|
||||
navigateToStorageFull()
|
||||
updatePhotoUri(null)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
withContext(Job() + Dispatchers.IO) {
|
||||
@@ -351,6 +348,7 @@ class ParentFolderOptionsViewModel @Inject constructor(
|
||||
navigateToPreview(fileId)
|
||||
}
|
||||
.onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to create new document $documentType in ${folderId.id}")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(appContext, configurationProvider.useExceptionMessage),
|
||||
|
||||
@@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.getDefaultMessage
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.entity.PhotoBackupState
|
||||
import me.proton.android.drive.photos.domain.usecase.GetPhotosConfiguration
|
||||
import me.proton.android.drive.photos.domain.usecase.GetPhotosDriveLink
|
||||
@@ -45,11 +46,10 @@ import me.proton.android.drive.ui.viewstate.PhotosBackupViewState
|
||||
import me.proton.android.drive.ui.viewstate.TagsMigrationProgressState
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.backup.domain.entity.BackupNetworkType
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.exception.DriveException
|
||||
import me.proton.core.drive.base.domain.extension.firstSuccessOrError
|
||||
import me.proton.core.drive.base.domain.extension.toResult
|
||||
import me.proton.core.drive.base.domain.log.LogTag.BACKUP
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.domain.usecase.IsIgnoringBatteryOptimizations
|
||||
@@ -180,7 +180,7 @@ class PhotosBackupViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}.onFailure { error ->
|
||||
error.log(BACKUP)
|
||||
error.log(VIEW_MODEL, "Failed to toggle backup")
|
||||
val userError = if (error.cause is DriveException) {
|
||||
error.cause as DriveException
|
||||
} else {
|
||||
@@ -201,7 +201,7 @@ class PhotosBackupViewModel @Inject constructor(
|
||||
override val onToggleMobileData = {
|
||||
viewModelScope.launch {
|
||||
togglePhotosNetworkConfiguration(userId).onFailure { error ->
|
||||
error.log(BACKUP)
|
||||
error.log(VIEW_MODEL, "Failed to toggle photos network configuration")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ import me.proton.android.drive.ui.viewevent.PhotosExportDataViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.PhotosExportDataViewState
|
||||
import me.proton.android.drive.usecase.ExportPhotoData
|
||||
import me.proton.android.drive.usecase.SendFile
|
||||
import me.proton.core.drive.base.domain.log.LogTag.BACKUP
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
@@ -82,7 +82,7 @@ class PhotosExportDataViewModel @Inject constructor(
|
||||
|
||||
private suspend fun onExportData(context: Context) {
|
||||
val onFailure: (exception: Throwable) -> Unit = { error ->
|
||||
error.log(BACKUP)
|
||||
error.log(VIEW_MODEL, "Failed to export data")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+25
-8
@@ -23,6 +23,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.usecase.AddToAlbumInfo
|
||||
import me.proton.android.drive.photos.domain.usecase.GetAddToAlbumPhotoListings
|
||||
import me.proton.android.drive.photos.domain.usecase.GetPhotoListingCount
|
||||
@@ -118,11 +119,19 @@ open class PhotosPickerAndSelectionViewModel(
|
||||
private fun addToAlbumAndToSelected(driveLink: DriveLink.File) = viewModelScope.launch {
|
||||
val photoListings = setOf(driveLink.toVolumePhotoListing())
|
||||
if (destinationAlbumId == null) {
|
||||
addToAlbumInfo(photoListings)
|
||||
.getOrNull(VIEW_MODEL, "Failed to add to album info for new album ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}")
|
||||
addToAlbumInfo(photoListings).onFailure { error ->
|
||||
error.log(
|
||||
VIEW_MODEL,
|
||||
"Failed to add to album info for new album ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
addToAlbumInfo(destinationAlbumId, photoListings)
|
||||
.getOrNull(VIEW_MODEL, "Failed to add to album info ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}")
|
||||
addToAlbumInfo(destinationAlbumId, photoListings).onFailure { error ->
|
||||
error.log(
|
||||
VIEW_MODEL,
|
||||
"Failed to add to album info ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
addSelected(listOf(driveLink.id))
|
||||
}
|
||||
@@ -130,11 +139,19 @@ open class PhotosPickerAndSelectionViewModel(
|
||||
private fun removeFromAlbumAndFromSelected(driveLink: DriveLink.File) = viewModelScope.launch {
|
||||
val photoListings = setOf(driveLink.toVolumePhotoListing())
|
||||
if (destinationAlbumId == null) {
|
||||
removeFromAlbumInfo(photoListings)
|
||||
.getOrNull(VIEW_MODEL, "Failed to remove from album info for new album ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}")
|
||||
removeFromAlbumInfo(photoListings).onFailure { error ->
|
||||
error.log(
|
||||
VIEW_MODEL,
|
||||
"Failed to remove from album info for new album ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
removeFromAlbumInfo(destinationAlbumId, photoListings)
|
||||
.getOrNull(VIEW_MODEL, "Failed to remove from album info ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}")
|
||||
removeFromAlbumInfo(destinationAlbumId, photoListings).onFailure { error ->
|
||||
error.log(
|
||||
VIEW_MODEL,
|
||||
"Failed to remove from album info ShareId=${driveLink.id.shareId.id.logId()}, LinkId=${driveLink.id.id.logId()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
removeSelected(listOf(driveLink.id))
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.presentation.viewevent.PhotosUpsellViewEvent
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.PHOTO
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.component.RunAction
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.telemetry.domain.event.PhotosEvent.UpsellPhotosAccepted
|
||||
@@ -60,7 +60,7 @@ class PhotosUpsellViewModel @Inject constructor(
|
||||
override val onDismiss = {
|
||||
viewModelScope.launch {
|
||||
cancelUserMessage(userId, UserMessage.UPSELL_PHOTOS).onFailure { error ->
|
||||
error.log(PHOTO, "Cannot cancel upsell photos message")
|
||||
error.log(VIEW_MODEL, "Cannot cancel upsell photos message")
|
||||
}
|
||||
}
|
||||
Unit
|
||||
|
||||
@@ -26,9 +26,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.CombinedLoadStates
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.insertSeparators
|
||||
import androidx.paging.map
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -38,10 +36,11 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
|
||||
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -53,6 +52,7 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.extension.thumbnailVO
|
||||
import me.proton.android.drive.photos.domain.entity.PhotoBackupState
|
||||
import me.proton.android.drive.photos.domain.usecase.AddToAlbumInfo
|
||||
@@ -92,7 +92,6 @@ import me.proton.core.drive.backup.domain.usecase.GetDisabledBackupState
|
||||
import me.proton.core.drive.backup.domain.usecase.RetryBackup
|
||||
import me.proton.core.drive.backup.domain.usecase.SyncFolders
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.entity.FastScrollAnchor
|
||||
import me.proton.core.drive.base.domain.entity.TimestampMs
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
@@ -100,7 +99,6 @@ import me.proton.core.drive.base.domain.extension.flowOf
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.drive.base.domain.extension.mapWithPrevious
|
||||
import me.proton.core.drive.base.domain.extension.onFailure
|
||||
import me.proton.core.drive.base.domain.log.LogTag.BACKUP
|
||||
import me.proton.core.drive.base.domain.log.LogTag.PHOTO
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.log.logId
|
||||
@@ -117,9 +115,12 @@ import me.proton.core.drive.base.presentation.state.ListContentState
|
||||
import me.proton.core.drive.base.presentation.viewmodel.onLoadState
|
||||
import me.proton.core.drive.base.presentation.viewstate.TagViewState
|
||||
import me.proton.core.drive.drivelink.domain.entity.DriveLink
|
||||
import me.proton.core.drive.drivelink.photo.domain.entity.PhotoListingAnchor
|
||||
import me.proton.core.drive.drivelink.photo.domain.entity.PhotoListingsSyncState
|
||||
import me.proton.core.drive.drivelink.photo.domain.paging.PhotoDriveLinks
|
||||
import me.proton.core.drive.drivelink.photo.domain.usecase.GetPagedPhotoListingsList
|
||||
import me.proton.core.drive.drivelink.photo.domain.usecase.GetPhotoCount
|
||||
import me.proton.core.drive.drivelink.photo.domain.usecase.GetPhotoListingsPagingData
|
||||
import me.proton.core.drive.drivelink.photo.domain.usecase.PhotoListingsLoader
|
||||
import me.proton.core.drive.drivelink.selection.domain.usecase.GetSelectedDriveLinks
|
||||
import me.proton.core.drive.drivelink.selection.domain.usecase.SelectAll
|
||||
import me.proton.core.drive.feature.flag.domain.usecase.IsSpringSalePromoEnabled
|
||||
@@ -148,6 +149,7 @@ import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.domain.extension.combine as baseCombine
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
import me.proton.core.presentation.R as CorePresentation
|
||||
@@ -165,7 +167,7 @@ class PhotosViewModel @Inject constructor(
|
||||
removeFromAlbumInfo: RemoveFromAlbumInfo,
|
||||
getAddToAlbumPhotoListings: GetAddToAlbumPhotoListings,
|
||||
getPhotoListingCount: GetPhotoListingCount,
|
||||
getPagedPhotoListingsList: GetPagedPhotoListingsList,
|
||||
private val getPhotoListingsPagingData: GetPhotoListingsPagingData,
|
||||
getBackupState: GetBackupState,
|
||||
getDisabledBackupState: GetDisabledBackupState,
|
||||
getPhotoCount: GetPhotoCount,
|
||||
@@ -191,6 +193,7 @@ class PhotosViewModel @Inject constructor(
|
||||
private val cancelUserMessage: CancelUserMessage,
|
||||
private val toFirstItemMetricsNotifier: ToFirstItemMetricsNotifier,
|
||||
private val isSpringSalePromoEnabled: IsSpringSalePromoEnabled,
|
||||
private val photoListingsLoader: PhotoListingsLoader,
|
||||
val backupPermissionsViewModel: BackupPermissionsViewModel,
|
||||
) : PhotosPickerAndSelectionViewModel(
|
||||
savedStateHandle = savedStateHandle,
|
||||
@@ -272,6 +275,7 @@ class PhotosViewModel @Inject constructor(
|
||||
val initialViewState = PhotosViewState(
|
||||
title = appContext.getString(I18N.string.photos_title),
|
||||
navigationIconResId = CorePresentation.drawable.ic_proton_hamburger,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_open_side_menu_action),
|
||||
topBarActions = topBarActions,
|
||||
listContentState = ListContentState.Loading,
|
||||
showEmptyList = null,
|
||||
@@ -304,7 +308,7 @@ class PhotosViewModel @Inject constructor(
|
||||
contentState = listContentState,
|
||||
shareType = Share.Type.PHOTO,
|
||||
)
|
||||
error.log(VIEW_MODEL, "Cannot get drive link")
|
||||
error.logResult(VIEW_MODEL, "Cannot get drive link")
|
||||
toFirstItemMetricsNotifier.reset()
|
||||
if (previous is DataResult.Success) {
|
||||
retryLoadingPhotosDriveLinkFolder()
|
||||
@@ -315,28 +319,75 @@ class PhotosViewModel @Inject constructor(
|
||||
)
|
||||
}.stateIn(viewModelScope, Eagerly, null)
|
||||
|
||||
private fun loaderKey(tag: PhotoTag?) =
|
||||
tag?.let { PhotoListingAnchor.tagKey(it) } ?: PhotoListingAnchor.mainKey
|
||||
|
||||
private val _startLoader = combine(
|
||||
driveLink.filterNotNull(),
|
||||
photoListingsFilter,
|
||||
) { link, tag ->
|
||||
photoListingsLoader.load(
|
||||
key = loaderKey(tag),
|
||||
userId = userId,
|
||||
volumeId = link.volumeId,
|
||||
shareId = link.id.shareId,
|
||||
tag = tag,
|
||||
)
|
||||
}.stateIn(viewModelScope, Eagerly, Unit)
|
||||
|
||||
val loaderSyncState: StateFlow<PhotoListingsSyncState> = combine(
|
||||
driveLink.filterNotNull(),
|
||||
photoListingsFilter,
|
||||
) { link, tag ->
|
||||
photoListingsLoader.stateFor(loaderKey(tag))
|
||||
}.flatMapLatest { it }
|
||||
.stateIn(viewModelScope, Eagerly, PhotoListingsSyncState.Idle)
|
||||
|
||||
private val _loaderErrors = viewModelScope.launch {
|
||||
loaderSyncState.collect { syncState ->
|
||||
if (syncState is PhotoListingsSyncState.Error && listContentState.value is ListContentState.Content) {
|
||||
_homeEffect.emit(
|
||||
HomeEffect.ShowSnackbar(
|
||||
syncState.error.getDefaultMessage(appContext, configurationProvider.useExceptionMessage)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val effectiveContentState: StateFlow<ListContentState> = combine(
|
||||
listContentState,
|
||||
loaderSyncState,
|
||||
) { contentState, syncState ->
|
||||
when {
|
||||
syncState is PhotoListingsSyncState.Loading && contentState is ListContentState.Content ->
|
||||
contentState.copy(isRefreshing = true)
|
||||
syncState is PhotoListingsSyncState.Loading && contentState !is ListContentState.Content ->
|
||||
ListContentState.Loading
|
||||
syncState is PhotoListingsSyncState.Error && contentState !is ListContentState.Content ->
|
||||
ListContentState.Error(
|
||||
message = syncState.error.getDefaultMessage(appContext, configurationProvider.useExceptionMessage),
|
||||
actionResId = I18N.string.common_retry,
|
||||
)
|
||||
else -> contentState
|
||||
}
|
||||
}.stateIn(viewModelScope, Eagerly, ListContentState.Loading)
|
||||
|
||||
val driveLinksMap: Flow<Map<LinkId, DriveLink>> = photoDriveLinks.getDriveLinksMapFlow(userId)
|
||||
|
||||
val driveLinks: Flow<PagingData<PhotosItem>> = combine(
|
||||
parentId.filterNotNull().distinctUntilChanged(),
|
||||
photoListingsFilter,
|
||||
) { _, filter ->
|
||||
filter
|
||||
}.transformLatest { filter ->
|
||||
emit(PagingData.empty())
|
||||
emitAll(getPagedPhotoListingsList(userId, filter)
|
||||
.map { pagingData ->
|
||||
pagingData.map { photoListing ->
|
||||
PhotosItem.PhotoListing(
|
||||
id = photoListing.linkId,
|
||||
captureTime = photoListing.captureTime,
|
||||
link = null,
|
||||
thumbnailVO = photoListing.thumbnailVO,
|
||||
)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
it.insertSeparators { before: PhotosItem.PhotoListing?, after: PhotosItem.PhotoListing? ->
|
||||
private val pagingDataCache = mutableMapOf<PhotoTag?, Flow<PagingData<PhotosItem>>>()
|
||||
|
||||
private fun pagingDataForTag(link: DriveLink.Folder, tag: PhotoTag?): Flow<PagingData<PhotosItem>> =
|
||||
pagingDataCache.getOrPut(tag) {
|
||||
getPhotoListingsPagingData(userId, link.volumeId, tag) { listing ->
|
||||
PhotosItem.PhotoListing(
|
||||
id = listing.linkId,
|
||||
captureTime = listing.captureTime,
|
||||
link = null,
|
||||
thumbnailVO = listing.thumbnailVO,
|
||||
)
|
||||
}.map { pagingData ->
|
||||
pagingData.insertSeparators { before: PhotosItem.PhotoListing?, after: PhotosItem.PhotoListing? ->
|
||||
if (after == null) {
|
||||
null
|
||||
} else if (before == null) {
|
||||
@@ -356,10 +407,8 @@ class PhotosViewModel @Inject constructor(
|
||||
val afterCalendar = Calendar.getInstance().apply {
|
||||
timeInMillis = after.captureTime.value * 1000L
|
||||
}
|
||||
if (beforeCalendar.get(Calendar.YEAR)
|
||||
!= afterCalendar.get(Calendar.YEAR) ||
|
||||
beforeCalendar.get(Calendar.MONTH)
|
||||
!= afterCalendar.get(Calendar.MONTH)
|
||||
if (beforeCalendar.get(Calendar.YEAR) != afterCalendar.get(Calendar.YEAR) ||
|
||||
beforeCalendar.get(Calendar.MONTH) != afterCalendar.get(Calendar.MONTH)
|
||||
) {
|
||||
val cal = Calendar.getInstance().apply {
|
||||
timeInMillis = after.captureTime.value * 1000L
|
||||
@@ -375,9 +424,17 @@ class PhotosViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}.cachedIn(viewModelScope)
|
||||
}.shareIn(viewModelScope, WhileSubscribed(5_000), replay = 1)
|
||||
}
|
||||
|
||||
val photoItems: Flow<PagingData<PhotosItem>> = combine(
|
||||
driveLink.filterNotNull(),
|
||||
photoListingsFilter,
|
||||
) { link, filter ->
|
||||
link to filter
|
||||
}.flatMapLatest { (link, filter) ->
|
||||
pagingDataForTag(link, filter)
|
||||
}
|
||||
|
||||
private val listContentAppendingState = MutableStateFlow<ListContentAppendingState>(
|
||||
ListContentAppendingState.Idle
|
||||
@@ -413,7 +470,7 @@ class PhotosViewModel @Inject constructor(
|
||||
|
||||
val viewState: Flow<PhotosViewState> = baseCombine(
|
||||
selected,
|
||||
listContentState,
|
||||
effectiveContentState,
|
||||
backupState,
|
||||
getPhotoCount(userId = userId),
|
||||
firstVisibleItemIndex,
|
||||
@@ -476,6 +533,11 @@ class PhotosViewModel @Inject constructor(
|
||||
} else {
|
||||
CorePresentation.drawable.ic_proton_cross
|
||||
},
|
||||
navigationContentDescription = if (showHamburgerMenuIcon) {
|
||||
appContext.getString(I18N.string.common_open_side_menu_action)
|
||||
} else {
|
||||
appContext.getString(I18N.string.common_close_action)
|
||||
},
|
||||
notificationDotVisible = showHamburgerMenuIcon && notificationDotRequested,
|
||||
inMultiselect = selected.isNotEmpty() || inPickerMode,
|
||||
isFastScrollEnabled = isFastScrollEnabled,
|
||||
@@ -660,7 +722,7 @@ class PhotosViewModel @Inject constructor(
|
||||
enablePhotosBackup(folderId).onSuccess { state ->
|
||||
onPhotoBackupState(state)
|
||||
}.onFailure { error ->
|
||||
error.log(BACKUP, "Cannot enable backup for folder: ${folderId.id}")
|
||||
error.log(VIEW_MODEL, "Cannot enable backup for folder: ${folderId.id}")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
@@ -702,7 +764,7 @@ class PhotosViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
(parentId.value as? FolderId)?.let { folderId ->
|
||||
retryBackup(folderId).onFailure { error ->
|
||||
error.log(BACKUP, "Cannot retry on backup")
|
||||
error.log(VIEW_MODEL, "Cannot retry on backup")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
@@ -719,7 +781,7 @@ class PhotosViewModel @Inject constructor(
|
||||
private fun dismissBackgroundRestrictions() {
|
||||
viewModelScope.launch {
|
||||
cancelUserMessage(userId, UserMessage.BACKUP_BATTERY_SETTINGS).onFailure { error ->
|
||||
error.log(BACKUP, "Cannot dismiss battery settings warning")
|
||||
error.log(VIEW_MODEL, "Cannot dismiss battery settings warning")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -734,7 +796,16 @@ class PhotosViewModel @Inject constructor(
|
||||
if (driveLink.value == null) {
|
||||
retryLoadingPhotosDriveLinkFolder()
|
||||
} else {
|
||||
retryList()
|
||||
driveLink.value?.let { link ->
|
||||
val tag = photoListingsFilter.value
|
||||
photoListingsLoader.retry(
|
||||
key = loaderKey(tag),
|
||||
userId = userId,
|
||||
volumeId = link.volumeId,
|
||||
shareId = link.id.shareId,
|
||||
tag = tag,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -744,10 +815,6 @@ class PhotosViewModel @Inject constructor(
|
||||
listContentState.value = ListContentState.Loading
|
||||
}
|
||||
|
||||
private suspend fun retryList() {
|
||||
_listEffect.emit(ListEffect.RETRY)
|
||||
}
|
||||
|
||||
private fun onRefresh() {
|
||||
viewModelScope.launch {
|
||||
(parentId.value as? FolderId)?.let { folderId ->
|
||||
@@ -758,6 +825,16 @@ class PhotosViewModel @Inject constructor(
|
||||
error.log(VIEW_MODEL, "Failed sync folder on manual refresh")
|
||||
}
|
||||
}
|
||||
driveLink.value?.let { link ->
|
||||
val tag = photoListingsFilter.value
|
||||
photoListingsLoader.refresh(
|
||||
key = loaderKey(tag),
|
||||
userId = userId,
|
||||
volumeId = link.volumeId,
|
||||
shareId = link.id.shareId,
|
||||
tag = tag,
|
||||
)
|
||||
}
|
||||
_listEffect.emit(ListEffect.REFRESH)
|
||||
}
|
||||
}
|
||||
@@ -792,7 +869,10 @@ class PhotosViewModel @Inject constructor(
|
||||
isFastScrollEnabled.value = isFastScrollThresholdReached(items.size, anchors, anchorsInLabel)
|
||||
}
|
||||
|
||||
private val fastScrollAnchors: MutableMap<Pair<Int, Int>, List<FastScrollAnchor>> = mutableMapOf()
|
||||
private val fastScrollAnchors: MutableMap<Pair<Int, Int>, List<FastScrollAnchor>> =
|
||||
object : LinkedHashMap<Pair<Int, Int>, List<FastScrollAnchor>>() {
|
||||
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Pair<Int, Int>, List<FastScrollAnchor>>) = size > 1
|
||||
}
|
||||
|
||||
private val List<PhotosItem>.itemsHash get() = this.fold(1) { acc, item ->
|
||||
31 * acc + item.hashCode()
|
||||
|
||||
@@ -74,6 +74,7 @@ import me.proton.core.drive.base.domain.entity.TimestampS
|
||||
import me.proton.core.drive.base.domain.entity.toFileTypeCategory
|
||||
import me.proton.core.drive.base.domain.extension.bytes
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.drive.base.domain.function.pagedList
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
@@ -258,6 +259,7 @@ class PreviewViewModel @Inject constructor(
|
||||
private val renderFailed = MutableStateFlow<Pair<Throwable, Any>?>(null)
|
||||
val initialViewState = PreviewViewState(
|
||||
navigationIconResId = CorePresentation.drawable.ic_arrow_back,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_back_action),
|
||||
isFullscreen = isFullscreen,
|
||||
previewContentState = PreviewContentState.Loading,
|
||||
items = emptyList(),
|
||||
@@ -547,7 +549,7 @@ class PreviewViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
openProtonDocumentInBrowser(driveLink)
|
||||
.onFailure { error ->
|
||||
error.log(LogTag.DEFAULT, "Open document failed")
|
||||
error.log(VIEW_MODEL, "Open document failed")
|
||||
val message = when {
|
||||
error is ActivityNotFoundException -> appContext.getString(I18N.string.common_error_no_browser_available)
|
||||
else -> error.getDefaultMessage(
|
||||
@@ -651,9 +653,9 @@ class FolderContentProvider(
|
||||
.distinctUntilChanged()
|
||||
.mapLatest {
|
||||
getDecryptedDriveLinks(folderId)
|
||||
.getOrNull()
|
||||
.getOrNull(LogTag.DEFAULT, "Cannot decrypt drive links for ${folderId.id}")
|
||||
?.filterIsInstance<DriveLink.File>()
|
||||
?: emptyList<DriveLink.File>()
|
||||
?: emptyList()
|
||||
}
|
||||
)
|
||||
} ?: emit(emptyList())
|
||||
@@ -726,9 +728,9 @@ class OfflineContentProvider(
|
||||
.distinctUntilChanged()
|
||||
.mapLatest {
|
||||
getDecryptedOfflineDriveLinks(userId)
|
||||
.getOrNull()
|
||||
.getOrNull(LogTag.DEFAULT, "Cannot decrypt offline drive links")
|
||||
?.filterIsInstance<DriveLink.File>()
|
||||
?: emptyList<DriveLink.File>()
|
||||
?: emptyList()
|
||||
}
|
||||
.stateIn(coroutineScope, SharingStarted.Eagerly, emptyList())
|
||||
|
||||
|
||||
+6
-9
@@ -46,7 +46,6 @@ import me.proton.android.drive.extension.getDefaultMessage
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.core.drive.base.domain.entity.TimestampMs
|
||||
import me.proton.core.drive.base.domain.extension.bytes
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.drive.base.domain.extension.toResult
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
@@ -189,20 +188,18 @@ class ScanDocumentNameViewModel @Inject constructor(
|
||||
error.showOnDoneError()
|
||||
return@launch
|
||||
}
|
||||
removeScanResult(userId, scanResult.id).getOrNull(
|
||||
tag = VIEW_MODEL,
|
||||
message = "Failed to remove scan result"
|
||||
)
|
||||
removeScanResult(userId, scanResult.id).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to remove scan result")
|
||||
}
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCancel(navigateBack: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
clearScanResult(userId, scanResultId).getOrNull(
|
||||
tag = VIEW_MODEL,
|
||||
message = "Failed clearing scan result $scanResultId",
|
||||
)
|
||||
clearScanResult(userId, scanResultId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed clearing scan result $scanResultId")
|
||||
}
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.common.Action
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.drivelink.domain.entity.DriveLink
|
||||
@@ -99,7 +101,9 @@ open class SelectionViewModel(
|
||||
super.onCleared()
|
||||
selectionId.value?.let { selectionId ->
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
deselectLinks(selectionId)
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +117,13 @@ open class SelectionViewModel(
|
||||
protected open fun onTopAppBarNavigation(nonSelectedBlock: () -> Unit): () -> Unit = {
|
||||
Unit.also {
|
||||
if (selected.value.isNotEmpty()) {
|
||||
selectionId.value?.let { viewModelScope.launch { deselectLinks(it) } }
|
||||
selectionId.value?.let { selectionId ->
|
||||
viewModelScope.launch {
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nonSelectedBlock()
|
||||
}
|
||||
@@ -152,16 +162,25 @@ open class SelectionViewModel(
|
||||
|
||||
protected open fun addSelected(linkIds: List<LinkId>) {
|
||||
viewModelScope.launch {
|
||||
selectionId.value?.let { selectionId ->
|
||||
selectLinks(selectionId, linkIds)
|
||||
} ?: setSelectionId(selectLinks(linkIds).getOrNull())
|
||||
val id = selectionId.value
|
||||
if (id != null) {
|
||||
selectLinks(id, linkIds).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to select links")
|
||||
}
|
||||
} else {
|
||||
setSelectionId(selectLinks(linkIds).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to select links")
|
||||
}.getOrNull())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun removeSelected(linkIds: List<LinkId>) {
|
||||
viewModelScope.launch {
|
||||
selectionId.value?.let { selectionId ->
|
||||
deselectLinks(selectionId, linkIds)
|
||||
deselectLinks(selectionId, linkIds).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,7 +188,11 @@ open class SelectionViewModel(
|
||||
protected fun removeAllSelected() {
|
||||
if (selected.value.isNotEmpty()) {
|
||||
viewModelScope.launch {
|
||||
selectionId.value?.let { selectionId -> deselectLinks(selectionId) }
|
||||
selectionId.value?.let { selectionId ->
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.take
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.entity.Percentage
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
|
||||
@@ -183,6 +183,7 @@ class SettingsViewModel @Inject constructor(
|
||||
) { debugSettings, themeStyle, enabled, autoLockDuration, isBackupEnabled, dynamicHomeTab, userLogKillSwitch ->
|
||||
SettingsViewState(
|
||||
navigationIcon = CorePresentation.drawable.ic_arrow_back,
|
||||
navigationContentDescription = context.getString(I18N.string.common_back_action),
|
||||
appNameResId = I18N.string.app_name,
|
||||
appVersion = BuildConfig.VERSION_NAME,
|
||||
legalLinks = listOf(
|
||||
@@ -225,7 +226,12 @@ class SettingsViewModel @Inject constructor(
|
||||
},
|
||||
onThemeStyleChanged = { newStyle ->
|
||||
viewModelScope.launch {
|
||||
updateThemeStyle(userId, enumValues<ThemeStyle>().first { style -> style.resId == newStyle })
|
||||
updateThemeStyle(
|
||||
userId = userId,
|
||||
themeStyle = enumValues<ThemeStyle>().first { style -> style.resId == newStyle }
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to update theme style")
|
||||
}
|
||||
}
|
||||
},
|
||||
onAccountSettings = {
|
||||
|
||||
+3
-3
@@ -32,9 +32,9 @@ import me.proton.core.compose.component.bottomsheet.RunAction
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.domain.entity.Permissions
|
||||
import me.proton.core.drive.base.domain.log.LogTag.SHARING
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -130,7 +130,7 @@ abstract class ShareInvitationOptionsViewModel(
|
||||
error: DataResult.Error,
|
||||
type: BroadcastMessage.Type = BroadcastMessage.Type.WARNING
|
||||
) {
|
||||
error.log(SHARING)
|
||||
error.logResult(VIEW_MODEL)
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+3
-3
@@ -31,13 +31,13 @@ import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.options.ShareLinkPermissionsOption
|
||||
import me.proton.core.compose.component.bottomsheet.RunAction
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.entity.Permissions
|
||||
import me.proton.core.drive.base.domain.log.LogTag.SHARING
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -112,7 +112,7 @@ class ShareLinkPermissionsViewModel @Inject constructor(
|
||||
permissions = permissions,
|
||||
).onFailure { error ->
|
||||
error.log(
|
||||
SHARING,
|
||||
VIEW_MODEL,
|
||||
"Cannot update permissions for ${sharedDriveLink.shareUrlId.id}"
|
||||
)
|
||||
broadcastMessages(
|
||||
|
||||
+5
-4
@@ -33,15 +33,16 @@ import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.options.MemberOption
|
||||
import me.proton.core.compose.component.bottomsheet.RunAction
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.domain.entity.Permissions
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
import me.proton.core.drive.base.domain.extension.onFailure
|
||||
import me.proton.core.drive.base.domain.log.LogTag.SHARING
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -145,7 +146,7 @@ class ShareMemberOptionsViewModel @Inject constructor(
|
||||
).filterSuccessOrError()
|
||||
.last()
|
||||
dataResult.onFailure { error ->
|
||||
error.log(SHARING)
|
||||
error.logResult(VIEW_MODEL, "Failed to update member permissions for $memberId")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
@@ -163,7 +164,7 @@ class ShareMemberOptionsViewModel @Inject constructor(
|
||||
memberId = memberId,
|
||||
).filterSuccessOrError().last()
|
||||
dataResult.onFailure { error ->
|
||||
error.log(SHARING)
|
||||
error.logResult(VIEW_MODEL, "Failed to delete member for $memberId")
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = error.getDefaultMessage(
|
||||
|
||||
+5
-1
@@ -24,11 +24,13 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.photos.domain.usecase.AddPhotosToAlbum
|
||||
import me.proton.android.drive.photos.domain.usecase.AddToAlbumInfo
|
||||
import me.proton.android.drive.ui.viewevent.ShareMultiplePhotosOptionsViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.ShareMultiplePhotosOptionsViewState
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.base.presentation.component.RunAction
|
||||
@@ -117,7 +119,9 @@ class ShareMultiplePhotosOptionsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
override suspend fun onSuccessfullyAdded(photoListings: List<PhotoListing.Volume>) {
|
||||
deselectLinks(selectionId)
|
||||
deselectLinks(selectionId).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to deselect links")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAlbumDetails(album: DriveLink.Album?): String? = album.details()
|
||||
|
||||
@@ -18,16 +18,17 @@
|
||||
|
||||
package me.proton.android.drive.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.ui.effect.HomeEffect
|
||||
import me.proton.android.drive.ui.effect.HomeTabViewModel
|
||||
@@ -42,6 +43,7 @@ import me.proton.core.drive.i18n.R as I18N
|
||||
|
||||
@HiltViewModel
|
||||
class SharedTabsViewModel @Inject constructor(
|
||||
@param:ApplicationContext private val appContext: Context,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
shouldUpgradeStorage: ShouldUpgradeStorage,
|
||||
) : ViewModel(),
|
||||
@@ -58,6 +60,7 @@ class SharedTabsViewModel @Inject constructor(
|
||||
val initialViewState = SharedTabsViewState(
|
||||
titleResId = I18N.string.title_shared,
|
||||
navigationIconResId = CorePresentation.drawable.ic_proton_hamburger,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_open_side_menu_action),
|
||||
notificationDotVisible = false,
|
||||
tabs = listOf(sharedWithMeTab, sharedWithByTab),
|
||||
selectedTab = selectedTab.value,
|
||||
|
||||
@@ -23,7 +23,9 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.sorting.domain.entity.Sorting
|
||||
@@ -43,7 +45,9 @@ class SortingDialogViewModel @Inject constructor(
|
||||
|
||||
suspend fun setSorting(sorting: Sorting) {
|
||||
viewModelScope.launch {
|
||||
updateSorting(userId, sorting)
|
||||
updateSorting(userId, sorting).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed update sorting")
|
||||
}
|
||||
}.join()
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,12 +27,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.R
|
||||
import me.proton.android.drive.log.DriveLogTag.DEFAULT
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.viewevent.DriveLitePopupViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.SubscriptionPromoViewState
|
||||
import me.proton.android.drive.usecase.MarkSubscriptionPromoAsShown
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.extension.GiB
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.component.RunAction
|
||||
import me.proton.core.drive.base.presentation.extension.asHumanReadableString
|
||||
import me.proton.core.drive.base.presentation.extension.require
|
||||
@@ -151,7 +151,7 @@ class SubscriptionPromoViewModel @Inject constructor(
|
||||
override val onDismiss = {
|
||||
viewModelScope.launch {
|
||||
markSubscriptionPromoAsShown(userId, key).onFailure { error ->
|
||||
error.log(DEFAULT, "Cannot mark subscription promo as shown")
|
||||
error.log(VIEW_MODEL, "Cannot mark subscription promo as shown")
|
||||
}
|
||||
}
|
||||
Unit
|
||||
|
||||
@@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.common.onClick
|
||||
import me.proton.android.drive.ui.effect.HomeEffect
|
||||
import me.proton.android.drive.ui.effect.HomeTabViewModel
|
||||
@@ -57,18 +58,18 @@ import me.proton.core.drive.base.domain.extension.onFailure
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.presentation.common.getThemeDrawableId
|
||||
import me.proton.core.drive.base.presentation.effect.ListEffect
|
||||
import me.proton.core.drive.base.presentation.state.ListContentAppendingState
|
||||
import me.proton.core.drive.base.presentation.state.ListContentState
|
||||
import me.proton.core.drive.base.presentation.viewmodel.UserViewModel
|
||||
import me.proton.core.drive.base.presentation.viewmodel.onLoadState
|
||||
import me.proton.core.drive.drivelink.crypto.domain.usecase.GetDecryptedDriveLink
|
||||
import me.proton.core.drive.drivelink.domain.entity.DriveLink
|
||||
import me.proton.core.drive.drivelink.domain.extension.isNameEncrypted
|
||||
import me.proton.core.drive.drivelink.list.domain.usecase.GetPagedDriveLinksList
|
||||
import me.proton.core.drive.files.domain.usecase.ToFirstItemMetricsNotifier
|
||||
import me.proton.core.drive.files.presentation.event.FilesViewEvent
|
||||
import me.proton.core.drive.files.presentation.state.FilesViewState
|
||||
import me.proton.core.drive.base.presentation.state.ListContentAppendingState
|
||||
import me.proton.core.drive.base.presentation.state.ListContentState
|
||||
import me.proton.core.drive.base.presentation.effect.ListEffect
|
||||
import me.proton.core.drive.base.presentation.viewmodel.onLoadState
|
||||
import me.proton.core.drive.files.domain.usecase.ToFirstItemMetricsNotifier
|
||||
import me.proton.core.drive.i18n.R
|
||||
import me.proton.core.drive.link.domain.entity.FolderId
|
||||
import me.proton.core.drive.link.domain.extension.userId
|
||||
@@ -76,7 +77,6 @@ import me.proton.core.drive.observability.domain.metrics.common.mobile.performan
|
||||
import me.proton.core.drive.share.domain.entity.ShareId
|
||||
import me.proton.core.drive.sorting.domain.entity.Sorting
|
||||
import me.proton.core.drive.sorting.domain.usecase.GetSorting
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import me.proton.drive.android.settings.domain.entity.LayoutType
|
||||
import me.proton.drive.android.settings.domain.usecase.GetLayoutType
|
||||
import me.proton.drive.android.settings.domain.usecase.ToggleLayoutType
|
||||
@@ -114,6 +114,7 @@ class SyncedFoldersViewModel @Inject constructor(
|
||||
title = folderName,
|
||||
titleResId = R.string.title_offline_available,
|
||||
navigationIconResId = me.proton.core.presentation.R.drawable.ic_arrow_back,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_back_action),
|
||||
drawerGesturesEnabled = true,
|
||||
sorting = Sorting.DEFAULT,
|
||||
listContentState = listContentState.value,
|
||||
@@ -254,7 +255,14 @@ class SyncedFoldersViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onToggleLayout() {
|
||||
viewModelScope.launch { toggleLayoutType(userId = userId, currentLayoutType = layoutType.value) }
|
||||
viewModelScope.launch {
|
||||
toggleLayoutType(
|
||||
userId = userId,
|
||||
currentLayoutType = layoutType.value,
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to toggle layout type $layoutType")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun retryList() {
|
||||
|
||||
@@ -41,12 +41,14 @@ import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.ui.effect.TrashEffect
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.android.drive.ui.screen.EmptyTrashIconState
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.domain.entity.TimestampMs
|
||||
import me.proton.core.drive.base.domain.extension.flowOf
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.presentation.common.getThemeDrawableId
|
||||
import me.proton.core.drive.base.presentation.effect.ListEffect
|
||||
@@ -112,6 +114,7 @@ class TrashViewModel @Inject constructor(
|
||||
titleResId = I18N.string.common_trash,
|
||||
sorting = Sorting.DEFAULT,
|
||||
navigationIconResId = CorePresentation.drawable.ic_arrow_back,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_back_action),
|
||||
drawerGesturesEnabled = true,
|
||||
listContentState = listContentState.value,
|
||||
listContentAppendingState = listContentAppendingState.value,
|
||||
@@ -247,6 +250,13 @@ class TrashViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onToggleLayout() {
|
||||
viewModelScope.launch { toggleLayoutType(userId = userId, currentLayoutType = layoutType.value) }
|
||||
viewModelScope.launch {
|
||||
toggleLayoutType(
|
||||
userId = userId,
|
||||
currentLayoutType = layoutType.value,
|
||||
).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Failed to toggle layout $layoutType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import me.proton.android.drive.ui.navigation.UploadParameters
|
||||
import me.proton.android.drive.ui.viewevent.UploadToViewEvent
|
||||
import me.proton.android.drive.ui.viewstate.UploadToViewState
|
||||
import me.proton.core.domain.arch.mapSuccessValueOrNull
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.core.drive.drivelink.crypto.domain.usecase.GetDecryptedDriveLink
|
||||
@@ -97,6 +97,7 @@ class UploadToViewModel @Inject constructor(
|
||||
filesViewState = initialFilesViewState,
|
||||
title = "",
|
||||
navigationIconResId = CorePresentation.drawable.ic_proton_cross,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_close_action),
|
||||
fileNames = uploadParameters?.uris?.map { uri -> uri.fileName } ?: emptyList(),
|
||||
)
|
||||
val driveLink: StateFlow<DriveLink.Folder?> = getDriveLink(userId, folderId = null)
|
||||
@@ -122,6 +123,11 @@ class UploadToViewModel @Inject constructor(
|
||||
} else {
|
||||
CorePresentation.drawable.ic_arrow_back
|
||||
},
|
||||
navigationContentDescription = if (parentLink == null || isRoot) {
|
||||
appContext.getString(I18N.string.common_close_action)
|
||||
} else {
|
||||
appContext.getString(I18N.string.common_back_action)
|
||||
},
|
||||
)
|
||||
}.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
@@ -176,7 +182,7 @@ class UploadToViewModel @Inject constructor(
|
||||
exitApp()
|
||||
}
|
||||
}.onFailure { error ->
|
||||
error.log(LogTag.UPLOAD, "Failed to upload ${copiedUris.size} files")
|
||||
error.log(VIEW_MODEL, "Failed to upload ${copiedUris.size} files")
|
||||
cleanupOnFailure(copiedUris)
|
||||
when (error) {
|
||||
is NotEnoughSpaceException -> navigateToStorageFull()
|
||||
|
||||
@@ -33,14 +33,14 @@ import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.domain.arch.onSuccess
|
||||
import me.proton.core.drive.base.data.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.data.extension.log as logResult
|
||||
import me.proton.core.drive.base.data.extension.logDefaultMessage
|
||||
import me.proton.core.drive.base.domain.extension.filterSuccessOrError
|
||||
import me.proton.core.drive.base.domain.extension.onFailure
|
||||
import me.proton.core.drive.base.domain.log.LogTag.SHARING
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
@@ -94,7 +94,7 @@ class UserInvitationViewModel @Inject constructor(
|
||||
when (result) {
|
||||
is DataResult.Processing -> listContentState.value = ListContentState.Loading
|
||||
is DataResult.Error -> {
|
||||
result.log(VIEW_MODEL)
|
||||
result.logResult(VIEW_MODEL)
|
||||
listContentState.value = ListContentState.Error(result.logDefaultMessage(
|
||||
context = appContext,
|
||||
useExceptionMessage = configurationProvider.useExceptionMessage,
|
||||
@@ -124,6 +124,7 @@ class UserInvitationViewModel @Inject constructor(
|
||||
}
|
||||
).format(0),
|
||||
navigationIconResId = CorePresentation.drawable.ic_proton_arrow_back,
|
||||
navigationContentDescription = appContext.getString(I18N.string.common_back_action),
|
||||
listContentState = listContentState.value,
|
||||
)
|
||||
|
||||
@@ -155,7 +156,7 @@ class UserInvitationViewModel @Inject constructor(
|
||||
acceptUserInvitation(invitationId).filterSuccessOrError()
|
||||
.last()
|
||||
.onFailure { error ->
|
||||
error.log(SHARING, "Cannot accept invitation: $invitationId")
|
||||
error.logResult(VIEW_MODEL, "Cannot accept invitation: $invitationId")
|
||||
broadcastMessages(
|
||||
userId,
|
||||
error.getDefaultMessage(
|
||||
@@ -185,7 +186,7 @@ class UserInvitationViewModel @Inject constructor(
|
||||
.filterSuccessOrError()
|
||||
.last()
|
||||
.onFailure { error ->
|
||||
error.log(SHARING, "Cannot delete invitation: $invitationId")
|
||||
error.logResult(VIEW_MODEL, "Cannot delete invitation: $invitationId")
|
||||
broadcastMessages(
|
||||
userId,
|
||||
error.getDefaultMessage(
|
||||
|
||||
@@ -26,9 +26,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.usecase.MarkWhatsNewAsShown
|
||||
import me.proton.core.drive.base.domain.extension.flowOf
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.drive.base.domain.log.LogTag.VIEW_MODEL
|
||||
import me.proton.core.drive.base.presentation.common.getThemeDrawableId
|
||||
import me.proton.core.drive.base.presentation.viewevent.WhatsNewViewEvent
|
||||
@@ -69,8 +69,9 @@ class WhatsNewViewModel @Inject constructor(
|
||||
|
||||
private fun whatsNewShown() {
|
||||
viewModelScope.launch {
|
||||
markWhatsNewAsShown(key)
|
||||
.getOrNull(VIEW_MODEL, "Marking whats new as shown failed for: $key")
|
||||
markWhatsNewAsShown(key).onFailure { error ->
|
||||
error.log(VIEW_MODEL, "Marking whats new as shown failed for: $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import me.proton.core.drive.base.presentation.state.ListContentState
|
||||
data class ComputersViewState(
|
||||
val title: String,
|
||||
@DrawableRes val navigationIconResId: Int,
|
||||
val navigationContentDescription: String?,
|
||||
val notificationDotVisible: Boolean,
|
||||
val listContentState: ListContentState,
|
||||
val isRefreshEnabled: Boolean,
|
||||
|
||||
@@ -29,6 +29,7 @@ data class MoveFileViewState(
|
||||
val title: String,
|
||||
val isTitleEncrypted: Boolean = false,
|
||||
val navigationIconResId: Int = 0,
|
||||
val navigationContentDescription: String? = null,
|
||||
val driveLinks: List<String> = emptyList(),
|
||||
val topBarActions: Flow<Set<Action>> = emptyFlow(),
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.annotation.StringRes
|
||||
data class SharedTabsViewState(
|
||||
@StringRes val titleResId: Int,
|
||||
@DrawableRes val navigationIconResId: Int,
|
||||
val navigationContentDescription: String?,
|
||||
val notificationDotVisible: Boolean,
|
||||
val tabs: List<SharedTab>,
|
||||
val selectedTab: SharedTab,
|
||||
|
||||
@@ -28,5 +28,6 @@ data class UploadToViewState(
|
||||
val isTitleEncrypted: Boolean = false,
|
||||
val isBackHandlerEnabled: Boolean = false,
|
||||
val navigationIconResId: Int = 0,
|
||||
val navigationContentDescription: String? = null,
|
||||
val fileNames: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
@@ -27,7 +27,6 @@ import me.proton.core.drive.announce.event.domain.usecase.AnnounceEvent
|
||||
import me.proton.core.drive.base.domain.usecase.ClearCacheFolder
|
||||
import me.proton.core.drive.base.domain.usecase.GetInternalStorageInfo
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.core.drive.photo.data.db.PhotoDatabase
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
@@ -36,7 +35,6 @@ class HandleUncaughtException @Inject constructor(
|
||||
private val getInternalStorageInfo: GetInternalStorageInfo,
|
||||
private val clearCacheFolder: ClearCacheFolder,
|
||||
private val announceEvent: AnnounceEvent,
|
||||
private val db: PhotoDatabase,
|
||||
) {
|
||||
|
||||
operator fun invoke(error: Throwable, isFromMainThread: Boolean): Result<Boolean> = coRunCatching {
|
||||
@@ -46,28 +44,7 @@ class HandleUncaughtException @Inject constructor(
|
||||
clearCacheFolder()
|
||||
}
|
||||
val isCursorWindowError = error.isCursorWindowError
|
||||
val hasGetPhotoListingsDescFlowString = error.stackTraceToString().contains("getPhotoListingsDescFlow")
|
||||
CoreLogger.d(DriveLogTag.CRASH, "HandleUncaughtException isCursorWindowError=$isCursorWindowError, hasGetPhotoListingsDescFlowString=$hasGetPhotoListingsDescFlowString, isFromMainThread=$isFromMainThread")
|
||||
if (error is java.lang.IllegalStateException ||
|
||||
error.cause is java.lang.IllegalStateException
|
||||
) {
|
||||
val invalidCaptureTimes = runBlocking {
|
||||
db.photoListingDao.getInvalidCaptureTimes()
|
||||
}
|
||||
val message = if (invalidCaptureTimes.isNotEmpty()) {
|
||||
buildString {
|
||||
append("Invalid capture time found: ")
|
||||
append(
|
||||
invalidCaptureTimes.joinToString { (captureTime, type) ->
|
||||
"(captureTime=$captureTime type=$type)"
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
"No invalid capture times found in PhotoListingEntity"
|
||||
}
|
||||
CoreLogger.d(DriveLogTag.CRASH, message)
|
||||
}
|
||||
CoreLogger.d(DriveLogTag.CRASH, "HandleUncaughtException isCursorWindowError=$isCursorWindowError, isFromMainThread=$isFromMainThread")
|
||||
when (error) {
|
||||
is IOException,
|
||||
is SQLiteDiskIOException,
|
||||
|
||||
@@ -26,10 +26,8 @@ import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.android.drive.log.DriveLogTag.UI
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.data.entity.LoggerLevel.WARNING
|
||||
import me.proton.core.drive.base.data.extension.log
|
||||
import me.proton.core.drive.base.domain.extension.getOrNull
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -48,8 +46,9 @@ class ShowRatingBooster @Inject constructor(
|
||||
if (task.isSuccessful) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
accountManager.getPrimaryUserId().firstOrNull()?.let { userId ->
|
||||
markRatingBoosterAsShown(userId = userId)
|
||||
.getOrNull(UI, "Marking rating booster as shown failed")
|
||||
markRatingBoosterAsShown(userId = userId).onFailure { error ->
|
||||
error.log(UI, "Marking rating booster as shown failed")
|
||||
}
|
||||
} ?: {
|
||||
CoreLogger.w(UI, "User Id is null, please investigate")
|
||||
}()
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ class AcceptNotificationEvent @Inject constructor(
|
||||
&& (newEvent.state == UploadState.NEW_UPLOAD || newEvent.exists(notificationId))
|
||||
|
||||
is Event.Download -> true
|
||||
is Event.DownloadFileProgress -> true
|
||||
is Event.ForcedSignOut -> true
|
||||
is Event.NoSpaceLeftOnDevice -> true
|
||||
is Event.Backup -> true
|
||||
|
||||
+1
-4
@@ -21,7 +21,6 @@ package me.proton.android.drive.usecase.notification
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.announce.event.domain.entity.Event
|
||||
import me.proton.core.drive.notification.data.extension.createNotificationId
|
||||
import me.proton.core.drive.notification.data.extension.tag
|
||||
import me.proton.core.drive.notification.domain.entity.Channel
|
||||
import me.proton.core.drive.notification.domain.entity.TaglessNotificationId
|
||||
import me.proton.core.drive.notification.domain.extension.createTaglessNotificationId
|
||||
@@ -32,9 +31,7 @@ class CreateUserNotificationId @Inject constructor() {
|
||||
userId: UserId,
|
||||
event: Event,
|
||||
) = when (event) {
|
||||
is Event.Download -> event.createNotificationId(userId).copy(
|
||||
tag = "${event.tag}_${event.downloadId}"
|
||||
)
|
||||
is Event.Download -> event.createNotificationId(userId)
|
||||
|
||||
// Foreground service in worker do not use tag
|
||||
is Event.TransferData -> TaglessNotificationId.UPLOAD.createTaglessNotificationId(
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.usecase.notification
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.net.toUri
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import me.proton.android.drive.extension.deepLinkBaseUrl
|
||||
import me.proton.android.drive.receiver.NotificationBroadcastReceiver
|
||||
import me.proton.android.drive.receiver.NotificationBroadcastReceiver.Companion.ACTION_CANCEL_ALL_DOWNLOADS
|
||||
import me.proton.android.drive.receiver.NotificationBroadcastReceiver.Companion.EXTRA_NOTIFICATION_ID
|
||||
import me.proton.android.drive.ui.navigation.Screen
|
||||
import me.proton.core.drive.announce.event.domain.entity.Event
|
||||
import me.proton.core.drive.notification.domain.entity.NotificationId
|
||||
import me.proton.core.util.kotlin.serialize
|
||||
import javax.inject.Inject
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
|
||||
class DownloadFileProgressNotificationBuilder @Inject constructor(
|
||||
@param:ApplicationContext private val appContext: Context,
|
||||
private val commonBuilder: CommonNotificationBuilder,
|
||||
private val contentIntent: CreateContentPendingIntent,
|
||||
) {
|
||||
operator fun invoke(
|
||||
notificationId: NotificationId.User,
|
||||
events: List<Event.DownloadFileProgress>,
|
||||
) = events.last().let { event ->
|
||||
commonBuilder(notificationId, event)
|
||||
.setContentText(
|
||||
appContext.resources.getQuantityString(
|
||||
I18N.plurals.notification_content_text_download_downloading,
|
||||
event.downloadingCount,
|
||||
event.downloadingCount,
|
||||
)
|
||||
)
|
||||
.setProgress(100, (event.progress.value * 100).toInt(), false)
|
||||
.setSilent(true)
|
||||
.setContentIntent(
|
||||
contentIntent(
|
||||
notificationId = notificationId,
|
||||
uri = "${appContext.deepLinkBaseUrl}/${Screen.Files(notificationId.channel.userId)}".toUri(),
|
||||
)
|
||||
)
|
||||
.addAction(
|
||||
NotificationCompat.Action.Builder(
|
||||
0,
|
||||
appContext.getString(
|
||||
if (event.downloadingCount > 1) {
|
||||
I18N.string.common_cancel_all_action
|
||||
} else {
|
||||
I18N.string.common_cancel_action
|
||||
}
|
||||
),
|
||||
cancelAllDownloadsIntent(notificationId),
|
||||
).build()
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
private fun cancelAllDownloadsIntent(
|
||||
notificationId: NotificationId,
|
||||
requestCode: Int = 8888,
|
||||
): PendingIntent =
|
||||
PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode,
|
||||
Intent(appContext, NotificationBroadcastReceiver::class.java).apply {
|
||||
action = ACTION_CANCEL_ALL_DOWNLOADS
|
||||
putExtra(EXTRA_NOTIFICATION_ID, notificationId.serialize())
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
+5
-5
@@ -35,10 +35,10 @@ class DownloadNotificationBuilder @Inject constructor(
|
||||
private val commonBuilder: CommonNotificationBuilder,
|
||||
private val contentIntent: CreateContentPendingIntent,
|
||||
) {
|
||||
operator fun invoke(notificationId: NotificationId.User, event: Event.Download) =
|
||||
commonBuilder(notificationId, event)
|
||||
operator fun invoke(notificationId: NotificationId.User, events: List<Event.Download>) =
|
||||
commonBuilder(notificationId, events.last())
|
||||
.setContentTitle(appContext.getString(I18N.string.notification_content_title_download_complete))
|
||||
.setContentText(event.text)
|
||||
.setContentText(events.sumOf { it.downloadedFiles }.text)
|
||||
.setContentIntent(notificationId)
|
||||
|
||||
private fun NotificationCompat.Builder.setContentIntent(
|
||||
@@ -50,9 +50,9 @@ class DownloadNotificationBuilder @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
private val Event.Download.text: String get() =
|
||||
private val Int.text: String get() =
|
||||
appContext.quantityString(
|
||||
I18N.plurals.common_in_app_notification_files_download_complete,
|
||||
downloadedFiles,
|
||||
this,
|
||||
)
|
||||
}
|
||||
|
||||
+1
@@ -51,6 +51,7 @@ class ShouldCancelNotification @Inject constructor(
|
||||
}
|
||||
|
||||
is Event.Download -> false
|
||||
is Event.DownloadFileProgress -> event.downloadingCount == 0
|
||||
is Event.ForcedSignOut -> false
|
||||
is Event.NoSpaceLeftOnDevice -> false
|
||||
is Event.Backup -> false
|
||||
|
||||
@@ -28,10 +28,12 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import me.proton.android.drive.extension.log
|
||||
import me.proton.android.drive.receiver.NotificationBroadcastReceiver.Companion.ACTION_CANCEL_ALL
|
||||
import me.proton.android.drive.receiver.NotificationBroadcastReceiver.Companion.ACTION_CANCEL_ALL_DOWNLOADS
|
||||
import me.proton.android.drive.receiver.NotificationBroadcastReceiver.Companion.ACTION_DELETE
|
||||
import me.proton.core.drive.base.domain.extension.resultValueOrNull
|
||||
import me.proton.core.drive.base.domain.log.LogTag
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.core.drive.drivelink.download.domain.usecase.CancelAllDownload
|
||||
import me.proton.core.drive.notification.domain.entity.NotificationId
|
||||
import me.proton.core.drive.notification.domain.usecase.RemoveNotification
|
||||
import me.proton.core.drive.share.domain.usecase.GetMainShare
|
||||
@@ -46,6 +48,7 @@ class NotificationActionWorker @AssistedInject constructor(
|
||||
private val removeNotification: RemoveNotification,
|
||||
private val getMainShare: GetMainShare,
|
||||
private val cancelAllUpload: CancelAllUpload,
|
||||
private val cancelAllDownload: CancelAllDownload,
|
||||
) : CoroutineWorker(appContext, params) {
|
||||
private val action = inputData.getString(KEY_ACTION)
|
||||
private val notificationIdString = inputData.getString(KEY_NOTIFICATION_ID)
|
||||
@@ -79,6 +82,7 @@ class NotificationActionWorker @AssistedInject constructor(
|
||||
shareId,
|
||||
)
|
||||
}
|
||||
ACTION_CANCEL_ALL_DOWNLOADS -> cancelAllDownload(notificationId.channel.userId)
|
||||
else -> RuntimeException("Unknown action '$action'").log(LogTag.NOTIFICATION)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -64,6 +64,9 @@ allprojects {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(module("com.google.protobuf:protobuf-lite"))
|
||||
.using(module("com.google.protobuf:protobuf-javalite:${libs.versions.protobufJavaLite.get()}"))
|
||||
substitute(module("androidx.navigation:navigation-compose"))
|
||||
.using(module("androidx.navigation:navigation-compose:${libs.versions.androidx.navigation.get()}"))
|
||||
.because("androidx-hilt 1.3.0 brings androidx.navigation version 2.9.0 which is more strict that currently used")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,11 +75,12 @@ allprojects {
|
||||
subprojects {
|
||||
configurations.all {
|
||||
exclude(group = "me.proton.crypto", module = "android-golib")
|
||||
exclude(group = "org.jetbrains.kotlin", module = "kotlin-android-extensions-runtime")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
||||
tasks.register("deleteTest", Delete::class) {
|
||||
|
||||
@@ -23,6 +23,7 @@ plugins {
|
||||
dependencies {
|
||||
implementation(libs.gradle.plugin.android)
|
||||
implementation(libs.gradle.plugin.kotlin)
|
||||
implementation(libs.gradle.plugin.ksp)
|
||||
implementation("com.squareup:javapoet:1.13.0") // https://github.com/google/dagger/issues/3068#issuecomment-999118496
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ object Config {
|
||||
const val minSdk = 29
|
||||
const val targetSdk = 35
|
||||
const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
const val versionName = "2.35.0"
|
||||
const val versionName = "2.36.0"
|
||||
const val archivesBaseName = "ProtonDrive-$versionName"
|
||||
val supportedResourceConfigurations = listOf(
|
||||
"b+es+419",
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.gradle.kotlin.dsl.findByType
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KaptExtension
|
||||
import com.google.devtools.ksp.gradle.KspExtension
|
||||
import java.io.File
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@@ -43,16 +43,16 @@ fun Project.driveModule(
|
||||
includeSubmodules: Boolean = false,
|
||||
i18n: Boolean = false,
|
||||
socialTest: Boolean = false,
|
||||
kapt: Boolean = hilt || room || socialTest,
|
||||
showkase: Boolean = false,
|
||||
ksp: Boolean = showkase || hilt || socialTest || room,
|
||||
buildConfig: Boolean = false,
|
||||
enableTestFixtures: Boolean = false,
|
||||
dependencies: DependencyHandler.() -> Unit = {},
|
||||
) {
|
||||
val catalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
apply(plugin = "kotlin-android")
|
||||
if (kapt) {
|
||||
apply(plugin = "kotlin-kapt")
|
||||
if (ksp) {
|
||||
apply(plugin = "com.google.devtools.ksp")
|
||||
}
|
||||
if (compose) {
|
||||
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
|
||||
@@ -72,12 +72,6 @@ fun Project.driveModule(
|
||||
applicationId = Config.applicationId
|
||||
versionCode = versionCodeFromGitCommitCount
|
||||
versionName = Config.versionName
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments["room.schemaLocation"] = "$projectDir/schemas"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -102,6 +96,15 @@ fun Project.driveModule(
|
||||
this.buildConfig = buildConfig
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.add("META-INF/licenses/**")
|
||||
resources.excludes.add("META-INF/LICENSE*")
|
||||
resources.excludes.add("META-INF/AL2.0")
|
||||
resources.excludes.add("META-INF/LGPL2.1")
|
||||
resources.excludes.add("licenses/*.txt")
|
||||
resources.excludes.add("licenses/*.xml")
|
||||
}
|
||||
|
||||
configureJvmTarget()
|
||||
}
|
||||
|
||||
@@ -114,14 +117,6 @@ fun Project.driveModule(
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments["room.schemaLocation"] = "$projectDir/schemas"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
enableUnitTestCoverage = true
|
||||
@@ -136,15 +131,23 @@ fun Project.driveModule(
|
||||
this.buildConfig = buildConfig
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.add("META-INF/licenses/**")
|
||||
resources.excludes.add("META-INF/LICENSE*")
|
||||
resources.excludes.add("META-INF/AL2.0")
|
||||
resources.excludes.add("META-INF/LGPL2.1")
|
||||
resources.excludes.add("licenses/*.txt")
|
||||
resources.excludes.add("licenses/*.xml")
|
||||
}
|
||||
|
||||
configureJvmTarget()
|
||||
}
|
||||
|
||||
extensions.findByType(KaptExtension::class.java)?.let { kaptExt ->
|
||||
kaptExt.arguments {
|
||||
if (showkase) {
|
||||
arg("skipPrivatePreviews", "true")
|
||||
}
|
||||
}
|
||||
if (showkase) {
|
||||
extensions.findByType<KspExtension>()?.arg("skipPrivatePreviews", "true")
|
||||
}
|
||||
if (room) {
|
||||
extensions.findByType<KspExtension>()?.arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
extensions.configure<TestedExtension> {
|
||||
@@ -170,15 +173,6 @@ fun Project.driveModule(
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources.excludes.add("META-INF/licenses/**")
|
||||
resources.excludes.add("META-INF/LICENSE*")
|
||||
resources.excludes.add("META-INF/AL2.0")
|
||||
resources.excludes.add("META-INF/LGPL2.1")
|
||||
resources.excludes.add("licenses/*.txt")
|
||||
resources.excludes.add("licenses/*.xml")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
@@ -204,14 +198,14 @@ fun Project.driveModule(
|
||||
}
|
||||
}
|
||||
if (room) {
|
||||
add("kapt", catalog.findLibrary("androidx.room.compiler").get())
|
||||
add("ksp", catalog.findLibrary("androidx.room.compiler").get())
|
||||
add("implementation", catalog.findLibrary("core.dataRoom").get())
|
||||
add("implementation", catalog.findLibrary("androidx.room.ktx").get())
|
||||
}
|
||||
if (hilt) {
|
||||
add("implementation", catalog.findLibrary("dagger.hilt.android").get())
|
||||
add("kapt", catalog.findLibrary("dagger.hilt.compiler").get())
|
||||
add("kapt", catalog.findLibrary("androidx.hilt.compiler").get())
|
||||
add("ksp", catalog.findLibrary("dagger.hilt.compiler").get())
|
||||
add("ksp", catalog.findLibrary("androidx.hilt.compiler").get())
|
||||
}
|
||||
if (workManager) {
|
||||
if (hilt) {
|
||||
@@ -233,7 +227,7 @@ fun Project.driveModule(
|
||||
}
|
||||
if (showkase) {
|
||||
add("debugImplementation", catalog.findLibrary("showkase").get())
|
||||
add("kaptDebug", catalog.findLibrary("showkaseProcessor").get())
|
||||
add("kspDebug", catalog.findLibrary("showkaseProcessor").get())
|
||||
}
|
||||
|
||||
// region Test
|
||||
@@ -242,7 +236,7 @@ fun Project.driveModule(
|
||||
if (socialTest) {
|
||||
add("testImplementation", project(":drive:test"))
|
||||
add("testImplementation", catalog.findLibrary("dagger.hilt.android.testing").get())
|
||||
add("kaptTest", catalog.findLibrary("dagger.hilt.compiler").get())
|
||||
add("kspTest", catalog.findLibrary("dagger.hilt.compiler").get())
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
@@ -70,17 +70,17 @@ fun Project.configureJacoco(flavor: String = "", srcFolder: String = "kotlin") {
|
||||
"**/ch/protonmail/**",
|
||||
)
|
||||
|
||||
val debugTree = fileTree("$buildDir/tmp/kotlin-classes/$taskName") { exclude(fileFilter) }
|
||||
val debugTree = fileTree(project.layout.buildDirectory.dir("tmp/kotlin-classes/$taskName").get().asFile) { exclude(fileFilter) }
|
||||
val mainSrc = "$projectDir/src/main/$srcFolder"
|
||||
|
||||
sourceDirectories.setFrom(mainSrc)
|
||||
classDirectories.setFrom(debugTree)
|
||||
executionData.setFrom(fileTree(buildDir) { include(listOf("**/*.exec", "**/*.ec")) })
|
||||
executionData.setFrom(fileTree(project.layout.buildDirectory.get().asFile) { include(listOf("**/*.exec", "**/*.ec")) })
|
||||
}.dependsOn("test${taskName.capitalize(Locale.ENGLISH)}UnitTest")
|
||||
|
||||
tasks.register("coverageReport") {
|
||||
dependsOn("jacocoTestReport")
|
||||
val reportFile = project.file("$buildDir/reports/jacoco/jacocoTestReport/jacocoTestReport.xml")
|
||||
val reportFile = project.layout.buildDirectory.file("reports/jacoco/jacocoTestReport/jacocoTestReport.xml").get().asFile
|
||||
inputs.files(reportFile).withPropertyName("reportFile")
|
||||
onlyIf { reportFile.exists() }
|
||||
doLast {
|
||||
@@ -111,8 +111,8 @@ fun Project.configureJacoco(flavor: String = "", srcFolder: String = "kotlin") {
|
||||
|
||||
tasks.register<Exec>("coberturaCoverageReport") {
|
||||
dependsOn("coverageReport")
|
||||
val jacocoFile = project.file("$buildDir/reports/jacoco/jacocoTestReport/jacocoTestReport.xml")
|
||||
val coberturaFile = project.file( "$buildDir/reports/cobertura-coverage.xml")
|
||||
val jacocoFile = project.layout.buildDirectory.file("reports/jacoco/jacocoTestReport/jacocoTestReport.xml").get().asFile
|
||||
val coberturaFile = project.layout.buildDirectory.file("reports/cobertura-coverage.xml").get().asFile
|
||||
inputs.file(jacocoFile).withPropertyName("jacocoFile")
|
||||
outputs.file(coberturaFile)
|
||||
workingDir = File(rootDir, "buildSrc")
|
||||
|
||||
+9
@@ -169,6 +169,15 @@ sealed class Event {
|
||||
override val occurredAt: TimestampMs = TimestampMs()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class DownloadFileProgress(
|
||||
val downloadingCount: Int,
|
||||
val progress: Percentage,
|
||||
) : Event() {
|
||||
override val id: String = "$EVENT_ID_PREFIX${this.javaClass.simpleName.uppercase()}_1"
|
||||
override val occurredAt: TimestampMs = TimestampMs()
|
||||
}
|
||||
|
||||
data object ForcedSignOut : Event() {
|
||||
override val id: String = "$EVENT_ID_PREFIX${this.javaClass.simpleName.uppercase()}_1"
|
||||
override val occurredAt: TimestampMs = TimestampMs()
|
||||
|
||||
-1
@@ -23,7 +23,6 @@ import me.proton.core.drive.announce.event.domain.handler.EventHandler
|
||||
import me.proton.core.drive.base.domain.log.LogTag.ANNOUNCE_EVENT
|
||||
import me.proton.core.drive.base.domain.usecase.ReportError
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import javax.inject.Inject
|
||||
|
||||
class AnnounceEvent @Inject constructor(
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ class BackupManagerImpl @Inject constructor(
|
||||
}
|
||||
|
||||
override fun sync(backupFolder: BackupFolder, uploadPriority: Long) {
|
||||
CoreLogger.i(BACKUP, "Sync bucket: ${backupFolder.bucketId.toBase36()}}")
|
||||
CoreLogger.i(BACKUP, "Sync bucket: ${backupFolder.bucketId.toBase36()}")
|
||||
workManager
|
||||
.beginUniqueWork(
|
||||
backupFolder.uniqueScanWorkName(),
|
||||
|
||||
@@ -41,6 +41,7 @@ object Dto {
|
||||
const val BLOCK_LIST = "BlockList"
|
||||
const val BLOCK_NUMBER = "BlockNumber"
|
||||
const val CAPTURE_TIME = "CaptureTime"
|
||||
const val CHECKSUM_VERIFIED = "ChecksumVerified"
|
||||
const val CHILD_LINK_IDS = "ChildLinkIDs"
|
||||
const val CLIENT_UID = "ClientUID"
|
||||
const val CLIENT_UIDS = "ClientUIDs"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user