From 8bb2211cedf0d96c544ab11a64cec47d439a6844 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 24 Jul 2026 16:32:44 +0200 Subject: [PATCH 1/3] Rename .java to .kt Signed-off-by: alperozturk96 --- ...ailActivitiesFragment.java => FileDetailActivitiesFragment.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/ui/fragment/{FileDetailActivitiesFragment.java => FileDetailActivitiesFragment.kt} (100%) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java rename to app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt From 80c46e810f858618c84fa6967b3a285c78cebc61 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 24 Jul 2026 16:32:45 +0200 Subject: [PATCH 2/3] FileDetailActivitiesFragment Kotlin Signed-off-by: alperozturk96 --- .../FileDetailActivitiesFragmentIT.kt | 34 + .../fragment/FileDetailActivitiesFragment.kt | 815 ++++++++---------- 2 files changed, 409 insertions(+), 440 deletions(-) create mode 100644 app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragmentIT.kt diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragmentIT.kt new file mode 100644 index 000000000000..be3e536f7e11 --- /dev/null +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragmentIT.kt @@ -0,0 +1,34 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.fragment + +import com.nextcloud.client.account.User +import com.nextcloud.utils.extensions.getParcelableArgument +import com.owncloud.android.AbstractIT +import com.owncloud.android.datamodel.OCFile +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test + +class FileDetailActivitiesFragmentIT : AbstractIT() { + + @Test + fun newInstanceStoresFileAndUserInArguments() { + val file = OCFile("/test.txt").apply { fileId = 12 } + + val fragment = FileDetailActivitiesFragment.newInstance(file, user) + + val arguments = fragment.arguments + assertNotNull(arguments) + + val storedFile = arguments.getParcelableArgument("FILE", OCFile::class.java) + val storedUser = arguments.getParcelableArgument("USER", User::class.java) + + assertEquals(file.fileId, storedFile?.fileId) + assertEquals(user.accountName, storedUser?.accountName) + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt index 451d37aaa4ac..3f0ba8a6b034 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt @@ -1,555 +1,490 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2019 Chris Narkiewicz * SPDX-FileCopyrightText: 2018 Andy Scherzinger * SPDX-FileCopyrightText: 2025 TSI-mc * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.owncloud.android.ui.fragment; - -import android.content.ContentResolver; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Editable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.google.android.material.snackbar.Snackbar; -import com.nextcloud.client.account.User; -import com.nextcloud.client.account.UserAccountManager; -import com.nextcloud.client.di.Injectable; -import com.nextcloud.client.network.ClientFactory; -import com.nextcloud.common.NextcloudClient; -import com.nextcloud.utils.extensions.BundleExtensionsKt; -import com.nextcloud.utils.extensions.FileExtensionsKt; -import com.owncloud.android.R; -import com.owncloud.android.databinding.FileDetailsActivitiesFragmentBinding; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.activities.GetActivitiesRemoteOperation; -import com.owncloud.android.lib.resources.activities.model.RichObject; -import com.owncloud.android.lib.resources.comments.MarkCommentsAsReadRemoteOperation; -import com.owncloud.android.lib.resources.files.ReadFileVersionsRemoteOperation; -import com.owncloud.android.lib.resources.files.model.FileVersion; -import com.owncloud.android.lib.resources.status.OCCapability; -import com.owncloud.android.operations.CommentFileOperation; -import com.owncloud.android.ui.activity.ComponentsGetter; -import com.owncloud.android.ui.activities.adapter.ActivityAndVersionListAdapter; -import com.owncloud.android.ui.events.CommentsEvent; -import com.owncloud.android.ui.helpers.FileOperationsHelper; -import com.owncloud.android.ui.interfaces.ActivityListInterface; -import com.owncloud.android.ui.interfaces.VersionListInterface; -import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import org.apache.commons.httpclient.HttpStatus; -import org.greenrobot.eventbus.EventBus; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.core.content.res.ResourcesCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.lifecycle.Lifecycle; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -public class FileDetailActivitiesFragment extends Fragment implements +package com.owncloud.android.ui.fragment + +import android.content.ContentResolver +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.DrawableRes +import androidx.annotation.VisibleForTesting +import androidx.core.content.res.ResourcesCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import com.nextcloud.client.account.User +import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.di.Injectable +import com.nextcloud.client.network.ClientFactory +import com.nextcloud.client.network.ClientFactory.CreationException +import com.nextcloud.common.NextcloudClient +import com.nextcloud.utils.extensions.getParcelableArgument +import com.owncloud.android.R +import com.owncloud.android.databinding.FileDetailsActivitiesFragmentBinding +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.activities.GetActivitiesRemoteOperation +import com.owncloud.android.lib.resources.activities.model.RichObject +import com.owncloud.android.lib.resources.comments.MarkCommentsAsReadRemoteOperation +import com.owncloud.android.lib.resources.files.ReadFileVersionsRemoteOperation +import com.owncloud.android.lib.resources.files.model.FileVersion +import com.owncloud.android.operations.CommentFileOperation +import com.owncloud.android.ui.activities.adapter.ActivityAndVersionListAdapter +import com.owncloud.android.ui.activity.ComponentsGetter +import com.owncloud.android.ui.events.CommentsEvent +import com.owncloud.android.ui.helpers.FileOperationsHelper +import com.owncloud.android.ui.interfaces.ActivityListInterface +import com.owncloud.android.ui.interfaces.VersionListInterface +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener +import com.owncloud.android.utils.theme.ViewThemeUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.apache.commons.httpclient.HttpStatus +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + +@Suppress("TooManyFunctions", "ReturnCount") +class FileDetailActivitiesFragment : + Fragment(), ActivityListInterface, - DisplayUtils.AvatarGenerationListener, + AvatarGenerationListener, VersionListInterface.View, Injectable { - private static final String TAG = FileDetailActivitiesFragment.class.getSimpleName(); + private var adapter: ActivityAndVersionListAdapter? = null + private var ownCloudClient: OwnCloudClient? = null + private var nextcloudClient: NextcloudClient? = null - private static final String ARG_FILE = "FILE"; - private static final String ARG_USER = "USER"; - private static final int END_REACHED = 0; + private var file: OCFile? = null + private var user: User? = null - private ActivityAndVersionListAdapter adapter; - private OwnCloudClient ownCloudClient; - private NextcloudClient nextcloudClient; + private var lastGiven: Long = 0 + private var isLoadingActivities = false + private var isDataFetched = false - private OCFile file; - private User user; + private var restoreFileVersionSupported = false + private var operationsHelper: FileOperationsHelper? = null + private var callback: VersionListInterface.CommentCallback? = null - private long lastGiven; - private boolean isLoadingActivities; - private boolean isDataFetched = false; + private var submitCommentJob: Job? = null - private boolean restoreFileVersionSupported; - private FileOperationsHelper operationsHelper; - private VersionListInterface.CommentCallback callback; + private var binding: FileDetailsActivitiesFragmentBinding? = null - private SubmitCommentTask submitCommentTask; - - FileDetailsActivitiesFragmentBinding binding; + @Inject + lateinit var accountManager: UserAccountManager - @Inject UserAccountManager accountManager; - @Inject ClientFactory clientFactory; - @Inject ContentResolver contentResolver; - @Inject ViewThemeUtils viewThemeUtils; + @Inject + lateinit var clientFactory: ClientFactory - public static FileDetailActivitiesFragment newInstance(OCFile file, User user) { - FileDetailActivitiesFragment fragment = new FileDetailActivitiesFragment(); - Bundle args = new Bundle(); - args.putParcelable(ARG_FILE, file); - args.putParcelable(ARG_USER, user); - fragment.setArguments(args); - return fragment; - } + @Inject + lateinit var contentResolver: ContentResolver - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, - Bundle savedInstanceState) { + @Inject + lateinit var viewThemeUtils: ViewThemeUtils - final Bundle arguments = getArguments(); - if (arguments == null) { - throw new IllegalStateException("arguments are mandatory"); - } - file = BundleExtensionsKt.getParcelableArgument(arguments, ARG_FILE, OCFile.class); - user = BundleExtensionsKt.getParcelableArgument(arguments, ARG_USER, User.class); + // region Lifecycle + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val arguments = checkNotNull(arguments) { "arguments are mandatory" } + val source = savedInstanceState ?: arguments + file = source.getParcelableArgument(ARG_FILE, OCFile::class.java) + user = source.getParcelableArgument(ARG_USER, User::class.java) - if (savedInstanceState != null) { - file = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_FILE, OCFile.class); - user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_USER, User.class); - } + val binding = FileDetailsActivitiesFragmentBinding.inflate(inflater, container, false) + this.binding = binding - binding = FileDetailsActivitiesFragmentBinding.inflate(inflater, container, false); - View view = binding.getRoot(); + setupView() - setupView(); + viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingEmpty) + viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingList) - viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingEmpty); - viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingList); + isLoadingActivities = true + fetchAndSetData(-1) - isLoadingActivities = true; - fetchAndSetData(-1); + setupRefreshListeners(binding) + callback = createCommentCallback() - binding.swipeContainingList.setOnRefreshListener(() -> { - setLoadingMessage(); - binding.swipeContainingList.setRefreshing(true); - isLoadingActivities = true; - fetchAndSetData(-1); - }); + binding.submitComment.setOnClickListener { submitComment() } + viewThemeUtils.material.colorTextInputLayout(binding.commentInputFieldContainer) - binding.swipeContainingEmpty.setOnRefreshListener(() -> { - setLoadingMessageEmpty(); - isLoadingActivities = true; - fetchAndSetData(-1); - }); + DisplayUtils.setAvatar( + user!!, + this, + resources.getDimension(R.dimen.activity_icon_radius), + resources, + binding.avatar, + context + ) - callback = new VersionListInterface.CommentCallback() { + return binding.root + } - @Override - public void onSuccess() { - if (binding != null && getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - binding.commentInputField.getText().clear(); - fetchAndSetData(-1); - } - } + override fun onDestroyView() { + super.onDestroyView() + submitCommentJob?.cancel() + submitCommentJob = null + callback = null + binding = null + } - @Override - public void onError(int error) { - View view = getView(); - if (view != null && isAdded()) { - Snackbar.make(view, error, Snackbar.LENGTH_LONG).show(); + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(ARG_FILE, file) + outState.putParcelable(ARG_USER, user) + } + // endregion + + // region Setup + private fun setupView() { + val binding = binding ?: return + val storageManager = FileDataStorageManager(user, contentResolver) + operationsHelper = (requireActivity() as ComponentsGetter).fileOperationsHelper + + val capability = storageManager.getCapability(user?.accountName) + restoreFileVersionSupported = capability.filesVersioning.isTrue + + binding.emptyList.emptyListIcon.setImageDrawable( + ResourcesCompat.getDrawable(resources, R.drawable.ic_activity, null) + ) + binding.emptyList.emptyListView.visibility = View.GONE + + adapter = ActivityAndVersionListAdapter(requireActivity(), accountManager, this, this, viewThemeUtils) + binding.list.adapter = adapter + + val layoutManager = LinearLayoutManager(context) + binding.list.layoutManager = layoutManager + binding.list.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val visibleItemCount = recyclerView.childCount + val totalItemCount = layoutManager.itemCount + val firstVisibleItemIndex = layoutManager.findFirstVisibleItemPosition() + + val reachedEnd = (totalItemCount - visibleItemCount) <= (firstVisibleItemIndex + LOAD_MORE_THRESHOLD) + if (!isLoadingActivities && reachedEnd && lastGiven > 0) { + fetchAndSetData(lastGiven) } } - }; - - binding.submitComment.setOnClickListener(v -> submitComment()); - - viewThemeUtils.material.colorTextInputLayout(binding.commentInputFieldContainer); - - DisplayUtils.setAvatar(user, - this, - getResources().getDimension(R.dimen.activity_icon_radius), - getResources(), - binding.avatar, - getContext()); - - return view; + }) } - public void submitComment() { - if (binding == null) { - return; + private fun setupRefreshListeners(binding: FileDetailsActivitiesFragmentBinding) { + binding.swipeContainingList.setOnRefreshListener { + setLoadingMessage() + binding.swipeContainingList.isRefreshing = true + isLoadingActivities = true + fetchAndSetData(-1) } - Editable commentField = binding.commentInputField.getText(); - - if (commentField == null) { - return; + binding.swipeContainingEmpty.setOnRefreshListener { + setLoadingMessageEmpty() + isLoadingActivities = true + fetchAndSetData(-1) } + } - String trimmedComment = commentField.toString().trim(); - - if (!trimmedComment.isEmpty() && nextcloudClient != null && isDataFetched) { - // Cancel previous task - if (submitCommentTask != null) { - submitCommentTask.cancel(true); + private fun createCommentCallback() = object : VersionListInterface.CommentCallback { + override fun onSuccess() { + if (binding != null && lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + binding?.commentInputField?.text?.clear() + fetchAndSetData(-1) } - - submitCommentTask = new SubmitCommentTask( - trimmedComment, - file.getLocalId(), - callback, - nextcloudClient - ); - submitCommentTask.execute(); } - } - private void setLoadingMessage() { - if (binding != null) { - binding.swipeContainingEmpty.setVisibility(View.GONE); + override fun onError(error: Int) { + val view = view ?: return + if (isAdded) { + Snackbar.make(view, error, Snackbar.LENGTH_LONG).show() + } } } + // endregion - @VisibleForTesting - public void setLoadingMessageEmpty() { - if (binding != null) { - binding.swipeContainingList.setVisibility(View.GONE); - binding.emptyList.emptyListView.setVisibility(View.GONE); - binding.loadingContent.setVisibility(View.VISIBLE); - } + // region Data loading + fun reload() { + fetchAndSetData(-1) } - @Override - public void onDestroyView() { - super.onDestroyView(); - - // Cancel any pending async operations - if (submitCommentTask != null) { - submitCommentTask.cancel(true); - submitCommentTask = null; + /** + * @param lastGiven long; -1 to disable + */ + private fun fetchAndSetData(lastGiven: Long) { + val activity = activity + if (activity == null) { + Log_OC.e(this, "Activity is null, aborting!") + return } - callback = null; // Clear callback reference - binding = null; - } - - private void setupView() { - FileDataStorageManager storageManager = new FileDataStorageManager(user, - contentResolver); - operationsHelper = ((ComponentsGetter) requireActivity()).getFileOperationsHelper(); - - OCCapability capability = storageManager.getCapability(user.getAccountName()); - restoreFileVersionSupported = capability.getFilesVersioning().isTrue(); - - binding.emptyList.emptyListIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_activity, null)); - binding.emptyList.emptyListView.setVisibility(View.GONE); - - adapter = new ActivityAndVersionListAdapter(requireActivity(), - accountManager, - this, - this, - viewThemeUtils - ); - binding.list.setAdapter(adapter); - - LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); - - binding.list.setLayoutManager(layoutManager); - binding.list.addOnScrollListener(new RecyclerView.OnScrollListener() { + val user = accountManager.user + if (user.isAnonymous) { + setEmptyContent( + getString(R.string.common_error), + getString(R.string.file_detail_activity_error) + ) + return + } - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); + if (!isLoadingActivities) { + return + } - int visibleItemCount = recyclerView.getChildCount(); - int totalItemCount = layoutManager.getItemCount(); - int firstVisibleItemIndex = layoutManager.findFirstVisibleItemPosition(); + val file = file ?: return - // synchronize loading state when item count changes - if (!isLoadingActivities && (totalItemCount - visibleItemCount) <= (firstVisibleItemIndex + 5) - && lastGiven > 0) { - // Almost reached the end, continue to load new activities - fetchAndSetData(lastGiven); + lifecycleScope.launch { + try { + val (result, versions) = withContext(Dispatchers.IO) { + loadActivities(user, file, lastGiven) } + handleActivitiesResult(activity, result, versions, lastGiven) + hideRefreshLayoutLoader() + } catch (e: CreationException) { + isDataFetched = false + Log_OC.e(TAG, "Error fetching file details activities", e) } - }); - } - - public void reload() { - fetchAndSetData(-1); + } } - /** - * @param lastGiven long; -1 to disable - */ - private void fetchAndSetData(long lastGiven) { - final FragmentActivity activity = getActivity(); - - if (activity == null) { - Log_OC.e(this, "Activity is null, aborting!"); - return; + private fun loadActivities( + user: User, + file: OCFile, + lastGiven: Long + ): Pair, ArrayList?> { + val ownCloudClient = clientFactory.create(user) + this.ownCloudClient = ownCloudClient + val nextcloudClient = clientFactory.createNextcloudClient(user) + this.nextcloudClient = nextcloudClient + isLoadingActivities = true + + val operation = if (lastGiven > 0) { + GetActivitiesRemoteOperation(file.localId, lastGiven) + } else { + GetActivitiesRemoteOperation(file.localId) } - final User user = accountManager.getUser(); + Log_OC.d(TAG, "BEFORE getRemoteActivitiesOperation.execute") + val result = nextcloudClient.execute(operation) - if (user.isAnonymous()) { - activity.runOnUiThread(() -> setEmptyContent(getString(R.string.common_error), getString(R.string.file_detail_activity_error))); - return; - } - - if (!isLoadingActivities) { - return; - } + val versions = when { + !restoreFileVersionSupported -> null - Thread t = new Thread(() -> { - try { - ownCloudClient = clientFactory.create(user); - nextcloudClient = clientFactory.createNextcloudClient(user); + else -> ReadFileVersionsRemoteOperation(file.localId).execute(ownCloudClient) + .takeIf { it.isSuccess } + ?.data + } - isLoadingActivities = true; + return result to versions + } - GetActivitiesRemoteOperation getRemoteNotificationOperation; + @Suppress("UNCHECKED_CAST") + private fun handleActivitiesResult( + activity: FragmentActivity, + result: RemoteOperationResult, + versions: ArrayList?, + lastGiven: Long + ) { + val data = result.data + if (result.isSuccess && data != null) { + val activitiesAndVersions = data[0] as ArrayList + this.lastGiven = data[1] as Long + + if (activitiesAndVersions.isEmpty()) { + this.lastGiven = END_REACHED.toLong() + } - if (lastGiven > 0) { - getRemoteNotificationOperation = new GetActivitiesRemoteOperation(file.getLocalId(), lastGiven); - } else { - getRemoteNotificationOperation = new GetActivitiesRemoteOperation(file.getLocalId()); - } + if (restoreFileVersionSupported && versions != null) { + activitiesAndVersions.addAll(versions) + } - Log_OC.d(TAG, "BEFORE getRemoteActivitiesOperation.execute"); - final var result = nextcloudClient.execute(getRemoteNotificationOperation); + if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { + populateList(activitiesAndVersions, lastGiven == -1L) + } - ArrayList versions = null; - if (restoreFileVersionSupported) { - ReadFileVersionsRemoteOperation readFileVersionsOperation = new ReadFileVersionsRemoteOperation( - file.getLocalId()); + isDataFetched = true + return + } - final var result1 = readFileVersionsOperation.execute(ownCloudClient); + Log_OC.d(TAG, result.logMessage) + val logMessage = if (result.httpCode == HttpStatus.SC_NOT_MODIFIED) { + getString(R.string.activities_no_results_message) + } else { + result.getLogMessage(activity) + } - if (result1.isSuccess()) { - versions = result1.getData(); - } - } + if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + setErrorContent(logMessage) + isLoadingActivities = false + } - if (result.isSuccess() && result.getData() != null) { - final List data = result.getData(); - final List activitiesAndVersions = (ArrayList) data.get(0); - - this.lastGiven = (long) data.get(1); - - if (activitiesAndVersions.isEmpty()) { - this.lastGiven = END_REACHED; - } - - if (restoreFileVersionSupported && versions != null) { - activitiesAndVersions.addAll(versions); - } - - activity.runOnUiThread(() -> { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { - populateList(activitiesAndVersions, lastGiven == -1); - } - }); - - isDataFetched = true; - } else { - Log_OC.d(TAG, result.getLogMessage()); - - String logMessage = result.getLogMessage(activity); - if (result.getHttpCode() == HttpStatus.SC_NOT_MODIFIED) { - logMessage = getString(R.string.activities_no_results_message); - } - final String finalLogMessage = logMessage; - activity.runOnUiThread(() -> { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - setErrorContent(finalLogMessage); - isLoadingActivities = false; - } - }); - - isDataFetched = false; - } + isDataFetched = false + } + // endregion + + // region Comments + fun submitComment() { + val binding = binding ?: return + val client = nextcloudClient + val comment = binding.commentInputField.text?.toString()?.trim().orEmpty() + if (comment.isEmpty() || client == null || !isDataFetched) { + return + } + val fileId = file?.localId ?: return - hideRefreshLayoutLoader(activity); - } catch (ClientFactory.CreationException e) { - isDataFetched = false; - Log_OC.e(TAG, "Error fetching file details activities", e); + submitCommentJob?.cancel() + submitCommentJob = lifecycleScope.launch { + val success = withContext(Dispatchers.IO) { + CommentFileOperation(comment, fileId).execute(client).isSuccess } - }); - - t.start(); + if (success) { + callback?.onSuccess() + } else { + callback?.onError(R.string.error_comment_file) + } + } } - public void markCommentsAsRead() { - new Thread(() -> { - if (file.getUnreadCommentsCount() > 0) { - MarkCommentsAsReadRemoteOperation unreadOperation = new MarkCommentsAsReadRemoteOperation( - file.getLocalId()); - RemoteOperationResult remoteOperationResult = unreadOperation.execute(ownCloudClient); + fun markCommentsAsRead() { + val file = file ?: return + if (file.unreadCommentsCount <= 0) { + return + } + val client = ownCloudClient - if (remoteOperationResult.isSuccess()) { - EventBus.getDefault().post(new CommentsEvent(file.getRemoteId())); - } + lifecycleScope.launch(Dispatchers.IO) { + val result = MarkCommentsAsReadRemoteOperation(file.localId).execute(client) + if (result.isSuccess) { + EventBus.getDefault().post(CommentsEvent(file.remoteId)) } - }).start(); + } + } + // endregion + + // region View state + private fun setLoadingMessage() { + binding?.swipeContainingEmpty?.visibility = View.GONE } @VisibleForTesting - public void populateList(List activities, boolean clear) { - adapter.setActivityAndVersionItems(activities, nextcloudClient, clear); + fun setLoadingMessageEmpty() { + val binding = binding ?: return + binding.swipeContainingList.visibility = View.GONE + binding.emptyList.emptyListView.visibility = View.GONE + binding.loadingContent.visibility = View.VISIBLE + } - if (binding == null) { - return; - } + @VisibleForTesting + fun populateList(activities: List?, clear: Boolean) { + val items = ArrayList(activities ?: emptyList()) + adapter?.setActivityAndVersionItems(items, nextcloudClient, clear) + + val binding = binding ?: return - if (adapter.getItemCount() == 0) { + if (adapter?.itemCount == 0) { setEmptyContent( getString(R.string.activities_no_results_headline), getString(R.string.activities_no_results_message) - ); + ) } else { - binding.swipeContainingList.setVisibility(View.VISIBLE); - binding.swipeContainingEmpty.setVisibility(View.GONE); - binding.emptyList.emptyListView.setVisibility(View.GONE); + binding.swipeContainingList.visibility = View.VISIBLE + binding.swipeContainingEmpty.visibility = View.GONE + binding.emptyList.emptyListView.visibility = View.GONE } - isLoadingActivities = false; + isLoadingActivities = false } - private void setEmptyContent(String headline, String message) { - setInfoContent(R.drawable.ic_activity, headline, message); + private fun setEmptyContent(headline: String?, message: String?) { + setInfoContent(R.drawable.ic_activity, headline, message) } @VisibleForTesting - public void setErrorContent(String message) { - setInfoContent(R.drawable.ic_list_empty_error, getString(R.string.common_error), message); + fun setErrorContent(message: String?) { + setInfoContent(R.drawable.ic_list_empty_error, getString(R.string.common_error), message) } - private void setInfoContent(@DrawableRes int icon, String headline, String message) { - if (binding == null) { - return; - } + private fun setInfoContent(@DrawableRes icon: Int, headline: String?, message: String?) { + val binding = binding ?: return - binding.emptyList.emptyListIcon.setImageDrawable(ResourcesCompat.getDrawable(requireContext().getResources(), - icon, - null)); - binding.emptyList.emptyListViewHeadline.setText(headline); - binding.emptyList.emptyListViewText.setText(message); + binding.emptyList.emptyListIcon.setImageDrawable( + ResourcesCompat.getDrawable(requireContext().resources, icon, null) + ) + binding.emptyList.emptyListViewHeadline.text = headline + binding.emptyList.emptyListViewText.text = message - binding.swipeContainingList.setVisibility(View.GONE); - binding.loadingContent.setVisibility(View.GONE); + binding.swipeContainingList.visibility = View.GONE + binding.loadingContent.visibility = View.GONE - binding.emptyList.emptyListViewHeadline.setVisibility(View.VISIBLE); - binding.emptyList.emptyListViewText.setVisibility(View.VISIBLE); - binding.emptyList.emptyListIcon.setVisibility(View.VISIBLE); - binding.emptyList.emptyListView.setVisibility(View.VISIBLE); - binding.swipeContainingEmpty.setVisibility(View.VISIBLE); + binding.emptyList.emptyListViewHeadline.visibility = View.VISIBLE + binding.emptyList.emptyListViewText.visibility = View.VISIBLE + binding.emptyList.emptyListIcon.visibility = View.VISIBLE + binding.emptyList.emptyListView.visibility = View.VISIBLE + binding.swipeContainingEmpty.visibility = View.VISIBLE } - private void hideRefreshLayoutLoader(FragmentActivity activity) { - activity.runOnUiThread(() -> { - if (binding != null && getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - binding.swipeContainingList.setRefreshing(false); - binding.swipeContainingEmpty.setRefreshing(false); - isLoadingActivities = false; - } - }); + private fun hideRefreshLayoutLoader() { + val binding = binding ?: return + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + return + } + binding.swipeContainingList.isRefreshing = false + binding.swipeContainingEmpty.isRefreshing = false + isLoadingActivities = false } + // endregion - @Override - public void onActivityClicked(RichObject richObject) { + // region Interface callbacks + override fun onActivityClicked(richObject: RichObject?) { // TODO implement activity click } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - FileExtensionsKt.logFileSize(file, TAG); - outState.putParcelable(ARG_FILE, file); - outState.putParcelable(ARG_USER, user); + override fun onRestoreClicked(fileVersion: FileVersion?) { + operationsHelper?.restoreFileVersion(fileVersion) } - @Override - public void onRestoreClicked(FileVersion fileVersion) { - operationsHelper.restoreFileVersion(fileVersion); + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { + binding?.avatar?.setImageDrawable(avatarDrawable) } - @Override - public void avatarGenerated(Drawable avatarDrawable, Object callContext) { - if (binding != null) { - binding.avatar.setImageDrawable(avatarDrawable); - } - } + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = false + // endregion - @Override - public boolean shouldCallGeneratedCallback(String tag, Object callContext) { - return false; - } - @VisibleForTesting - public void disableLoadingActivities() { - isLoadingActivities = false; + fun disableLoadingActivities() { + isLoadingActivities = false } - private static class SubmitCommentTask extends AsyncTask { - - private final String message; - private final long fileId; - private final WeakReference callbackRef; - private final NextcloudClient client; - - private SubmitCommentTask(String message, - long fileId, - VersionListInterface.CommentCallback callback, - NextcloudClient client) { - this.message = message; - this.fileId = fileId; - this.callbackRef = new WeakReference<>(callback); - this.client = client; - } - - @Override - protected Boolean doInBackground(Void... voids) { - if (isCancelled()) { - return false; - } - - CommentFileOperation commentFileOperation = new CommentFileOperation(message, fileId); - RemoteOperationResult result = commentFileOperation.execute(client); - return result.isSuccess(); - } - - @Override - protected void onPostExecute(Boolean success) { - super.onPostExecute(success); - - // Don't call callback if task was cancelled - if (isCancelled()) { - return; - } + companion object { + private val TAG: String = FileDetailActivitiesFragment::class.java.simpleName - VersionListInterface.CommentCallback callback = callbackRef.get(); - if (callback == null) { - // Fragment was destroyed, callback was GC'd - return; - } - - if (success) { - callback.onSuccess(); - } else { - callback.onError(R.string.error_comment_file); + private const val ARG_FILE = "FILE" + private const val ARG_USER = "USER" + private const val END_REACHED = 0 + private const val LOAD_MORE_THRESHOLD = 5 + @JvmStatic + fun newInstance(file: OCFile?, user: User?): FileDetailActivitiesFragment = + FileDetailActivitiesFragment().apply { + arguments = Bundle().apply { + putParcelable(ARG_FILE, file) + putParcelable(ARG_USER, user) + } } - } } } From f1d32133450e671c862ca6e7fb5eea5ff42bdb54 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 27 Jul 2026 15:44:51 +0200 Subject: [PATCH 3/3] wip Signed-off-by: alperozturk96 --- .../android/ui/fragment/FileDetailActivitiesFragment.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt index 3f0ba8a6b034..8283e0d82439 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.kt @@ -227,6 +227,7 @@ class FileDetailActivitiesFragment : /** * @param lastGiven long; -1 to disable */ + @Suppress("DEPRECATION") private fun fetchAndSetData(lastGiven: Long) { val activity = activity if (activity == null) { @@ -263,6 +264,7 @@ class FileDetailActivitiesFragment : } } + @Suppress("DEPRECATION") private fun loadActivities( user: User, file: OCFile, @@ -280,7 +282,6 @@ class FileDetailActivitiesFragment : GetActivitiesRemoteOperation(file.localId) } - Log_OC.d(TAG, "BEFORE getRemoteActivitiesOperation.execute") val result = nextcloudClient.execute(operation) val versions = when { @@ -294,7 +295,7 @@ class FileDetailActivitiesFragment : return result to versions } - @Suppress("UNCHECKED_CAST") + @Suppress("UNCHECKED_CAST", "DEPRECATION") private fun handleActivitiesResult( activity: FragmentActivity, result: RemoteOperationResult, @@ -361,6 +362,7 @@ class FileDetailActivitiesFragment : } } + @Suppress("DEPRECATION") fun markCommentsAsRead() { val file = file ?: return if (file.unreadCommentsCount <= 0) { @@ -392,7 +394,7 @@ class FileDetailActivitiesFragment : @VisibleForTesting fun populateList(activities: List?, clear: Boolean) { - val items = ArrayList(activities ?: emptyList()) + val items = ArrayList(activities ?: emptyList()) adapter?.setActivityAndVersionItems(items, nextcloudClient, clear) val binding = binding ?: return