diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml index 11226bbe99..a4094ca280 100644 --- a/.idea/navEditor.xml +++ b/.idea/navEditor.xml @@ -303,18 +303,6 @@ - - - - - - - @@ -443,18 +431,6 @@ - - - - - - - @@ -875,6 +851,18 @@ + + + + + + + @@ -1143,18 +1131,6 @@ - - - - - - - diff --git a/app/src/main/java/com/infomaniak/drive/MainApplication.kt b/app/src/main/java/com/infomaniak/drive/MainApplication.kt index 36c7327cbc..a22ec0338d 100644 --- a/app/src/main/java/com/infomaniak/drive/MainApplication.kt +++ b/app/src/main/java/com/infomaniak/drive/MainApplication.kt @@ -44,6 +44,7 @@ import com.infomaniak.drive.data.models.UiSettings import com.infomaniak.drive.data.services.MqttClientWrapper import com.infomaniak.drive.ui.LaunchActivity import com.infomaniak.drive.utils.AccountUtils +import com.infomaniak.drive.utils.MyKSuiteDataUtils import com.infomaniak.drive.utils.NotificationUtils.buildGeneralNotification import com.infomaniak.drive.utils.NotificationUtils.initNotificationChannel import com.infomaniak.drive.utils.NotificationUtils.notifyCompat @@ -146,6 +147,8 @@ class MainApplication : Application(), ImageLoaderFactory, DefaultLifecycleObser ) HttpClient.init(tokenInterceptorListener) MqttClientWrapper.init(applicationContext) + + MyKSuiteDataUtils.initDatabase(this) } override fun onStart(owner: LifecycleOwner) { diff --git a/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt index 2a49a54f87..d44ffe7393 100644 --- a/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt @@ -19,6 +19,7 @@ package com.infomaniak.drive.data.api import androidx.collection.arrayMapOf import com.google.gson.JsonElement +import com.infomaniak.core.myksuite.ui.data.MyKSuiteData import com.infomaniak.drive.data.api.UploadTask.Companion.ConflictOption import com.infomaniak.drive.data.models.* import com.infomaniak.drive.data.models.ArchiveUUID.ArchiveBody @@ -44,6 +45,7 @@ import com.infomaniak.lib.core.models.ApiResponse import com.infomaniak.lib.core.models.ApiResponseStatus import com.infomaniak.lib.core.networking.HttpClient import okhttp3.OkHttpClient +import com.infomaniak.core.myksuite.ui.network.ApiRoutes as MyKSuiteApiRoutes object ApiRepository : ApiRepositoryCore() { @@ -509,6 +511,15 @@ object ApiRepository : ApiRepositoryCore() { return callApi(ApiRoutes.cancelExternalImport(driveId, importId), PUT) } + fun getMyKSuiteData(okHttpClient: OkHttpClient): ApiResponse { + return callApi( + url = MyKSuiteApiRoutes.myKSuiteData(), + method = ApiController.ApiMethod.GET, + okHttpClient = okHttpClient, + useKotlinxSerialization = true, + ) + } + private fun pagination(page: Int, perPage: Int = PER_PAGE) = "page=$page&per_page=$perPage" private fun loadCursor(cursor: String?, perPage: Int = PER_PAGE): String { diff --git a/app/src/main/java/com/infomaniak/drive/ui/MainActivity.kt b/app/src/main/java/com/infomaniak/drive/ui/MainActivity.kt index 3a72897a45..90ac206263 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/MainActivity.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/MainActivity.kt @@ -102,12 +102,14 @@ import com.infomaniak.lib.stores.updatemanagers.InAppUpdateManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import com.infomaniak.core.myksuite.R as RMyKSuite class MainActivity : BaseActivity() { private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } private val mainViewModel: MainViewModel by viewModels() + private val myKSuiteViewModel: MyKSuiteViewModel by viewModels() private val navigationArgs: MainActivityArgs? by lazy { intent?.extras?.let { MainActivityArgs.fromBundle(it) } } private val uiSettings by lazy { UiSettings(this) } private val navController by lazy { setupNavController() } @@ -204,6 +206,7 @@ class MainActivity : BaseActivity() { override fun onStart() { super.onStart() mainViewModel.loadRootFiles() + myKSuiteViewModel.refreshMyKSuite() handleDeletionOfUploadedPhotos() } @@ -439,7 +442,7 @@ class MainActivity : BaseActivity() { } when (destination.id) { - R.id.fileDetailsFragment -> { + R.id.fileDetailsFragment, RMyKSuite.id.myKSuiteDashboardFragment -> { setColorNavigationBar(true) } R.id.fileShareLinkSettingsFragment -> { diff --git a/app/src/main/java/com/infomaniak/drive/ui/MyKSuiteViewModel.kt b/app/src/main/java/com/infomaniak/drive/ui/MyKSuiteViewModel.kt new file mode 100644 index 0000000000..1470e65d2f --- /dev/null +++ b/app/src/main/java/com/infomaniak/drive/ui/MyKSuiteViewModel.kt @@ -0,0 +1,35 @@ +/* + * Infomaniak kDrive - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.drive.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.infomaniak.core.myksuite.ui.data.MyKSuiteData +import com.infomaniak.drive.utils.MyKSuiteDataUtils +import com.infomaniak.lib.core.utils.SingleLiveEvent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class MyKSuiteViewModel : ViewModel() { + + val myKSuiteDataResult = SingleLiveEvent() + + fun refreshMyKSuite() = viewModelScope.launch(Dispatchers.IO) { + MyKSuiteDataUtils.fetchData()?.let(myKSuiteDataResult::postValue) + } +} diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/settings/KSuiteDashboardFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/settings/KSuiteDashboardFragment.kt new file mode 100644 index 0000000000..8934822b27 --- /dev/null +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/settings/KSuiteDashboardFragment.kt @@ -0,0 +1,46 @@ +/* + * Infomaniak kDrive - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.drive.ui.menu.settings + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.viewModels +import com.infomaniak.core.myksuite.ui.utils.MyKSuiteUiUtils +import com.infomaniak.core.myksuite.ui.views.MyKSuiteDashboardFragment +import com.infomaniak.drive.ui.MyKSuiteViewModel +import com.infomaniak.drive.utils.AccountUtils + +class KSuiteDashboardFragment : MyKSuiteDashboardFragment() { + + private val myKSuiteViewModel: MyKSuiteViewModel by viewModels() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + myKSuiteViewModel.refreshMyKSuite() + myKSuiteViewModel.myKSuiteDataResult.observe(viewLifecycleOwner) { myKSuiteData -> + resetContent( + dashboardData = MyKSuiteUiUtils.getDashboardData( + context = requireContext(), + myKSuiteData = myKSuiteData, + avatarUri = AccountUtils.currentUser?.avatar ?: "", + ) + ) + } + } +} diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/settings/SettingsFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/settings/SettingsFragment.kt index a12013aff1..22e21db488 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/menu/settings/SettingsFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/settings/SettingsFragment.kt @@ -28,6 +28,8 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.infomaniak.core.myksuite.ui.data.MyKSuiteData +import com.infomaniak.core.myksuite.ui.utils.MyKSuiteUiUtils import com.infomaniak.drive.MatomoDrive.toFloat import com.infomaniak.drive.MatomoDrive.trackEvent import com.infomaniak.drive.R @@ -36,6 +38,7 @@ import com.infomaniak.drive.data.models.UiSettings import com.infomaniak.drive.databinding.FragmentSettingsBinding import com.infomaniak.drive.utils.AccountUtils import com.infomaniak.drive.utils.DrivePermissions +import com.infomaniak.drive.utils.MyKSuiteDataUtils import com.infomaniak.drive.utils.SyncUtils.launchAllUpload import com.infomaniak.drive.utils.SyncUtils.syncImmediately import com.infomaniak.lib.applock.LockActivity @@ -69,6 +72,8 @@ class SettingsFragment : Fragment() { requireActivity().launchAllUpload(drivePermissions) } + setupMyKSuiteLayout() + syncPicture.setOnClickListener { safeNavigate(R.id.syncSettingsActivity) } @@ -96,6 +101,34 @@ class SettingsFragment : Fragment() { } } + private fun toggleMyKSuiteLayoutVisibility(isVisible: Boolean) { + binding.myKSuiteSettingsTitle.isVisible = isVisible + binding.myKSuiteLayout.isVisible = isVisible + } + + private fun setupMyKSuiteLayout() = with(binding) { + toggleMyKSuiteLayoutVisibility(MyKSuiteDataUtils.myKSuite != null) + + MyKSuiteDataUtils.myKSuite?.let { myKSuiteData -> + + myKSuiteSettingsEmail.text = myKSuiteData.mail.email + + AccountUtils.getCurrentDrive()?.let { drive -> + myKSuiteSettingsTitle.setText(if (drive.isFreeTier) R.string.myKSuiteName else R.string.myKSuitePlusName) + } + + dashboardSettings.setOnClickListener { + trackSettingsEvent("openMyKSuiteDashboard") + openMyKSuiteDashboard(myKSuiteData) + } + } + } + + private fun openMyKSuiteDashboard(myKSuiteData: MyKSuiteData) { + val data = MyKSuiteUiUtils.getDashboardData(requireContext(), myKSuiteData, AccountUtils.currentUser?.avatar) + safeNavigate(directions = SettingsFragmentDirections.actionSettingsFragmentToMyKSuiteDashboardFragment(data)) + } + private fun openThemeSettings() { val items = arrayOf( getString(R.string.themeSettingsLightLabel), diff --git a/app/src/main/java/com/infomaniak/drive/utils/AccountUtils.kt b/app/src/main/java/com/infomaniak/drive/utils/AccountUtils.kt index d5103c2004..ced6ba4971 100644 --- a/app/src/main/java/com/infomaniak/drive/utils/AccountUtils.kt +++ b/app/src/main/java/com/infomaniak/drive/utils/AccountUtils.kt @@ -203,6 +203,7 @@ object AccountUtils : CredentialManager() { } } + MyKSuiteDataUtils.deleteData(user.id) removeUser(context, user) } diff --git a/app/src/main/java/com/infomaniak/drive/utils/MyKSuiteDataUtils.kt b/app/src/main/java/com/infomaniak/drive/utils/MyKSuiteDataUtils.kt new file mode 100644 index 0000000000..1f87351f72 --- /dev/null +++ b/app/src/main/java/com/infomaniak/drive/utils/MyKSuiteDataUtils.kt @@ -0,0 +1,57 @@ +/* + * Infomaniak kDrive - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.drive.utils + +import com.infomaniak.core.myksuite.ui.data.MyKSuiteData +import com.infomaniak.core.myksuite.ui.data.MyKSuiteDataManager +import com.infomaniak.drive.data.api.ApiRepository +import com.infomaniak.lib.core.networking.HttpClient +import com.infomaniak.lib.core.utils.SentryLog +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.MissingFieldException +import kotlin.coroutines.cancellation.CancellationException + +object MyKSuiteDataUtils : MyKSuiteDataManager() { + + private val TAG = MyKSuiteDataUtils::class.simpleName.toString() + + override val currentUserId get() = AccountUtils.currentUserId + + override var myKSuite: MyKSuiteData? = null + + override suspend fun fetchData(): MyKSuiteData? = runCatching { + MyKSuiteDataUtils.requestKSuiteData() + val apiResponse = ApiRepository.getMyKSuiteData(HttpClient.okHttpClient) + if (apiResponse.data != null) { + MyKSuiteDataUtils.upsertKSuiteData(apiResponse.data!!) + } else { + @OptIn(ExperimentalSerializationApi::class) + apiResponse.error?.exception?.let { + if (it is MissingFieldException || it.message?.contains("Unexpected JSON token") == true) { + SentryLog.e(TAG, "Error decoding the api result MyKSuiteObject", it) + } + } + } + + return@runCatching apiResponse.data + }.getOrElse { exception -> + if (exception is CancellationException) throw exception + SentryLog.d(TAG, "Exception during myKSuite data fetch", exception) + null + } +} diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index dfaa6d2138..e92aea5124 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -45,233 +45,318 @@ android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + android:layout_height="match_parent" + android:layout_marginVertical="@dimen/marginStandard" + android:orientation="vertical"> - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/marginStandard" + android:layout_marginBottom="@dimen/marginStandardMedium" + tools:text="@string/myKSuiteName" /> + + - + android:layout_height="match_parent" + android:orientation="vertical"> - - + tools:text="ellen.ripley@ik.me" /> - + - - - - - - - - - - - + android:background="?selectableItemBackground" + android:padding="20dp"> + + + + + + + + + + + + + - + android:layout_height="match_parent" + android:orientation="vertical"> - - - + + + + + + + + + + - - + + + + + + + + + + - - - - - - - - + + + + + + + + + + - - + + + + + + + + + + - - - - + android:background="?selectableItemBackground" + android:paddingHorizontal="@dimen/marginStandard" + android:paddingTop="@dimen/marginStandardSmall" + android:paddingBottom="@dimen/marginStandard"> + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 6cd6cfa919..b8959e9f8a 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -509,6 +509,11 @@ + + + +