Merge remote-tracking branch 'origin/master' into dev

This commit is contained in:
Tobias Kaminsky 2024-08-02 02:31:12 +02:00
commit 3de6dfb9a6
24 changed files with 452 additions and 316 deletions

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.files

View file

@ -2,7 +2,7 @@
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/

View file

@ -1,6 +1,7 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Jonas Mayer <jonas.mayer@nextcloud.com>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH
@ -64,7 +65,7 @@ class FilesSyncWork(
private lateinit var syncedFolder: SyncedFolder
@Suppress("MagicNumber")
@Suppress("MagicNumber", "ReturnCount")
override fun doWork(): Result {
val syncFolderId = inputData.getLong(SYNCED_FOLDER_ID, -1)
val changedFiles = inputData.getStringArray(CHANGED_FILES)
@ -72,25 +73,39 @@ class FilesSyncWork(
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class) + "_" + syncFolderId)
Log_OC.d(TAG, "File-sync worker started for folder ID: $syncFolderId")
if (canExitEarly(changedFiles, syncFolderId)) {
val result = Result.success()
backgroundJobManager.logEndOfWorker(
BackgroundJobManagerImpl.formatClassTag(this::class) +
"_" + syncFolderId,
result
)
return result
}
// Create all the providers we'll need
val resources = context.resources
val lightVersion = resources.getBoolean(R.bool.syncedFolder_light)
FilesSyncHelper.restartUploadsIfNeeded(
uploadsStorageManager,
userAccountManager,
connectivityService,
powerManagementService
val filesystemDataProvider = FilesystemDataProvider(contentResolver)
val currentLocale = resources.configuration.locale
val dateFormat = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale)
dateFormat.timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
if (!setSyncedFolder(syncFolderId)) {
Log_OC.d(TAG, "File-sync kill worker since syncedFolder ($syncFolderId) is not enabled!")
return logEndOfWorker(syncFolderId)
}
// Always first try to schedule uploads to make sure files are uploaded even if worker was killed to early
uploadFilesFromFolder(
context,
resources,
lightVersion,
filesystemDataProvider,
currentLocale,
dateFormat,
syncedFolder
)
if (canExitEarly(changedFiles, syncFolderId)) {
return logEndOfWorker(syncFolderId)
}
val user = userAccountManager.getUser(syncedFolder.account)
if (user.isPresent) {
backgroundJobManager.startFilesUploadJob(user.get())
}
// Get changed files from ContentObserverWork (only images and videos) or by scanning filesystem
Log_OC.d(
TAG,
@ -100,13 +115,7 @@ class FilesSyncWork(
collectChangedFiles(changedFiles)
Log_OC.d(TAG, "File-sync worker (${syncedFolder.remotePath}) finished checking files.")
// Create all the providers we'll need
val filesystemDataProvider = FilesystemDataProvider(contentResolver)
val currentLocale = resources.configuration.locale
val dateFormat = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale)
dateFormat.timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
syncFolder(
uploadFilesFromFolder(
context,
resources,
lightVersion,
@ -116,6 +125,17 @@ class FilesSyncWork(
syncedFolder
)
FilesSyncHelper.restartUploadsIfNeeded(
uploadsStorageManager,
userAccountManager,
connectivityService,
powerManagementService
)
return logEndOfWorker(syncFolderId)
}
private fun logEndOfWorker(syncFolderId: Long): Result {
Log_OC.d(TAG, "File-sync worker (${syncedFolder.remotePath}) finished")
val result = Result.success()
backgroundJobManager.logEndOfWorker(
@ -140,6 +160,7 @@ class FilesSyncWork(
// If we are in power save mode better to postpone scan and upload
val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
if ((powerManagementService.isPowerSavingEnabled && !overridePowerSaving)) {
Log_OC.d(TAG, "File-sync kill worker since powerSaving is enabled!")
return true
}
@ -148,9 +169,8 @@ class FilesSyncWork(
return true
}
// or sync worker already running and no changed files to be processed
val alreadyRunning = backgroundJobManager.bothFilesSyncJobsRunning(syncedFolderID)
if (alreadyRunning && changedFiles.isNullOrEmpty()) {
// or sync worker already running
if (backgroundJobManager.bothFilesSyncJobsRunning(syncedFolderID)) {
Log_OC.d(
TAG,
"File-sync kill worker since another instance of the worker " +
@ -159,8 +179,17 @@ class FilesSyncWork(
return true
}
if (!setSyncedFolder(syncedFolderID)) {
Log_OC.d(TAG, "File-sync kill worker since syncedFolder ($syncedFolderID) is not enabled!")
val passedScanInterval = (
syncedFolder.lastScanTimestampMs +
FilesSyncHelper.calculateScanInterval(syncedFolder, connectivityService, powerManagementService)
) <= System.currentTimeMillis()
if (!passedScanInterval && changedFiles.isNullOrEmpty() && !overridePowerSaving) {
Log_OC.d(
TAG,
"File-sync kill worker since started before scan " +
"Interval and nothing todo (${syncedFolder.localPath})!"
)
return true
}
@ -184,10 +213,12 @@ class FilesSyncWork(
// filesystemDataProvider database (potentially needs a long time)
FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder)
}
syncedFolder.lastScanTimestampMs = System.currentTimeMillis()
syncedFolderProvider.updateSyncFolder(syncedFolder)
}
@Suppress("LongMethod") // legacy code
private fun syncFolder(
private fun uploadFilesFromFolder(
context: Context,
resources: Resources,
lightVersion: Boolean,

View file

@ -2,7 +2,7 @@
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.adapter

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.events

View file

@ -407,14 +407,19 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
private void updateSubtitle(GalleryFragmentBottomSheetDialog.MediaState mediaState) {
requireActivity().runOnUiThread(() -> {
String subTitle = requireContext().getResources().getString(R.string.subtitle_photos_videos);
if (mediaState == GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_PHOTOS_ONLY) {
subTitle = requireContext().getResources().getString(R.string.subtitle_photos_only);
} else if (mediaState == GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_VIDEOS_ONLY) {
subTitle = requireContext().getResources().getString(R.string.subtitle_videos_only);
if (!isAdded()) {
return;
}
if (requireActivity() instanceof ToolbarActivity) {
((ToolbarActivity) requireActivity()).updateToolbarSubtitle(subTitle);
String subTitle = getResources().getString(R.string.subtitle_photos_videos);
if (mediaState == GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_PHOTOS_ONLY) {
subTitle = getResources().getString(R.string.subtitle_photos_only);
} else if (mediaState == GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_VIDEOS_ONLY) {
subTitle = getResources().getString(R.string.subtitle_videos_only);
}
if (requireActivity() instanceof ToolbarActivity toolbarActivity) {
toolbarActivity.updateToolbarSubtitle(subTitle);
}
});
}

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.fragment

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.preview

View file

@ -18,7 +18,6 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.nextcloud.utils.extensions.FileExtensionsKt;
@ -29,7 +28,6 @@ import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import org.mozilla.universalchardet.ReaderFactory;
@ -45,8 +43,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.core.view.MenuItemCompat;
@ -64,9 +60,6 @@ public class PreviewTextFileFragment extends PreviewTextFragment {
private TextLoadAsyncTask textLoadAsyncTask;
private User user;
@Inject UserAccountManager accountManager;
@Inject ViewThemeUtils viewThemeUtils;
public static PreviewTextFileFragment create(User user, OCFile file, boolean openSearch, String searchQuery) {
Bundle args = new Bundle();
args.putParcelable(EXTRA_FILE, file);
@ -144,7 +137,7 @@ public class PreviewTextFileFragment extends PreviewTextFragment {
}
@Override
void loadAndShowTextPreview() {
public void loadAndShowTextPreview() {
textLoadAsyncTask = new TextLoadAsyncTask(new WeakReference<>(binding.textPreview),
new WeakReference<>(binding.emptyListProgress));
textLoadAsyncTask.execute(getFile().getStoragePath());
@ -228,7 +221,7 @@ public class PreviewTextFileFragment extends PreviewTextFragment {
if (searchView != null) {
searchView.setOnQueryTextListener(PreviewTextFileFragment.this);
if (searchOpen) {
if (searchOpen && searchView != null) {
searchView.setQuery(searchQuery, true);
}
}

View file

@ -1,229 +0,0 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2019-2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019-2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2016 ownCloud Inc.
* SPDX-FileCopyrightText: 2014 Jorge Antonio Diaz-Benito Soriano <jorge.diazbenitosoriano@gmail.com>
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
*/
package com.owncloud.android.ui.preview;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.databinding.TextFilePreviewBinding;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.fragment.FileFragment;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.StringUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.ext.tables.TablePlugin;
import io.noties.markwon.ext.tasklist.TaskListDrawable;
import io.noties.markwon.ext.tasklist.TaskListPlugin;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.syntax.Prism4jTheme;
import io.noties.markwon.syntax.Prism4jThemeDefault;
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
import io.noties.prism4j.Prism4j;
import io.noties.prism4j.annotations.PrismBundle;
@PrismBundle(
include = {
"c", "clike", "clojure", "cpp", "csharp", "css", "dart", "git", "go", "groovy", "java", "javascript", "json",
"kotlin", "latex", "makefile", "markdown", "markup", "python", "scala", "sql", "swift", "yaml"
},
grammarLocatorClassName = ".MarkwonGrammarLocator"
)
public abstract class PreviewTextFragment extends FileFragment implements SearchView.OnQueryTextListener, Injectable {
private static final String TAG = PreviewTextFragment.class.getSimpleName();
protected SearchView searchView;
protected String searchQuery = "";
protected boolean searchOpen;
protected Handler handler;
protected String originalText;
@Inject UserAccountManager accountManager;
@Inject DeviceInfo deviceInfo;
@Inject ViewThemeUtils viewThemeUtils;
protected TextFilePreviewBinding binding;
/**
* {@inheritDoc}
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Log_OC.e(TAG, "onCreateView");
binding = TextFilePreviewBinding.inflate(inflater, container, false);
View view = binding.getRoot();
binding.emptyListProgress.setVisibility(View.VISIBLE);
return view;
}
@Override
public void onStart() {
super.onStart();
Log_OC.e(TAG, "onStart");
loadAndShowTextPreview();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
abstract void loadAndShowTextPreview();
@Override
public boolean onQueryTextSubmit(String query) {
performSearch(query, 0);
return true;
}
@Override
public boolean onQueryTextChange(final String newText) {
performSearch(newText, 500);
return true;
}
private void performSearch(final String query, int delay) {
handler.removeCallbacksAndMessages(null);
if (originalText != null) {
if (getActivity() instanceof FileDisplayActivity) {
FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) getActivity();
fileDisplayActivity.setSearchQuery(query);
}
handler.postDelayed(() -> markText(query), delay);
}
if (delay == 0 && searchView != null) {
searchView.clearFocus();
}
}
private void markText(String query) {
// called asynchronously - must check preconditions in case of UI detachment
if (binding == null) {
return;
}
final Activity activity = getActivity();
if (activity == null) {
return;
}
final Resources resources = activity.getResources();
if (resources == null) {
return;
}
if (!TextUtils.isEmpty(query)) {
String coloredText = StringUtils.searchAndColor(originalText,
query,
resources.getColor(R.color.primary));
binding.textPreview.setText(Html.fromHtml(coloredText.replace("\n", "<br \\>")));
} else {
setText(binding.textPreview, originalText, getFile(), activity, false, false, viewThemeUtils);
}
}
protected static Spanned getRenderedMarkdownText(Activity activity,
String markdown,
ViewThemeUtils viewThemeUtils) {
Prism4j prism4j = new Prism4j(new MarkwonGrammarLocator());
Prism4jTheme prism4jTheme = Prism4jThemeDefault.create();
TaskListDrawable drawable = new TaskListDrawable(Color.GRAY, Color.GRAY, Color.WHITE);
viewThemeUtils.platform.tintPrimaryDrawable(activity, drawable);
final Markwon markwon = Markwon.builder(activity)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
builder.linkColor(viewThemeUtils.platform.primaryColor(activity));
builder.headingBreakHeight(0);
}
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> DisplayUtils.startLinkIntent(activity, link));
}
})
.usePlugin(TablePlugin.create(activity))
.usePlugin(TaskListPlugin.create(drawable))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(HtmlPlugin.create())
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
.build();
return markwon.toMarkdown(markdown);
}
/**
* Finishes the preview
*/
protected void finish() {
requireActivity().runOnUiThread(() -> requireActivity().onBackPressed());
}
public static void setText(TextView textView,
@Nullable String text,
@Nullable OCFile file,
Activity activity,
boolean ignoreMimetype,
boolean preview,
ViewThemeUtils viewThemeUtils) {
if (text == null) {
return;
}
if ((ignoreMimetype || file != null && MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN.equals(file.getMimeType()))
&& activity != null) {
if (!preview) {
// clickable links prevent to open full view of rich workspace
textView.setMovementMethod(LinkMovementMethod.getInstance());
}
textView.setText(getRenderedMarkdownText(activity, text, viewThemeUtils));
} else {
textView.setText(text);
}
}
}

View file

@ -0,0 +1,243 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2019-2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2019-2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2016 ownCloud Inc.
* SPDX-FileCopyrightText: 2014 Jorge Antonio Diaz-Benito Soriano <jorge.diazbenitosoriano@gmail.com>
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
*/
package com.owncloud.android.ui.preview
import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.text.Spanned
import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.di.Injectable
import com.nextcloud.utils.extensions.setHtmlContent
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.TextFilePreviewBinding
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.MimeTypeUtil
import com.owncloud.android.utils.StringUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.core.MarkwonTheme
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.ext.tasklist.TaskListDrawable
import io.noties.markwon.ext.tasklist.TaskListPlugin
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.syntax.Prism4jTheme
import io.noties.markwon.syntax.Prism4jThemeDefault
import io.noties.markwon.syntax.SyntaxHighlightPlugin
import io.noties.prism4j.Prism4j
import io.noties.prism4j.annotations.PrismBundle
import javax.inject.Inject
@PrismBundle(
include = [
"c", "clike", "clojure", "cpp", "csharp", "css", "dart", "git", "go", "groovy", "java",
"javascript", "json", "kotlin", "latex", "makefile", "markdown", "markup", "python", "scala",
"sql", "swift", "yaml"
],
grammarLocatorClassName = ".MarkwonGrammarLocator"
)
abstract class PreviewTextFragment : FileFragment(), SearchView.OnQueryTextListener, Injectable {
@JvmField
protected var searchView: SearchView? = null
@JvmField
protected var searchQuery: String = ""
@JvmField
protected var searchOpen: Boolean = false
@JvmField
protected var handler: Handler? = null
@JvmField
protected var originalText: String? = null
@Inject
lateinit var accountManager: UserAccountManager
@Inject
lateinit var deviceInfo: DeviceInfo
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
protected lateinit var binding: TextFilePreviewBinding
/**
* {@inheritDoc}
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
Log_OC.e(TAG, "onCreateView")
binding = TextFilePreviewBinding.inflate(inflater, container, false)
binding.emptyListProgress.visibility = View.VISIBLE
return binding.root
}
override fun onStart() {
super.onStart()
Log_OC.e(TAG, "onStart")
loadAndShowTextPreview()
}
abstract fun loadAndShowTextPreview()
override fun onQueryTextSubmit(query: String): Boolean {
performSearch(query, 0)
return true
}
@Suppress("MagicNumber")
override fun onQueryTextChange(newText: String): Boolean {
performSearch(newText, 500)
return true
}
private fun performSearch(query: String, delay: Int) {
handler?.removeCallbacksAndMessages(null)
if (originalText != null) {
if (activity is FileDisplayActivity) {
val fileDisplayActivity = activity as FileDisplayActivity?
fileDisplayActivity?.setSearchQuery(query)
}
handler?.postDelayed({ markText(query) }, delay.toLong())
}
if (delay == 0 && searchView != null) {
searchView?.clearFocus()
}
}
private fun markText(query: String) {
if (!TextUtils.isEmpty(query)) {
val coloredText = StringUtils.searchAndColor(
originalText,
query,
ContextCompat.getColor(requireContext(), R.color.primary)
)
binding.textPreview.setHtmlContent(coloredText.replace("\n", "<br \\>"))
} else {
val activity = activity ?: return
setText(binding.textPreview, originalText, file, activity, false, false, viewThemeUtils)
}
}
/**
* Finishes the preview
*/
protected fun finish() {
requireActivity().runOnUiThread { requireActivity().onBackPressed() }
}
companion object {
private val TAG: String = PreviewTextFragment::class.java.simpleName
protected fun getRenderedMarkdownText(
activity: Activity?,
markdown: String?,
viewThemeUtils: ViewThemeUtils?
): Spanned {
val prism4j = Prism4j(MarkwonGrammarLocator())
val prism4jTheme: Prism4jTheme = Prism4jThemeDefault.create()
val drawable = TaskListDrawable(Color.GRAY, Color.GRAY, Color.WHITE)
if (activity == null || markdown == null) {
return Markwon.builder(MainApp.getAppContext()).build().toMarkdown(markdown ?: "")
}
viewThemeUtils?.platform?.tintDrawable(activity, drawable, ColorRole.PRIMARY)
val markwon = Markwon.builder(activity)
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureTheme(builder: MarkwonTheme.Builder) {
val linkColor = viewThemeUtils?.platform?.primaryColor(activity)
linkColor?.let {
builder.linkColor(it)
}
builder.headingBreakHeight(0)
}
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { _: View?, link: String? ->
DisplayUtils.startLinkIntent(
activity,
link
)
}
}
})
.usePlugin(TablePlugin.create(activity))
.usePlugin(TaskListPlugin.create(drawable))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(HtmlPlugin.create())
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
.build()
return markwon.toMarkdown(markdown)
}
@Suppress("LongParameterList", "ComplexCondition")
@JvmStatic
fun setText(
textView: TextView,
text: String?,
file: OCFile?,
activity: Activity?,
ignoreMimetype: Boolean,
preview: Boolean,
viewThemeUtils: ViewThemeUtils?
) {
if (text == null) {
return
}
if ((ignoreMimetype || file != null && MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN == file.mimeType) &&
activity != null
) {
if (!preview) {
// clickable links prevent to open full view of rich workspace
textView.movementMethod = LinkMovementMethod.getInstance()
}
textView.text = getRenderedMarkdownText(activity, text, viewThemeUtils)
} else {
textView.text = text
}
}
}
}

View file

@ -18,15 +18,11 @@ import android.view.ViewGroup;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.utils.extensions.FileExtensionsKt;
import com.owncloud.android.R;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
@ -35,9 +31,6 @@ import androidx.core.view.MenuItemCompat;
public class PreviewTextStringFragment extends PreviewTextFragment {
private static final String EXTRA_FILE = "FILE";
@Inject UserAccountManager accountManager;
@Inject ViewThemeUtils viewThemeUtils;
private final static String TAG = "PreviewTextStringFragment";
private boolean isEditorWebviewLaunched = false;
@ -128,7 +121,7 @@ public class PreviewTextStringFragment extends PreviewTextFragment {
}
}
void loadAndShowTextPreview() {
public void loadAndShowTextPreview() {
originalText = getFile().getRichWorkspace();
setText(binding.textPreview, originalText, getFile(), requireActivity(), true, false, viewThemeUtils);

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.preview.pdf

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.preview.pdf

View file

@ -1,9 +1,9 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.preview.pdf

View file

@ -1,6 +1,7 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Jonas Mayer <jonas.mayer@nextcloud.com>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH
@ -17,6 +18,7 @@ import android.provider.MediaStore;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.jobs.BackgroundJobManagerImpl;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
import com.nextcloud.client.network.ConnectivityService;
import com.owncloud.android.MainApp;
@ -27,15 +29,20 @@ import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.lib.common.utils.Log_OC;
import org.lukhnos.nnio.file.AccessDeniedException;
import org.lukhnos.nnio.file.FileVisitResult;
import org.lukhnos.nnio.file.FileVisitor;
import org.lukhnos.nnio.file.Path;
import org.lukhnos.nnio.file.Paths;
import org.lukhnos.nnio.file.SimpleFileVisitor;
import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
import org.lukhnos.nnio.file.Files;
import org.lukhnos.nnio.file.impl.FileBasedPathImpl;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
@ -51,16 +58,50 @@ public final class FilesSyncHelper {
// utility class -> private constructor
}
/**
* Copy of {@link Files#walkFileTree(Path, FileVisitor)} that walks the file tree in random order.
*
* @see org.lukhnos.nnio.file.Files#walkFileTree(Path, FileVisitor)
*/
public static void walkFileTreeRandomly(Path start, FileVisitor<? super Path> visitor) throws IOException {
File file = start.toFile();
if (!file.canRead()) {
visitor.visitFileFailed(start, new AccessDeniedException(file.toString()));
} else {
if (Files.isDirectory(start)) {
FileVisitResult preVisitDirectoryResult = visitor.preVisitDirectory(start, (BasicFileAttributes)null);
if (preVisitDirectoryResult == FileVisitResult.CONTINUE) {
File[] children = start.toFile().listFiles();
Collections.shuffle(Arrays.asList(children));
if (children != null) {
File[] var5 = children;
int var6 = children.length;
for(int var7 = 0; var7 < var6; ++var7) {
File child = var5[var7];
walkFileTreeRandomly(FileBasedPathImpl.get(child), visitor);
}
visitor.postVisitDirectory(start, (IOException)null);
}
}
} else {
visitor.visitFile(start, new BasicFileAttributes(file));
}
}
}
private static void insertCustomFolderIntoDB(Path path,
SyncedFolder syncedFolder,
FilesystemDataProvider filesystemDataProvider,
long lastCheck,
long thisCheck) {
long lastCheck) {
final long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
try {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
walkFileTreeRandomly(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
File file = path.toFile();
@ -98,7 +139,6 @@ public final class FilesSyncHelper {
return FileVisitResult.CONTINUE;
}
});
syncedFolder.setLastScanTimestampMs(thisCheck);
} catch (IOException e) {
Log_OC.e(TAG, "Something went wrong while indexing files for auto upload", e);
}
@ -113,7 +153,6 @@ public final class FilesSyncHelper {
if (syncedFolder.isEnabled() && (syncedFolder.isExisting() || enabledTimestampMs >= 0)) {
MediaFolderType mediaType = syncedFolder.getType();
final long lastCheckTimestampMs = syncedFolder.getLastScanTimestampMs();
final long thisCheckTimestampMs = System.currentTimeMillis();
Log_OC.d(TAG,"File-sync start check folder "+syncedFolder.getLocalPath());
long startTime = System.nanoTime();
@ -121,21 +160,21 @@ public final class FilesSyncHelper {
if (mediaType == MediaFolderType.IMAGE) {
FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.INTERNAL_CONTENT_URI,
syncedFolder,
lastCheckTimestampMs, thisCheckTimestampMs);
lastCheckTimestampMs);
FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
syncedFolder,
lastCheckTimestampMs, thisCheckTimestampMs);
lastCheckTimestampMs);
} else if (mediaType == MediaFolderType.VIDEO) {
FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.INTERNAL_CONTENT_URI,
syncedFolder,
lastCheckTimestampMs, thisCheckTimestampMs);
lastCheckTimestampMs);
FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
syncedFolder,
lastCheckTimestampMs, thisCheckTimestampMs);
lastCheckTimestampMs);
} else {
FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
Path path = Paths.get(syncedFolder.getLocalPath());
FilesSyncHelper.insertCustomFolderIntoDB(path, syncedFolder, filesystemDataProvider, lastCheckTimestampMs, thisCheckTimestampMs);
FilesSyncHelper.insertCustomFolderIntoDB(path, syncedFolder, filesystemDataProvider, lastCheckTimestampMs);
}
Log_OC.d(TAG,"File-sync finished full check for custom folder "+syncedFolder.getLocalPath()+" within "+(System.nanoTime() - startTime)+ "ns");
@ -177,7 +216,7 @@ public final class FilesSyncHelper {
}
private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder,
long lastCheckTimestampMs, long thisCheckTimestampMs) {
long lastCheckTimestampMs) {
final Context context = MainApp.getAppContext();
final ContentResolver contentResolver = context.getContentResolver();
@ -224,7 +263,6 @@ public final class FilesSyncHelper {
}
}
cursor.close();
syncedFolder.setLastScanTimestampMs(thisCheckTimestampMs);
}
}
@ -260,5 +298,34 @@ public final class FilesSyncHelper {
}
}
}
public static long calculateScanInterval(
SyncedFolder syncedFolder,
ConnectivityService connectivityService,
PowerManagementService powerManagementService
) {
long defaultInterval = BackgroundJobManagerImpl.DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES * 1000 * 60;
if (!connectivityService.isConnected() || connectivityService.isInternetWalled()) {
return defaultInterval * 2;
}
if ((syncedFolder.isWifiOnly() && !connectivityService.getConnectivity().isWifi())) {
return defaultInterval * 4;
}
if (powerManagementService.getBattery().getLevel() < 80){
return defaultInterval * 2;
}
if (powerManagementService.getBattery().getLevel() < 50){
return defaultInterval * 4;
}
if (powerManagementService.getBattery().getLevel() < 20){
return defaultInterval * 8;
}
return defaultInterval;
}
}

View file

@ -516,6 +516,7 @@
قد تستغرق هذه العملية بعض الوقت.</string>
<string name="manage_space_title">إدارة المساحة</string>
<string name="max_file_count_warning_message">تجاوزت الحد الأقصى المسموح به لزمن رفع الملفات. رجاءً، لا تقم برفع عدد يزيد عن 500 ملف في كل مرة.</string>
<string name="media_err_invalid_progressive_playback">ملف الوسائط لا يمكن بثه</string>
<string name="media_err_io">تعذرت قراءة ملف الوسائط</string>
<string name="media_err_malformed">يحتوي ملف الوسائط على ترميز غير صحيح</string>
@ -670,8 +671,11 @@
<string name="prefs_value_theme_light">فاتح</string>
<string name="prefs_value_theme_system">اتبع النظام</string>
<string name="preview_image_description">معاينة الصورة</string>
<string name="preview_image_downloading_image_for_edit">جارٍ تنزيل الصورة لبدء شاشة التعديل، يرجى الانتظار...</string>
<string name="preview_image_error_no_local_file">لا يوجد ملف محلي للمعاينة</string>
<string name="preview_image_error_unknown_format">تعذرت عملية عرض الصورة</string>
<string name="preview_image_file_is_not_downloaded">لم يتم تنزيل الملف</string>
<string name="preview_image_file_is_not_exist">الملف غير موجود</string>
<string name="preview_media_unhandled_http_code_message">الملف مقفلٌ حالياً من قِبَل مستخدِمٍ أو عمليةٍ أخرى؛ وبالتالي لا يمكن حذفه. الرجاء معاودة المحاولة في وقتٍ لاحقٍ.</string>
<string name="preview_sorry">آسف</string>
<string name="privacy">الخصوصية</string>
@ -911,6 +915,9 @@
<string name="trashbin_file_remove">حذف نهائي</string>
<string name="trashbin_loading_failed">فشل تحميل سلة المحذوفات</string>
<string name="trashbin_not_emptied">تعذر حذف الملفات نهائياً!</string>
<string name="unified_search_fragment_calendar_event_not_found">لم يمكن العثور على الحدث. يمكنك دائمًا المزامنة للتحديث. جارٍ إعادة التوجيه إلى الويب...</string>
<string name="unified_search_fragment_contact_not_found">لم يمكن العثور على جهة الاتصال. يمكنك دائمًا المزامنة للتحديث. جارٍ إعادة التوجيه إلى الويب...</string>
<string name="unified_search_fragment_permission_needed">مطلوب أذونات لفتح نتيجة البحث و إلّا ستتم إعادة توجيهها إلى الويب...</string>
<string name="unlock_file">فتح قفل الملف</string>
<string name="unread_comments">توجد تعليقات غير مقروءة</string>
<string name="unset_encrypted">لم يتم تحديد التشفير</string>

View file

@ -512,6 +512,7 @@
<string name="manage_space_clear_data">Daten löschen</string>
<string name="manage_space_description">Einstellungen, Datenbank und Server-Zertifikate von %1$s\'s Daten werden dauerhaft gelöscht.\n\nHerunter geladene Dateien bleiben unangetastet.\n\nDieser Vorgang kann eine Zeit dauern.</string>
<string name="manage_space_title">Verwalte Speicherplatz</string>
<string name="max_file_count_warning_message">Sie haben die maximale Anzahl an Datei-Uploads erreicht. Bitte laden Sie weniger als 500 Dateien gleichzeitig hoch.</string>
<string name="media_err_invalid_progressive_playback">Die Mediendatei kann nicht gestreamt werden</string>
<string name="media_err_io">Konnte die Mediendatei nicht lesen</string>
<string name="media_err_malformed">Mediendatei ist nicht korrekt encodiert</string>
@ -666,8 +667,11 @@
<string name="prefs_value_theme_light">Hell</string>
<string name="prefs_value_theme_system">Systemvorgaben verwenden</string>
<string name="preview_image_description">Bildvorschau</string>
<string name="preview_image_downloading_image_for_edit">Bild wird für das Bearbeiten heruntergeladen, bitte warten…</string>
<string name="preview_image_error_no_local_file">Keine lokale Datei für die Vorschau vorhanden</string>
<string name="preview_image_error_unknown_format">Bild kann nicht angezeigt werden</string>
<string name="preview_image_file_is_not_downloaded">Datei nicht heruntergeladen</string>
<string name="preview_image_file_is_not_exist">Datei existiert nicht</string>
<string name="preview_media_unhandled_http_code_message">Die Datei ist derzeit von einem anderen Benutzer oder Prozess gesperrt und kann daher nicht gelöscht werden. Bitte später noch einmal versuchen.</string>
<string name="preview_sorry">Entschuldigung</string>
<string name="privacy">Datenschutz</string>
@ -885,6 +889,9 @@
<string name="trashbin_file_remove">Endgültig löschen</string>
<string name="trashbin_loading_failed">Laden des Papierkorbs fehlgeschlagen!</string>
<string name="trashbin_not_emptied">Dateien konnten nicht endgültig gelöscht werden!</string>
<string name="unified_search_fragment_calendar_event_not_found">Termin nicht gefunden, Sie können jederzeit die Synchronisierung wiederholen. Weiterleiten zum Web…</string>
<string name="unified_search_fragment_contact_not_found">Kontakt nicht gefunden, Sie können jederzeit synchronisieren für eine Aktualisierung. Weiterleiten zum Web…</string>
<string name="unified_search_fragment_permission_needed">Berechtigungen für das Öffnen der Suchergebnisse notwendig, ansonsten werden Sie zum Web weitergeleitet</string>
<string name="unlock_file">Datei entsperren</string>
<string name="unread_comments">Es gibt ungelesene Kommentare</string>
<string name="unset_encrypted">Verschlüsselung aufheben</string>

View file

@ -512,6 +512,7 @@
<string name="manage_space_clear_data">Borrar datos</string>
<string name="manage_space_description">Las configuraciones, base de datos y certificados del servidor de los datos de %1$s serán eliminados permanentemente.\n\nLos archivos descargados se mantendrán sin cambios.\n\nEste proceso puede tomar algo de tiempo. </string>
<string name="manage_space_title">Administrar espacio</string>
<string name="max_file_count_warning_message">Ha alcanzado el límite de carga máxima de archivos. Por favor, cargue menos de 500 archivos a la vez.</string>
<string name="media_err_invalid_progressive_playback">No se puede transmitir el archivo multimedia</string>
<string name="media_err_io">No fue posible leer el archivo de medios</string>
<string name="media_err_malformed">El archivo de medios tiene una codificación incorrecta</string>
@ -666,8 +667,11 @@
<string name="prefs_value_theme_light">Claro</string>
<string name="prefs_value_theme_system">Seguir el del sistema</string>
<string name="preview_image_description">Vista previa de imagen</string>
<string name="preview_image_downloading_image_for_edit">Descargando la imagen para abrir la pantalla de edición, por favor espere...</string>
<string name="preview_image_error_no_local_file">No existe un archivo local a previsualizar</string>
<string name="preview_image_error_unknown_format">No es posible mostrar la imagen</string>
<string name="preview_image_file_is_not_downloaded">El archivo no está descargado</string>
<string name="preview_image_file_is_not_exist">El archivo no existe</string>
<string name="preview_media_unhandled_http_code_message">El archivo se encuentra bloqueado actualmente por otro usuario o proceso y, por lo tanto, no es posible eliminarlo. Por favor, intente de nuevo más tarde.</string>
<string name="preview_sorry">Disculpa</string>
<string name="privacy">Privacidad</string>
@ -677,6 +681,7 @@
<string name="push_notifications_temp_error">En este momento las notificaciones push no están disponibles.</string>
<string name="qr_could_not_be_read">¡No se pudo leer el código QR!</string>
<string name="receive_external_files_activity_start_sync_folder_is_not_exists_message">No se pudo encontrar la carpeta, la sincronización se ha cancelado</string>
<string name="receive_external_files_activity_unable_to_find_file_to_upload">No se pudo encontrar el archivo a cargar</string>
<string name="recommend_subject">¡Prueba %1$s en tu dispositivo!</string>
<string name="recommend_text">Quiero invitarte a usar %1$s en tu dispositivo\nDescárgalo aquí:%2$s </string>
<string name="recommend_urls">%1$s ó %2$s</string>
@ -884,6 +889,9 @@
<string name="trashbin_file_remove">Borrar permanentemente</string>
<string name="trashbin_loading_failed">¡No se pudo cargar la papelera de reciclaje!</string>
<string name="trashbin_not_emptied">¡Los archivos no pudieron ser eliminados permanentemente!</string>
<string name="unified_search_fragment_calendar_event_not_found">Evento no encontrado, siempre puede sincronizar para actualizar. Redirigiendo a la web...</string>
<string name="unified_search_fragment_contact_not_found">Contacto no encontrado, siempre puede sincronizar para actualizar. Redirigiendo a la web...</string>
<string name="unified_search_fragment_permission_needed">Permisos requeridos para abrir los resultados de búsqueda, de lo contrario se redirigirá a la web...</string>
<string name="unlock_file">Archivo desbloqueado</string>
<string name="unread_comments">Existen comentarios sin leer</string>
<string name="unset_encrypted">Desestablecer encripción</string>

View file

@ -512,6 +512,7 @@
<string name="manage_space_clear_data">Limpar os datos</string>
<string name="manage_space_description">Van ser eliminados definitivamente os axustes , base de datos e certificados do servidor de %1$s.\n\nOs ficheiros descargados conservaranse sen cambios.\n\nEste proceso pode levar bastante tempo.</string>
<string name="manage_space_title">Xestionar o espazo</string>
<string name="max_file_count_warning_message">Acadou o límite máximo de envío de ficheiros. Envíe menos de 500 ficheiros dunha sentada.</string>
<string name="media_err_invalid_progressive_playback">Non é posíbel transmitir o ficheiro multimedia</string>
<string name="media_err_io">Non foi posíbel ler o ficheiro multimedia</string>
<string name="media_err_malformed">O ficheiro multimedia ten unha codificación incorrecta</string>
@ -594,7 +595,7 @@
<string name="permission_storage_access">Precísanse permisos adicionais para enviar e descargar ficheiros.</string>
<string name="picture_set_as_no_app">Non se atopou unha aplicación coa que definir unha imaxe</string>
<string name="pin_home">Fixar na pantalla de Inicio</string>
<string name="pin_shortcut_label">Aberto %1$s</string>
<string name="pin_shortcut_label">Abrir %1$s</string>
<string name="placeholder_fileSize">389 KB</string>
<string name="placeholder_filename">marcadordeposición.txt</string>
<string name="placeholder_media_time">12:23:45</string>
@ -667,8 +668,11 @@
<string name="prefs_value_theme_light">Claro</string>
<string name="prefs_value_theme_system">Seguir o sistema</string>
<string name="preview_image_description">Vista previa da imaxe</string>
<string name="preview_image_downloading_image_for_edit">Descargando a imaxe para iniciar a pantalla de edición, agarde…</string>
<string name="preview_image_error_no_local_file">Non hai ficheiro local que ver</string>
<string name="preview_image_error_unknown_format">Non é posíbel amosar a imaxe</string>
<string name="preview_image_file_is_not_downloaded">O ficheiro non está descargado</string>
<string name="preview_image_file_is_not_exist">O ficheiro non existe</string>
<string name="preview_media_unhandled_http_code_message">O ficheiro está bloqueado actualmente por outro usuario ou proceso e, polo tanto, non é posíbel eliminalo. Ténteo de novo máis tarde.</string>
<string name="preview_sorry">Desculpe.</string>
<string name="privacy">Privacidade</string>

View file

@ -512,6 +512,7 @@
<string name="manage_space_clear_data">清除資料</string>
<string name="manage_space_description">來自 %1$s 的設定,資料庫與伺服器憑證相關資料將被永久刪除,已下載的檔案將不會變動,此過程需要花費一些時間。</string>
<string name="manage_space_title">管理空間</string>
<string name="max_file_count_warning_message">您已達最大檔案上傳限制。一次上傳僅能上傳少於 500 個檔案。</string>
<string name="media_err_invalid_progressive_playback">此媒體檔案無法被串流播放</string>
<string name="media_err_io">無法讀取媒體檔案</string>
<string name="media_err_malformed">媒體檔案編碼不正確</string>
@ -666,8 +667,11 @@
<string name="prefs_value_theme_light">淺色</string>
<string name="prefs_value_theme_system">跟隨系統</string>
<string name="preview_image_description">圖片預覽</string>
<string name="preview_image_downloading_image_for_edit">正在下載影像以啟動編輯畫面,請稍候……</string>
<string name="preview_image_error_no_local_file">沒有可供預覽的本機檔案</string>
<string name="preview_image_error_unknown_format">無法顯示圖片</string>
<string name="preview_image_file_is_not_downloaded">檔案未下載</string>
<string name="preview_image_file_is_not_exist">檔案不存在</string>
<string name="preview_media_unhandled_http_code_message">檔案目前已被其他使用者或處理程序鎖定,因此無法刪除。請稍後再試。</string>
<string name="preview_sorry">很抱歉</string>
<string name="privacy">隱私權</string>
@ -885,6 +889,9 @@
<string name="trashbin_file_remove">永久刪除</string>
<string name="trashbin_loading_failed">載入回收桶失敗!</string>
<string name="trashbin_not_emptied">無法永久刪除檔案!</string>
<string name="unified_search_fragment_calendar_event_not_found">找不到活動,您隨時可以同步更新。正在重新導向至網路……</string>
<string name="unified_search_fragment_contact_not_found">找不到聯絡人,您隨時可以同步更新。正在重新導向至網路……</string>
<string name="unified_search_fragment_permission_needed">開啟搜尋結果需要權限,否則其將會重新導向至網路……</string>
<string name="unlock_file">解鎖檔案</string>
<string name="unread_comments">有未讀留言</string>
<string name="unset_encrypted">取消加密</string>

View file

@ -3,7 +3,7 @@
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.adapter